原文:ASP.NET Core Razor 编辑表单 - ASP.NET Core 基础教程 - 简单教程,简单编程
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