第 6 部分,ASP.NET Core 中的控制器方法和视图
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。
打开 Models/Movie.cs
文件,并添加以下代码中突出显示的行:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
将在下一教程中说明。 Display 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。
要使 Entity Framework Core 能将 Price
正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")]
数据注释。 有关详细信息,请参阅数据类型。
浏览到 Movies
控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL。
“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml
文件中由 Core MVC 定位标记帮助程序生成的。
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 在上面的代码中,AnchorTagHelper
从控制器操作方法和路由 ID 动态生成 HTML href
特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记。 生成的 HTML 的一部分如下所示:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
重新调用在 Program.cs
文件中设置的路由的格式:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core 将 https://localhost:5001/Movies/Edit/4
转换为对 Movies
控制器的 Edit
操作方法的请求,参数 Id
为 4。 (控制器方法也称为操作方法。)
标记帮助程序是 ASP.NET Core 中最受欢迎的新功能之一。 有关详细信息,请参阅其他资源。
打开 Movies
控制器并检查两个 Edit
操作方法。 以下代码显示了 HTTP GET Edit
方法,此方法将提取电影并填充由 Edit.cshtml
Razor 文件生成的编辑表单。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
以下代码显示 HTTP POST Edit
方法,它会处理已发布的电影值:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[Bind]
特性是防止过度发布的一种方法。 只应在 [Bind]
特性中包含想要更改的属性。 有关详细信息,请参阅防止控制器过度发布。 ViewModels 提供了一种替代方法以防止过度发布。
请注意第二个 Edit
操作方法的前面是 [HttpPost]
特性。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
HttpPost
特性指定只能为 POST
请求调用此 Edit
方法。 可将 [HttpGet]
属性应用于第一个编辑方法,但不是必需,因为 [HttpGet]
是默认设置。
ValidateAntiForgeryToken
特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml
) 中生成的防伪令牌匹配。 编辑视图文件使用表单标记帮助程序生成防伪令牌。
<form asp-action="Edit">
表单标记帮助程序会生成隐藏的防伪令牌,此令牌必须与电影控制器的 Edit
方法中 [ValidateAntiForgeryToken]
生成的防伪令牌匹配。 有关详细信息,请参阅在 ASP.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击。
HttpGet Edit
方法采用电影 ID
参数,使用Entity Framework FindAsync
方法查找电影,并将所选电影返回到“编辑”视图。 如果无法找到电影,则返回 NotFound
(HTTP 404)。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie
语句。 @model MvcMovie.Models.Movie
指定视图期望的视图模板的模型为 Movie
类型。
基架的代码使用几个标记帮助程序方法来简化 HTML 标记。 标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。 输入标记帮助程序呈现 HTML <input>
元素。 验证标记帮助程序显示与该属性相关联的任何验证消息。
运行应用程序并导航到 /Movies
URL。 点击“编辑”链接。 在浏览器中查看页面的源。 为 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
<input>
元素位于 HTML <form>
元素中,后者的 action
特性设置为发布到 /Movies/Edit/id
URL。 当单击 Save
按钮时,表单数据将发布到服务器。 关闭 </form>
元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。
处理 POST 请求
以下列表显示了 Edit
操作方法的 [HttpPost]
版本。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性验证表单标记帮助程序中的防伪令牌生成器生成的隐藏 XSRF 令牌
模型绑定系统采用发布的表单值,并创建一个作为 movie
参数传递的 Movie
对象。 ModelState.IsValid
属性验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync
方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController
类的 Index
操作方法,此方法显示电影集合,包括刚才所做的更改。
在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会发布表单。 如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。 稍后在本教程中,我们将更详细地研究模型验证。 Views/Movies/Edit.cshtml
视图模板中的验证标记帮助程序负责显示相应的错误消息。
电影控制器中的所有 HttpGet
方法都遵循类似的模式。 它们获取电影对象(对于 Index
获取的是对象列表)并将对象(模型)传递给视图。 Create
方法将空的电影对象传递给 Create
视图。 在方法的 [HttpPost]
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 以 HTTP GET
方式修改数据是一种安全隐患。 以 HTTP GET
方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。
其他资源
电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。
打开 Models/Movie.cs
文件,并添加以下代码中突出显示的行:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
将在下一教程中说明。 Display 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。
要使 Entity Framework Core 能将 Price
正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")]
数据注释。 有关详细信息,请参阅数据类型。
浏览到 Movies
控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL。
“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml
文件中由 Core MVC 定位标记帮助程序生成的。
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 在上面的代码中,AnchorTagHelper
从控制器操作方法和路由 ID 动态生成 HTML href
特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记。 生成的 HTML 的一部分如下所示:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
重新调用在 Program.cs
文件中设置的路由的格式:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core 将 https://localhost:5001/Movies/Edit/4
转换为对 Movies
控制器的 Edit
操作方法的请求,参数 Id
为 4。 (控制器方法也称为操作方法。)
标记帮助程序是 ASP.NET Core 中最受欢迎的新功能之一。 有关详细信息,请参阅其他资源。
打开 Movies
控制器并检查两个 Edit
操作方法。 以下代码显示了 HTTP GET Edit
方法,此方法将提取电影并填充由 Edit.cshtml
Razor 文件生成的编辑表单。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
以下代码显示 HTTP POST Edit
方法,它会处理已发布的电影值:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[Bind]
特性是防止过度发布的一种方法。 只应在 [Bind]
特性中包含想要更改的属性。 有关详细信息,请参阅防止控制器过度发布。 ViewModels 提供了一种替代方法以防止过度发布。
请注意第二个 Edit
操作方法的前面是 [HttpPost]
特性。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
HttpPost
特性指定只能为 POST
请求调用此 Edit
方法。 可将 [HttpGet]
属性应用于第一个编辑方法,但不是必需,因为 [HttpGet]
是默认设置。
ValidateAntiForgeryToken
特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml
) 中生成的防伪令牌匹配。 编辑视图文件使用表单标记帮助程序生成防伪令牌。
<form asp-action="Edit">
表单标记帮助程序会生成隐藏的防伪令牌,此令牌必须与电影控制器的 Edit
方法中 [ValidateAntiForgeryToken]
生成的防伪令牌匹配。 有关详细信息,请参阅在 ASP.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击。
HttpGet Edit
方法采用电影 ID
参数,使用Entity Framework FindAsync
方法查找电影,并将所选电影返回到“编辑”视图。 如果无法找到电影,则返回 NotFound
(HTTP 404)。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie
语句。 @model MvcMovie.Models.Movie
指定视图期望的视图模板的模型为 Movie
类型。
基架的代码使用几个标记帮助程序方法来简化 HTML 标记。 标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。 输入标记帮助程序呈现 HTML <input>
元素。 验证标记帮助程序显示与该属性相关联的任何验证消息。
运行应用程序并导航到 /Movies
URL。 点击“编辑”链接。 在浏览器中查看页面的源。 为 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
<input>
元素位于 HTML <form>
元素中,后者的 action
特性设置为发布到 /Movies/Edit/id
URL。 当单击 Save
按钮时,表单数据将发布到服务器。 关闭 </form>
元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。
处理 POST 请求
以下列表显示了 Edit
操作方法的 [HttpPost]
版本。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性验证表单标记帮助程序中的防伪令牌生成器生成的隐藏 XSRF 令牌
模型绑定系统采用发布的表单值,并创建一个作为 movie
参数传递的 Movie
对象。 ModelState.IsValid
属性验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync
方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController
类的 Index
操作方法,此方法显示电影集合,包括刚才所做的更改。
在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会发布表单。 如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。 稍后在本教程中,我们将更详细地研究模型验证。 Views/Movies/Edit.cshtml
视图模板中的验证标记帮助程序负责显示相应的错误消息。
电影控制器中的所有 HttpGet
方法都遵循类似的模式。 它们获取电影对象(对于 Index
获取的是对象列表)并将对象(模型)传递给视图。 Create
方法将空的电影对象传递给 Create
视图。 在方法的 [HttpPost]
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 以 HTTP GET
方式修改数据是一种安全隐患。 以 HTTP GET
方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。
其他资源
电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。
打开 Models/Movie.cs
文件,并添加以下代码中突出显示的行:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations
将在下一教程中说明。 Display 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。
要使 Entity Framework Core 能将 Price
正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")]
数据注释。 有关详细信息,请参阅数据类型。
浏览到 Movies
控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL。
“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml
文件中由 Core MVC 定位标记帮助程序生成的。
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 在上面的代码中,AnchorTagHelper
从控制器操作方法和路由 ID 动态生成 HTML href
特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记。 生成的 HTML 的一部分如下所示:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
重新调用在 Program.cs
文件中设置的路由的格式:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core 将 https://localhost:5001/Movies/Edit/4
转换为对 Movies
控制器的 Edit
操作方法的请求,参数 Id
为 4。 (控制器方法也称为操作方法。)
标记帮助程序是 ASP.NET Core 中最受欢迎的新功能之一。 有关详细信息,请参阅其他资源。
打开 Movies
控制器并检查两个 Edit
操作方法。 以下代码显示了 HTTP GET Edit
方法,此方法将提取电影并填充由 Edit.cshtml
Razor 文件生成的编辑表单。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
以下代码显示 HTTP POST Edit
方法,它会处理已发布的电影值:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[Bind]
特性是防止过度发布的一种方法。 只应在 [Bind]
特性中包含想要更改的属性。 有关详细信息,请参阅防止控制器过度发布。 ViewModels 提供了一种替代方法以防止过度发布。
请注意第二个 Edit
操作方法的前面是 [HttpPost]
特性。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
HttpPost
特性指定只能为 POST
请求调用此 Edit
方法。 可将 [HttpGet]
属性应用于第一个编辑方法,但不是必需,因为 [HttpGet]
是默认设置。
ValidateAntiForgeryToken
特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml
) 中生成的防伪令牌匹配。 编辑视图文件使用表单标记帮助程序生成防伪令牌。
<form asp-action="Edit">
表单标记帮助程序会生成隐藏的防伪令牌,此令牌必须与电影控制器的 Edit
方法中 [ValidateAntiForgeryToken]
生成的防伪令牌匹配。 有关详细信息,请参阅在 ASP.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击。
HttpGet Edit
方法采用电影 ID
参数,使用Entity Framework FindAsync
方法查找电影,并将所选电影返回到“编辑”视图。 如果无法找到电影,则返回 NotFound
(HTTP 404)。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie
语句。 @model MvcMovie.Models.Movie
指定视图期望的视图模板的模型为 Movie
类型。
基架的代码使用几个标记帮助程序方法来简化 HTML 标记。 标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。 输入标记帮助程序呈现 HTML <input>
元素。 验证标记帮助程序显示与该属性相关联的任何验证消息。
运行应用程序并导航到 /Movies
URL。 点击“编辑”链接。 在浏览器中查看页面的源。 为 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
<input>
元素位于 HTML <form>
元素中,后者的 action
特性设置为发布到 /Movies/Edit/id
URL。 当单击 Save
按钮时,表单数据将发布到服务器。 关闭 </form>
元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。
处理 POST 请求
以下列表显示了 Edit
操作方法的 [HttpPost]
版本。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性验证表单标记帮助程序中的防伪令牌生成器生成的隐藏 XSRF 令牌
模型绑定系统采用发布的表单值,并创建一个作为 movie
参数传递的 Movie
对象。 ModelState.IsValid
属性验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync
方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController
类的 Index
操作方法,此方法显示电影集合,包括刚才所做的更改。
在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会发布表单。 如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。 稍后在本教程中,我们将更详细地研究模型验证。 Views/Movies/Edit.cshtml
视图模板中的验证标记帮助程序负责显示相应的错误消息。
电影控制器中的所有 HttpGet
方法都遵循类似的模式。 它们获取电影对象(对于 Index
获取的是对象列表)并将对象(模型)传递给视图。 Create
方法将空的电影对象传递给 Create
视图。 在方法的 [HttpPost]
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 以 HTTP GET
方式修改数据是一种安全隐患。 以 HTTP GET
方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。
其他资源
电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。
打开 Models/Movie.cs
文件,并添加以下代码中突出显示的行:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
DataAnnotations
将在下一教程中说明。 Display 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。
要使 Entity Framework Core 能将 Price
正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")]
数据注释。 有关详细信息,请参阅数据类型。
浏览到 Movies
控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL。
“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml
文件中由 Core MVC 定位标记帮助程序生成的。
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 在上面的代码中,AnchorTagHelper
从控制器操作方法和路由 ID 动态生成 HTML href
特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记。 生成的 HTML 的一部分如下所示:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
重新调用在 Program.cs
文件中设置的路由的格式:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core 将 https://localhost:5001/Movies/Edit/4
转换为对 Movies
控制器的 Edit
操作方法的请求,参数 Id
为 4。 (控制器方法也称为操作方法。)
标记帮助程序是 ASP.NET Core 中的一项受欢迎的功能。 有关详细信息,请参阅其他资源。
打开 Movies
控制器并检查两个 Edit
操作方法。 以下代码显示了 HTTP GET Edit
方法,此方法将提取电影并填充由 Edit.cshtml
Razor 文件生成的编辑表单。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
以下代码显示 HTTP POST Edit
方法,它会处理已发布的电影值:
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[Bind]
特性是防止过度发布的一种方法。 只应在 [Bind]
特性中包含想要更改的属性。 有关详细信息,请参阅防止控制器过度发布。 ViewModels 提供了一种替代方法以防止过度发布。
请注意第二个 Edit
操作方法的前面是 [HttpPost]
特性。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
HttpPost
特性指定只能为 POST
请求调用此 Edit
方法。 可将 [HttpGet]
属性应用于第一个编辑方法,但不是必需,因为 [HttpGet]
是默认设置。
ValidateAntiForgeryToken
特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml
) 中生成的防伪令牌匹配。 编辑视图文件使用表单标记帮助程序生成防伪令牌。
<form asp-action="Edit">
表单标记帮助程序会生成隐藏的防伪令牌,此令牌必须与电影控制器的 Edit
方法中 [ValidateAntiForgeryToken]
生成的防伪令牌匹配。 有关详细信息,请参阅在 ASP.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击。
HttpGet Edit
方法采用电影 ID
参数,使用Entity Framework FindAsync
方法查找电影,并将所选电影返回到“编辑”视图。 如果无法找到电影,则返回 NotFound
(HTTP 404)。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie
语句。 @model MvcMovie.Models.Movie
指定视图期望的视图模板的模型为 Movie
类型。
基架的代码使用几个标记帮助程序方法来简化 HTML 标记。 标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。 输入标记帮助程序呈现 HTML <input>
元素。 验证标记帮助程序显示与该属性相关联的任何验证消息。
运行应用程序并导航到 /Movies
URL。 点击“编辑”链接。 在浏览器中查看页面的源。 为 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
<input>
元素位于 HTML <form>
元素中,后者的 action
特性设置为发布到 /Movies/Edit/id
URL。 当单击 Save
按钮时,表单数据将发布到服务器。 关闭 </form>
元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。
处理 POST 请求
以下列表显示了 Edit
操作方法的 [HttpPost]
版本。
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性验证表单标记帮助程序中的防伪令牌生成器生成的隐藏 XSRF 令牌
模型绑定系统采用发布的表单值,并创建一个作为 movie
参数传递的 Movie
对象。 ModelState.IsValid
属性验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync
方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController
类的 Index
操作方法,此方法显示电影集合,包括刚才所做的更改。
在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会发布表单。 如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。 稍后在本教程中,我们将更详细地研究模型验证。 Views/Movies/Edit.cshtml
视图模板中的验证标记帮助程序负责显示相应的错误消息。
电影控制器中的所有 HttpGet
方法都遵循类似的模式。 它们获取电影对象(对于 Index
获取的是对象列表)并将对象(模型)传递给视图。 Create
方法将空的电影对象传递给 Create
视图。 在方法的 [HttpPost]
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 以 HTTP GET
方式修改数据是一种安全隐患。 以 HTTP GET
方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。
其他资源
电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。
打开 Models/Movie.cs
文件,并添加以下代码中突出显示的行:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
下一教程将介绍 DataAnnotations。 Display 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。 DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。
要使 Entity Framework Core 能将 Price
正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")]
数据注释。 有关详细信息,请参阅数据类型。
浏览到 Movies
控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL。
“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml
文件中由 Core MVC 定位标记帮助程序生成的。
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 在上面的代码中,AnchorTagHelper
从控制器操作方法和路由 ID 动态生成 HTML href
特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记。 生成的 HTML 的一部分如下所示:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
重新调用在 Startup.cs
文件中设置的路由的格式:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core 将 https://localhost:5001/Movies/Edit/4
转换为对 Movies
控制器的 Edit
操作方法的请求,参数 Id
为 4。 (控制器方法也称为操作方法。)
打开 Movies
控制器并检查两个 Edit
操作方法。 以下代码显示了 HTTP GET Edit
方法,此方法将提取电影并填充由 Edit.cshtml
Razor 文件生成的编辑表单。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
以下代码显示 HTTP POST Edit
方法,它会处理已发布的电影值:
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
[Bind]
特性是防止过度发布的一种方法。 只应在 [Bind]
特性中包含想要更改的属性。 有关详细信息,请参阅防止控制器过度发布。 ViewModels 提供了一种替代方法以防止过度发布。
请注意第二个 Edit
操作方法的前面是 [HttpPost]
特性。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
HttpPost
特性指定只能为 POST
请求调用此 Edit
方法。 可将 [HttpGet]
属性应用于第一个编辑方法,但不是必需,因为 [HttpGet]
是默认设置。
ValidateAntiForgeryToken
特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml
) 中生成的防伪令牌匹配。 编辑视图文件使用表单标记帮助程序生成防伪令牌。
<form asp-action="Edit">
表单标记帮助程序会生成隐藏的防伪令牌,此令牌必须与电影控制器的 Edit
方法中 [ValidateAntiForgeryToken]
生成的防伪令牌匹配。 有关详细信息,请参阅在 ASP.NET Core 中预防跨网站请求伪造 (XSRF/CSRF) 攻击。
HttpGet Edit
方法采用电影 ID
参数,使用Entity Framework FindAsync
方法查找电影,并将所选电影返回到“编辑”视图。 如果无法找到电影,则返回 NotFound
(HTTP 404)。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
当基架系统创建“编辑”视图时,它会检查 Movie
类并创建代码为类的每个属性呈现 <label>
和 <input>
元素。 以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie
语句。 @model MvcMovie.Models.Movie
指定视图期望的视图模板的模型为 Movie
类型。
基架的代码使用几个标记帮助程序方法来简化 HTML 标记。 标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。 输入标记帮助程序呈现 HTML <input>
元素。 验证标记帮助程序显示与该属性相关联的任何验证消息。
运行应用程序并导航到 /Movies
URL。 点击“编辑”链接。 在浏览器中查看页面的源。 为 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
<input>
元素位于 HTML <form>
元素中,后者的 action
特性设置为发布到 /Movies/Edit/id
URL。 当单击 Save
按钮时,表单数据将发布到服务器。 关闭 </form>
元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。
处理 POST 请求
以下列表显示了 Edit
操作方法的 [HttpPost]
版本。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性验证表单标记帮助程序中的防伪令牌生成器生成的隐藏 XSRF 令牌
模型绑定系统采用发布的表单值,并创建一个作为 movie
参数传递的 Movie
对象。 ModelState.IsValid
属性验证表单中提交的数据是否可以用于修改(编辑或更新)Movie
对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync
方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController
类的 Index
操作方法,此方法显示电影集合,包括刚才所做的更改。
在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会发布表单。 如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。 稍后在本教程中,我们将更详细地研究模型验证。 Views/Movies/Edit.cshtml
视图模板中的验证标记帮助程序负责显示相应的错误消息。
电影控制器中的所有 HttpGet
方法都遵循类似的模式。 它们获取电影对象(对于 Index
获取的是对象列表)并将对象(模型)传递给视图。 Create
方法将空的电影对象传递给 Create
视图。 在方法的 [HttpPost]
重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。 以 HTTP GET
方式修改数据是一种安全隐患。 以 HTTP GET
方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。