ASP.NET Core Razor 编辑表单
上一章节我们介绍了标签助手和 HTML 助手,也使用标签助手和 HTML 助手分别创建了一个职工列表,感觉好像有点喜欢上标签助手和 HTML 助手了,正好之前我们只讲解了如何列出数据,没有讲解如何创建表单来添加和修改数据
要不本章节我们就来讲讲? 顺带多用用标签助手和 HTML 助手
本章中,我们将继续讨论标签助手。
与此同时,还会给 HelloWorld 应用程序中添加一项新功能,并使其能够编辑现有员工的详细信息
我们将首先在每位员工旁边上添加一个链接,以便访问 HomeController 上的 "编辑" 操作,同时将页面美化一下
修改 Index.cshtml 为如下内容
@model HomePageViewModel
@{
ViewBag.Title = "职工列表";
}
<style>
body {margin:10px auto;text-align:center}
table {
margin:0 auto;
width:90%
}
table, th, td {
border:1px solid #eee;
border-collapse:collapse;
border-spacing:0;
padding:5px;
text-align:center
}
.txt-left {
text-align:left;
}
</style>
<h1>职工列表</h1>
<table>
<tr>
<td>ID</td>
<td>姓名</td>
<td class="txt-left">操作</td>
</tr>
@foreach (var employee in Model.Employees)
{
<tr>
<td>@employee.ID</td>
<td>@employee.Name</td>
<td class="txt-left"><a asp-action="Detail" asp-route-Id="@employee.ID">详情</a> <a asp-controller="Home" asp-action="Edit"
asp-route-id="@employee.ID">编辑</a></td>
</tr>
}
</table>
刷新浏览器,显示结果如下

如果我们此时点击任意一个 编辑 ,则会抛出 404 页面不存在错误

我们先来给 HomeController 添加一个 Edit 方法,接受一个 int 类型的 id 参数,然后返回一个 IActionResult 的结果,方法内容如下
[HttpGet] public IActionResult Edit(int id) { var model = new HomePageViewModel(); SQLEmployeeData sqlData = new SQLEmployeeData(_context); Employee employee = sqlData.Get(id); if ( employee == null ) { return RedirectToAction("Index"); } return View(employee); }
这段代码出现了很多新面孔,比如 [HttpGet] 特性,有关 C# 特性的知识,请访问我们的 C# 基础教程:特性 ( Attribute )
ASP.NET Core 所有以 Http 开头的特性用于标识该方法只接受特定的 HTTP 请求方法
ASP.NET Core 支持下图七个 HTTP 请求方法特性

| 特性 | 说明 |
|---|---|
| HttpGet | 标识某个方法只支持 HTTP GET 请求方法 |
| HttpPut | 标识某个方法只支持 HTTP PUT 请求方法 |
| HttpHead | 标识某个方法只支持 HTTP HEAD 请求方法 |
| HttpPost | 标识某个方法只支持 HTTP POST 请求方法 |
| HttpPatch | 标识某个方法只支持 HTTP PATCH 请求方法 |
| HttpDelete | 标识某个方法只支持 HTTP DELETE 请求方法 |
| HttpOptions | 标识某个方法只支持 HTTP OPTIONS 请求方法 |
关于这些 HTTP 请求方法的介绍,可以访问我们的 HTTP 基础教程:HTTP 请求方法
第二个新面孔就是方法的返回值 IActionResult ,我们在 ASP.NET Core 动作结果 有提到,IActionResult 是所有返回结果的必须实现的接口,用这个作为返回值,也就是说该方法可以接受任意返回值。
第三个新面孔就是下面这段代码
if ( employee == null ) { return RedirectToAction("Index"); }
意思是是如果 employee 的结果为空,也就是没有在数据库里找到 id 对应的记录,则重定向到 Index 方法,从某些方面说也就是重定向到首页
RedirectTo 家族非常庞大,数量之多,几乎涵盖了各种情况下的重定向

好了,重启我们的应用程序,然后刷新浏览器,自然,肯定是出错了,因为缺少对应的视图

那么,我们就在 Views/Home 下添加一个视图 Edit.cshtml 吧,添加方法和之前添加 Index.cshtml 、 Detail.cshtml 一样,我们就不做介绍了,然后输入以下内容
@model Employee
@{
ViewBag.Title = $"编辑 {Model.Name}";
}
<h1>Edit @Model.Name</h1>
<form asp-action="Edit" method="post">
<div>
<label asp-for="Name">员工姓名</label>
<input asp-for="Name" />
<span asp-validation-for = "Name"></span>
</div>
<div>
<input type = "submit" value = "保存" />
</div>
</form>
我们来解释下下面这段代码
保存我们的 Edit.cshtml,刷新浏览器,还是报错了。这次的错误是提示 Employee 类没有找到

修改 _ViewImports.cshtml 添加 @using HelloWorld.Models,添加完成后 _ViewImports.cshtml 的内容如下
@namespace HelloWorld.Views @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @using HelloWorld.Controllers @using HelloWorld.Models
保存,然后刷新浏览器,这回显示正确了

当然了,不管我们是否编辑了员工姓名,如果点击保存,还是会报错,提示 404 不存在

这是因为我们刚刚添加的方法只适用于 HTTP GET 请求方法
我们需要再重载一个 Edit 方法用于接受 HTTP POST 请求
[HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { SQLEmployeeData sqlData = new SQLEmployeeData( _context ); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; _context.SaveChanges(); return RedirectToAction("Detail", new { id = employee.ID }); } return View(employee); }
根据我们的路由规则,编辑表单应始终使用包含了 id 的 URL 来传递,如 /home/edit/1,
而显示表单和提交表单数据的最大差别就是 HTTP 请求方法,提交表单使用 [HttpPost] 特性
ASP.NET Core MVC 框架可以从 URL 中提起 id 数据并作为参数传递给动作方法
至于表单传递的其它数据,比如员工姓名,则使用另一个模型来接受不了,这个模型就是 EmployeeEditViewModel
EmployeeEditViewModel
EmployeeEditViewModel 用来从 HTTP POST 请求方法中接受传递的表单数据
那 EmployeeEditViewModel 到底长啥样呢,其实它跟 HomePageViewModel 差不多
我们再添加一下代码到 HomePageViewModel 后面
public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } }
HomePageViewModel 包含了一个属性 Name,注意,Name 的名称和大小写必须和表单一模一样。
然后使用 C# 特性来限制 Name 字段必须输入数据 ( Required ),且数据的最大长度不得超过 80 ( MaxLength(80) )
因为表单只有一个字段 Name ,所以模型也就只有一个属性 Name,很简单,我们就不多做阐述了。
最后,使用 Required 等注解需要引入命名空间 System.ComponentModel.DataAnnotations
`
ASP.NET Core MVC 参数模型绑定
回到 public IActionResult Edit(int id, EmployeeEditViewModel input) 方法,相比你也很好奇,ASP.NET Core MVC 是怎么给我们的 Edit 的两个参数传递实际的值的
我查阅了很多文档,原来 ASP.NET Core 使用了一种叫 模型绑定 的方法
模型绑定 是将 HTTP 请求的各种数据映射到动作方法参数的一种方法,参数即可以是简单类型,如字符串、整数或浮点数,也可以是复杂类型,如一个类。
模型绑定 查找数据源的顺序是
- Form values: 通过 HTTP POST 方法提交的数据,例如
Name - Route values: 路由器解析的参数,例如
id - Query String: URI 中的查询字符串部分的数据,也就是
?后面那一串
而模型绑定按照上面的顺序,只要找到了数据,就会停止继续查找。假设我们我们的 URI 为 /home/edit/1 ,而我们的表单有一个字段 id 值为 2 ,那么 Edit 方法的值为 2 而不是一,这是因为已经在 Form 表单中找到了 id 值,所以就不会继续查找了
对于复杂的数据,比如 HomePageViewModel,它的属性也是有要求的
- 要求接收数据的属性必须是
public的,如果不是,那么就没法注入值 - 要求必须有一个无参数的构造函数
当发生绑定时,类只会使用公共默认构造函数来实例化,然后在设置属性值。
ModelState
当一个参数绑定了数据后,模型绑定就会停止查找该名称的值,并继续绑定下一个参数。
如果某个参数绑定失败,那么 MVC 框架也不会抛出任何错误,而是设置 ModelState 的属性为 false,仅此而已
因此,我们可以通过检查 ModelState 的值来检查数据格式是否正确
_context.SaveChanges();
_context.SaveChanges(); 用于保存验证后的数据到数据库
其实 模型绑定 还有很多内容,但是限于篇幅,我们就不再继续了,如果你想深入阅读,可以查阅 ASP.NET 官方文档
运行范例
我们回到 HomeController.cs,当所有的修改完成后,完整的代码如下
HomeController
using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; using HelloWorld.Models; namespace HelloWorld.Controllers { public class HomeController: Controller { private readonly HelloWorldDBContext _context; public HomeController(HelloWorldDBContext context) { _context = context; } public ViewResult Index() { var model = new HomePageViewModel(); SQLEmployeeData sqlData = new SQLEmployeeData(_context); model.Employees = sqlData.GetAll(); return View(model); } public ViewResult Detail(int id) { var model = new HomePageViewModel(); SQLEmployeeData sqlData = new SQLEmployeeData(_context); Employee employee = sqlData.Get(id); return View(employee); } [HttpGet] public IActionResult Edit(int id) { var model = new HomePageViewModel(); SQLEmployeeData sqlData = new SQLEmployeeData(_context); Employee employee = sqlData.Get(id); if ( employee == null ) { return RedirectToAction("Index"); } return View(employee); } [HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { SQLEmployeeData sqlData = new SQLEmployeeData( _context ); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; _context.SaveChanges(); return RedirectToAction("Detail", new { id = employee.ID }); } return View(employee); } } public class SQLEmployeeData { private HelloWorldDBContext _context { get; set; } public SQLEmployeeData(HelloWorldDBContext context) { _context = context; } public void Add(Employee emp) { _context.Add(emp); _context.SaveChanges(); } public Employee Get(int ID) { return _context.Employees.FirstOrDefault(e => e.ID == ID); } public IEnumerable<Employee> GetAll() { return _context.Employees.ToList<Employee>(); } } public class HomePageViewModel { public IEnumerable<Employee> Employees { get; set; } } }
保存 HomeController.cs,重启应用程序,然后刷新浏览器,结果如下

点击 ID 为 1 的 编辑,显示如下

我们将 李白 改成 李太白

点击保存

然后点击返回首页

ASP.NET Core MVC 的基础教程终于学习完了