在 ASP.NET Core MVC 上传文件
admin
2021-06-03本文作者:梁桐铭- 微软最有价值专家(Microsoft MVP)
本文出自《从零开始学 ASP.NET Core 与 EntityFramework Core》目录
视频课程效果更佳:跨平台开发实战掌握 ASP.NET Core 与 EntityFramework Core
在 ASP.NET Core MVC 上传文件
在本章节中,我们将做一个案例如何使用 ASP.NET Core MVC 上传文件。我们会完善我们的创建学生的表单信息。
我们会实现下面的功能,在创建学生信息的时候,可以使用表单中的图像字段来上传学生的照片信息。
提交信息后,我们能够在数据库Students表中,存储 Student 类的数据,包含Name,Email,Major,PhotoPath信息。
下面是数据库 Students 表的信息,它是通过 ASP.NET Core 中的迁移功能创建的 。
Id | 名字 | 电子邮件 | 主修科目 | 图片路径 |
---|---|---|---|---|
1 | 52ABP管理员 | 2 | info@ddxc.org | info.png |
2 | 梁桐铭 | 3 | ltm@ddxc.org | ltm.png |
学生的照片信息,会上传到 Web 服务器上的wwwroot/images
文件夹中。
为了实现这个上传功能,我们需要添加修改和添加以下几个类文件。
学生模型类
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public MajorEnum? Major { get; set; }
public string PhotoPath { get; set; }
}
Student 类文件中的各种验证属性已经移除掉了,因为从现在开始它的职责更多的就是负责同步数据库的架构。而基于添加学生信息所需要进行的业务规则的验证,我们需要添加一个新的类文件,叫做StudentCreateViewModel.cs,在我们之前的课程中提到过,很多时候领域模型是不满足我们的业务要求的,所以需要使用视图模型也被称作 DTO,来帮助我们完善业务。
StudentCreateViewModel 文件
public class StudentCreateViewModel
{
[Required(ErrorMessage = "请输入名字"), MaxLength(50, ErrorMessage = "名字的长度不能超过50个字符")]
[Display(Name = "名字")]
public string Name { get; set; }
[Required]
[Display(Name = "主修科目")]
public MajorEnum? Major { get; set; }
[Display(Name = "电子邮件")]
[RegularExpression(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
ErrorMessage = "邮箱的格式不正确")]
[Required(ErrorMessage = "请输入邮箱地址")]
public string Email { get; set; }
[Display(Name = "图像")]
public IFormFile Photo { get; set; }
}
在我们的StudentCreateViewModel文件中添加了一个类型IFormFile的 Photo 的字段。
- IFormFile 位于
Microsoft.AspNetCore.Http
命名空间中。 - 上传至服务器的文件可通过 IFormFile 接口通过模型绑定的形式进行访问。
- Photos 属性通过模型绑定接收上传的文件
- 如果要支持多个文件上传,我们将 Photos 属性的数据类型设置为 List即可
- 接口
IFormFile
具有以下属性和方法
public interface IFormFile
{
string ContentType { get; }
string ContentDisposition { get; }
IHeaderDictionary Headers { get; }
long Length { get; }
string Name { get; }
string FileName { get; }
Stream OpenReadStream();
void CopyTo(Stream target);
Task CopyToAsync(Stream target, CancellationToken cancellationToken = null);
}
名称 | 内容 |
---|---|
ContentType | 获取上传文件的原始Content-Type标头。 |
ContentDisposition | 获取上传文件的原始Content-Disposition标头。 |
Length | 获取文件长度,以字节为单位。 |
FileName | 从Content-Disposition标头中获取文件名。 |
Name | 从Content-Disposition标头中获取的字段名称。 |
Headers | 获取上传文件的HTTP消息头的字典信息。 |
OpenReadStream | 打开请求流以读取上载的文件。 |
CopyTo | 将上传文件的内容复制到流 |
CopyToAsync | 异步地将上传文件的内容复制到流 |
以上就是这些属性以及方法的说明。
更新 Create 视图的代码
@model StudentCreateViewModel
@{ ViewBag.Title = "创建学生信息"; }
要支持文件上传,请设置表单元素为enctype="multipart/form-data"
- enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。
- application/x-www-form-urlencoded 在发送前编码所有字符(默认)
- multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
- text/plain 空格转换为 "+" 加号,但不对特殊字符编码。 以上都是 Hhtml 的基础。
<form
enctype="multipart/form-data"
asp-controller="home"
asp-action="create"
method="post"
class="mt-3"
>
<div class="form-group row">
<label asp-for="Name" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="Name" class="form-control" placeholder="请输入名字" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<label asp-for="Email" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input
asp-for="Email"
class="form-control"
placeholder="请输入邮箱地址"
/>
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<label asp-for="Major" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<select
asp-for="Major"
class="custom-select mr-sm-2"
asp-items="Html.GetEnumSelectList<MajorEnum>()"
>
<option value=""> 请选择</option>
</select>
<span asp-validation-for="Major" class="text-danger"></span>
</div>
</div>
@* 我们使用了asp-for的taghelper设置input的属性为"Photo"。
"Photo"属性类型是IFormFile, 所以在运行的时候ASP.NET
Core会将该标签生成上传控件(input type=file) *@
<div class="form-group row">
<label asp-for="Photo" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<div class="custom-file">
<input asp-for="Photo" class="form-control custom-file-input" />
<label class="custom-file-label">请选择照片...</label>
</div>
</div>
</div>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">创建</button>
</div>
</div>
@*以下JavaScript代码的作用是,可以在上传标签中显示选定的上传文件名称。*@
@section Scripts {
<script>
$(document).ready(function() {
$(".custom-file-input").on("change", function() {
var fileName = $(this)
.val()
.split("\\")
.pop();
$(this)
.next(".custom-file-label")
.html(fileName);
});
});
</script>
}
</form>
我们使用了 asp-for 的 taghelper 标签,将设置 input 的属性为"Photo"。而"Photo"属性类型是 IFormFile
, 所以在 ASP.NET
Core 运行的时候会自动将该标签生成为上传控件(input type=file),参考以下代码:
<input
class="form-control custom-file-input"
type="file"
id="Photo"
name="Photo"
/>
因为原有的
Student
领域模型已经不满足我们的业务呈现了,所以当前页面视图模型改用StudentCreateViewModel
。在结束
form
标签之前的 JavaScript 代码的作用是,可以增强上传控件,可以显示选定的上传文件名称。
修改 Create 操作方法
我们回到HomeController
文件中,因为要让 Create()方法支持文件上传,所以我们要对这个方法进行修改。修改规则如下:
- 判断用户是否上传了图片,如果没有上传图片路径信息为空。
- 所以我们要判断
StudentCreateViewModel
中的Photo
属性是否为空
- 所以我们要判断
- 如果有上传图片,则要进行规则验证。
- 所有的图片都必须上传到
wwwroot
中的images
文件夹中。- 而要获取
wwwroot
文件夹的路径,我们需要通过 ASP.NET Core 中的依赖注入注册HostingEnvironment
服务
- 而要获取
- 为了确保文件名是唯一的,文件名的生成规则为 GUID 值加一个下划线。
完整的上传文件代码如下:
[HttpPost]
public IActionResult Create(StudentCreateViewModel model)
{
if (ModelState.IsValid)
{
string uniqueFileName = null;
//如果传入模型对象中的Photo属性不为null,则表示该用户选择了要上传的图片信息。
if (model.Photo != null)
{
//必须将图像上传到wwwroot中的images文件夹
//而要获取wwwroot文件夹的路径,我们需要注入 ASP.NET Core提供的HostingEnvironment服务
string uploadsFolder = Path.Combine(hostingEnvironment.WebRootPath, "images");
//为了确保文件名是唯一的,我们在文件名后附加一个新的GUID值和一个下划线
uniqueFileName = Guid.NewGuid().ToString() + "_" + model.Photo.FileName;
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
//使用IFormFile接口提供的CopyTo()方法将文件复制到wwwroot/images文件夹
model.Photo.CopyTo(new FileStream(filePath, FileMode.Create));
}
Student newStudent = new Student
{
Name = model.Name,
Email = model.Email,
Major = model.Major,
// 将文件名保存在student对象的PhotoPath属性中。
//它将保存到数据库 Students的 表中
PhotoPath = uniqueFileName
};
_studentRepository.Add(newStudent);
return RedirectToAction("details", new { id = newStudent.Id });
}
return View();
}
此HomeController
文件中的其余代码不需要上传文件。涉及到图片显示的视图页面为学生详情页面和学生列表页面,所以去修改对应的代码地方即可。
学生详情视图页面代码
@model HomeDetailsViewModel
@{
ViewBag.Title = "学生详情页";
var photoPath = "~/images/" + (Model.Student.PhotoPath ?? "noimage.jpg");
}
<div class="row justify-content-center m-3">
<div class="col-sm-8">
<div class="card">
<div class="card-header">
<h1>@Model.Student.Name</h1>
</div>
<div class="card-body text-center">
<img class="card-img-top" src="@photoPath" asp-append-version="true"/>
<h4>学生ID : @Model.Student.Id</h4>
<h4>邮箱 : @Model.Student.Email</h4>
<h4>主修科目名称 : @Model.Student.Major</h4>
</div>
<div class="card-footer text-center">
<a asp-action="Index" asp-controller="home" class="btn btn-primary">返回</a>
<a href="#" class="btn btn-primary">编辑</a>
<a href="#" class="btn btn-danger">删除</a>
</div>
</div>
</div>
</div>
@section Scripts{
<script src="~/js/CustomScript.js"></script>
}
视图详情页面需要显示我们的学生图片信息,所以我们声明一个photoPath属性来拼接我们的图片路径完整地址。
将原本的<img class="card-img-top" src="~/images/timg.jpg"/>
修改为
<img class="card-img-top" src="@photoPath" asp-append-version="true" />
同时给 img 标签加上 asp-append-version="true"
为我们的图片提供缓存服务。
##学生列表视图页面代码
同样的学生列表视图页面,我们也只需要加上一个photoPath属性即可。和详情页面不同的是,因为列表页面存在多个不同的内容,所以photoPath不能设置为全局的,我们需要在 foreach 方法中添加以下代码。
var photoPath = "~/images/" + (student.PhotoPath ?? "noimage.jpg");
同样的将原来的纯静态网页
<img class="card-img-top" src="~/images/timg.jpg" asp-append-version="true" />
替换为
<img
class="card-img-top imageThumbnail"
src="@photoPath"
asp-append-version="true"
/>
同时为 img 标签的 class 属性添加了一个imageThumbnail
自定义样式
最终完整的学生列表页面代码如下:
@model IEnumerable<Student>
@{
ViewBag.Title = "学生列表页面";
}
<div class="card-deck">
@foreach (var student in Model)
{
var photoPath = "~/images/" + (student.PhotoPath ?? "noimage.jpg");
<div class="card m-3">
<div class="card-header">
<h3>@student.Name</h3>
</div>
<img class="card-img-top imageThumbnail" src="@photoPath"
asp-append-version="true"/>
<div class="card-footer text-center">
<a asp-controller="home" asp-action="details" asp-route-id="@student.Id" class="btn btn-primary m-1">查看</a>
<a href="#" class="btn btn-primary m-1">编辑</a>
<a href="#" class="btn btn-danger m-1">删除</a>
</div>
</div>
}
</div>
最后我们到 wwwroot 文件夹中的 css 文件夹中的 site.css
添加以下样式内容:
.imageThumbnail {
height: 200px;
width: auto;
}
文章说明
如果您觉得我的文章质量还不错,欢迎打赏,也可以订阅我的视频哦
未得到授权不得擅自转载本文内容,52abp.com 保留版权
感谢您对我的支持