ASP.NET Core 中的控制器方法和视图Controller methods and views in ASP.NET Core

本文内容

作者:Rick Anderson

电影应用的开头不错,但展示效果不理想,例如,ReleaseDate 应为两个词。

索引视图:Release Date 为一个词(没有空格),每个电影发行日期都显示时间中午 12 点

打开 Models/Movie.cs 文件,并添加以下代码中突出显示的行 :

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.ComponentModel.DataAnnotations.Schema;
  4. namespace MvcMovie.Models
  5. {
  6. public class Movie
  7. {
  8. public int Id { get; set; }
  9. public string Title { get; set; }
  10. [Display(Name = "Release Date")]
  11. [DataType(DataType.Date)]
  12. public DateTime ReleaseDate { get; set; }
  13. public string Genre { get; set; }
  14. [Column(TypeName = "decimal(18, 2)")]
  15. public decimal Price { get; set; }
  16. }
  17. }

下一教程将介绍 DataAnnotationsDisplay 特性指定要显示的字段名称的内容(本例中应为“Release Date”,而不是“ReleaseDate”)。DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。

要使 Entity Framework Core 能将 Price 正确地映射到数据库中的货币,则必须使用 [Column(TypeName = "decimal(18, 2)")] 数据注释。有关详细信息,请参阅数据类型

浏览到 Movies 控制器,并将鼠标指针悬停在“编辑”链接上以查看目标 URL 。

鼠标悬停在“编辑”链接上的浏览器窗口,显示了 https://localhost:5001/Movies/Edit/5 的链接 URL

“编辑”、“详细信息”和“删除”链接是在 Views/Movies/Index.cshtml 文件中由 Core MVC 定位标记帮助程序生成的 。

  1. <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
  2. <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
  3. <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
  4. </td>
  5. </tr>

标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。在上面的代码中,AnchorTagHelper 从控制器操作方法和路由 ID 动态生成 HTML href 特性值。在最喜欢的浏览器中使用“查看源”,或使用开发人员工具来检查生成的标记 。生成的 HTML 的一部分如下所示:

  1. <td>
  2. <a href="/Movies/Edit/4"> Edit </a> |
  3. <a href="/Movies/Details/4"> Details </a> |
  4. <a href="/Movies/Delete/4"> Delete </a>
  5. </td>

重新调用在 Startup.cs 文件中设置的路由的格式 :

  1. app.UseEndpoints(endpoints =>
  2. {
  3. endpoints.MapControllerRoute(
  4. name: "default",
  5. pattern: "{controller=Home}/{action=Index}/{id?}");
  6. });

ASP.NET Core 将 https://localhost:5001/Movies/Edit/4 转换为对 Movies 控制器的 Edit 操作方法的请求,参数 Id 为 4。(控制器方法也称为操作方法。)

标记帮助程序是 ASP.NET Core 中最受欢迎的新功能之一。有关详细信息,请参阅其他资源

打开 Movies 控制器并检查两个 Edit 操作方法。以下代码显示了 HTTP GET Edit 方法,此方法将提取电影并填充由 Edit.cshtml Razor 文件生成的编辑表单 。

  1. // GET: Movies/Edit/5
  2. public async Task<IActionResult> Edit(int? id)
  3. {
  4. if (id == null)
  5. {
  6. return NotFound();
  7. }
  8. var movie = await _context.Movie.FindAsync(id);
  9. if (movie == null)
  10. {
  11. return NotFound();
  12. }
  13. return View(movie);
  14. }

以下代码显示 HTTP POST Edit 方法,它会处理已发布的电影值:

  1. // POST: Movies/Edit/5
  2. // To protect from overposting attacks, please enable the specific properties you want to bind to, for
  3. // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
  4. [HttpPost]
  5. [ValidateAntiForgeryToken]
  6. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  7. {
  8. if (id != movie.ID)
  9. {
  10. return NotFound();
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. try
  15. {
  16. _context.Update(movie);
  17. await _context.SaveChangesAsync();
  18. }
  19. catch (DbUpdateConcurrencyException)
  20. {
  21. if (!MovieExists(movie.ID))
  22. {
  23. return NotFound();
  24. }
  25. else
  26. {
  27. throw;
  28. }
  29. }
  30. return RedirectToAction("Index");
  31. }
  32. return View(movie);
  33. }
  1. // GET: Movies/Edit/5
  2. public async Task<IActionResult> Edit(int? id)
  3. {
  4. if (id == null)
  5. {
  6. return NotFound();
  7. }
  8. var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
  9. if (movie == null)
  10. {
  11. return NotFound();
  12. }
  13. return View(movie);
  14. }

以下代码显示 HTTP POST Edit 方法,它会处理已发布的电影值:

  1. // POST: Movies/Edit/5
  2. // To protect from overposting attacks, please enable the specific properties you want to bind to, for
  3. // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
  4. [HttpPost]
  5. [ValidateAntiForgeryToken]
  6. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  7. {
  8. if (id != movie.ID)
  9. {
  10. return NotFound();
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. try
  15. {
  16. _context.Update(movie);
  17. await _context.SaveChangesAsync();
  18. }
  19. catch (DbUpdateConcurrencyException)
  20. {
  21. if (!MovieExists(movie.ID))
  22. {
  23. return NotFound();
  24. }
  25. else
  26. {
  27. throw;
  28. }
  29. }
  30. return RedirectToAction("Index");
  31. }
  32. return View(movie);
  33. }

[Bind] 特性是防止过度发布的一种方法。只应在 [Bind] 特性中包含想要更改的属性。有关详细信息,请参阅防止控制器过度发布ViewModels 提供了一种替代方法以防止过度发布。

请注意第二个 Edit 操作方法的前面是 [HttpPost] 特性。

  1. [HttpPost]
  2. [ValidateAntiForgeryToken]
  3. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  4. {
  5. if (id != movie.ID)
  6. {
  7. return NotFound();
  8. }
  9. if (ModelState.IsValid)
  10. {
  11. try
  12. {
  13. _context.Update(movie);
  14. await _context.SaveChangesAsync();
  15. }
  16. catch (DbUpdateConcurrencyException)
  17. {
  18. if (!MovieExists(movie.ID))
  19. {
  20. return NotFound();
  21. }
  22. else
  23. {
  24. throw;
  25. }
  26. }
  27. return RedirectToAction(nameof(Index));
  28. }
  29. return View(movie);
  30. }
  1. // POST: Movies/Edit/5
  2. // To protect from overposting attacks, please enable the specific properties you want to bind to, for
  3. // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
  4. [HttpPost]
  5. [ValidateAntiForgeryToken]
  6. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  7. {
  8. if (id != movie.ID)
  9. {
  10. return NotFound();
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. try
  15. {
  16. _context.Update(movie);
  17. await _context.SaveChangesAsync();
  18. }
  19. catch (DbUpdateConcurrencyException)
  20. {
  21. if (!MovieExists(movie.ID))
  22. {
  23. return NotFound();
  24. }
  25. else
  26. {
  27. throw;
  28. }
  29. }
  30. return RedirectToAction("Index");
  31. }
  32. return View(movie);
  33. }

HttpPost 特性指定只能为 POST 请求调用此 Edit 方法 。可将 [HttpGet] 属性应用于第一个编辑方法,但不是必需,因为 [HttpGet] 是默认设置。

ValidateAntiForgeryToken 特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml) 中生成的防伪标记相配对 。编辑视图文件使用表单标记帮助程序生成防伪标记。

  1. <form asp-action="Edit">

表单标记帮助程序会生成隐藏的防伪标记,此标记必须与电影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 生成的防伪标记相匹配。有关详细信息,请参阅反请求伪造

HttpGet Edit 方法采用电影 ID 参数,使用Entity Framework FindAsync 方法查找电影,并将所选电影返回到“编辑”视图。如果无法找到电影,则返回 NotFound (HTTP 404)。

  1. // GET: Movies/Edit/5
  2. public async Task<IActionResult> Edit(int? id)
  3. {
  4. if (id == null)
  5. {
  6. return NotFound();
  7. }
  8. var movie = await _context.Movie.FindAsync(id);
  9. if (movie == null)
  10. {
  11. return NotFound();
  12. }
  13. return View(movie);
  14. }

当基架系统创建“编辑”视图时,它会检查 Movie 类并创建代码为类的每个属性呈现 <label><input> 元素。以下示例显示由 Visual Studio 基架系统生成的“编辑”视图:

  1. @model MvcMovie.Models.Movie
  2. @{
  3. ViewData["Title"] = "Edit";
  4. }
  5. <h1>Edit</h1>
  6. <h4>Movie</h4>
  7. <hr />
  8. <div class="row">
  9. <div class="col-md-4">
  10. <form asp-action="Edit">
  11. <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  12. <input type="hidden" asp-for="Id" />
  13. <div class="form-group">
  14. <label asp-for="Title" class="control-label"></label>
  15. <input asp-for="Title" class="form-control" />
  16. <span asp-validation-for="Title" class="text-danger"></span>
  17. </div>
  18. <div class="form-group">
  19. <label asp-for="ReleaseDate" class="control-label"></label>
  20. <input asp-for="ReleaseDate" class="form-control" />
  21. <span asp-validation-for="ReleaseDate" class="text-danger"></span>
  22. </div>
  23. <div class="form-group">
  24. <label asp-for="Genre" class="control-label"></label>
  25. <input asp-for="Genre" class="form-control" />
  26. <span asp-validation-for="Genre" class="text-danger"></span>
  27. </div>
  28. <div class="form-group">
  29. <label asp-for="Price" class="control-label"></label>
  30. <input asp-for="Price" class="form-control" />
  31. <span asp-validation-for="Price" class="text-danger"></span>
  32. </div>
  33. <div class="form-group">
  34. <input type="submit" value="Save" class="btn btn-primary" />
  35. </div>
  36. </form>
  37. </div>
  38. </div>
  39. <div>
  40. <a asp-action="Index">Back to List</a>
  41. </div>
  42. @section Scripts {
  43. @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
  44. }

请注意视图模板在文件顶端有一个 @model MvcMovie.Models.Movie 语句。@model MvcMovie.Models.Movie 指定视图期望的视图模板的模型为 Movie 类型。

基架的代码使用几个标记帮助程序方法来简化 HTML 标记。标签标记帮助程序显示字段的名称(“Title”、“ReleaseDate”、“Genre”或“Price”)。输入标记帮助程序呈现 HTML <input> 元素。验证标记帮助程序显示与该属性相关联的任何验证消息。

运行应用程序并导航到 /Movies URL。点击“编辑”链接 。在浏览器中查看页面的源。<form> 元素生成的 HTML 如下所示。

  1. <form action="/Movies/Edit/7" method="post">
  2. <div class="form-horizontal">
  3. <h4>Movie</h4>
  4. <hr />
  5. <div class="text-danger" />
  6. <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
  7. <div class="form-group">
  8. <label class="control-label col-md-2" for="Genre" />
  9. <div class="col-md-10">
  10. <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
  11. <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
  12. </div>
  13. </div>
  14. <div class="form-group">
  15. <label class="control-label col-md-2" for="Price" />
  16. <div class="col-md-10">
  17. <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" />
  18. <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
  19. </div>
  20. </div>
  21. <!-- Markup removed for brevity -->
  22. <div class="form-group">
  23. <div class="col-md-offset-2 col-md-10">
  24. <input type="submit" value="Save" class="btn btn-default" />
  25. </div>
  26. </div>
  27. </div>
  28. <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
  29. </form>

<input> 元素位于 HTML <form> 元素中,后者的 action 特性设置为发布到 /Movies/Edit/id URL。当单击 Save 按钮时,表单数据将发布到服务器。关闭 </form> 元素之前的最后一行显示表单标记帮助程序生成的隐藏的 XSRF 标记。

处理 POST 请求Processing the POST Request

以下列表显示了 Edit 操作方法的 [HttpPost] 版本。

  1. [HttpPost]
  2. [ValidateAntiForgeryToken]
  3. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  4. {
  5. if (id != movie.ID)
  6. {
  7. return NotFound();
  8. }
  9. if (ModelState.IsValid)
  10. {
  11. try
  12. {
  13. _context.Update(movie);
  14. await _context.SaveChangesAsync();
  15. }
  16. catch (DbUpdateConcurrencyException)
  17. {
  18. if (!MovieExists(movie.ID))
  19. {
  20. return NotFound();
  21. }
  22. else
  23. {
  24. throw;
  25. }
  26. }
  27. return RedirectToAction(nameof(Index));
  28. }
  29. return View(movie);
  30. }
  1. // POST: Movies/Edit/5
  2. // To protect from overposting attacks, please enable the specific properties you want to bind to, for
  3. // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
  4. [HttpPost]
  5. [ValidateAntiForgeryToken]
  6. public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
  7. {
  8. if (id != movie.ID)
  9. {
  10. return NotFound();
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. try
  15. {
  16. _context.Update(movie);
  17. await _context.SaveChangesAsync();
  18. }
  19. catch (DbUpdateConcurrencyException)
  20. {
  21. if (!MovieExists(movie.ID))
  22. {
  23. return NotFound();
  24. }
  25. else
  26. {
  27. throw;
  28. }
  29. }
  30. return RedirectToAction("Index");
  31. }
  32. return View(movie);
  33. }

[ValidateAntiForgeryToken] 特性验证表单标记帮助程序中的防伪标记生成器生成的隐藏的 XSRF 标记

模型绑定系统采用发布的表单值,并创建一个作为 movie 参数传递的 Movie 对象。ModelState.IsValid 方法验证表单中提交的数据是否可以用于修改(编辑或更新)Movie 对象。如果数据有效,将保存此数据。通过调用数据库上下文的 SaveChangesAsync 方法,将更新(编辑)的电影数据保存到数据库。保存数据后,代码将用户重定向到 MoviesController 类的 Index 操作方法,此方法显示电影集合,包括刚才所做的更改。

在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。如果有任何验证错误,则将显示错误消息,并且不会发布表单。如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。稍后在本教程中,我们将更详细地研究模型验证Views/Movies/Edit.cshtml 视图模板中的验证标记帮助程序负责显示相应的错误消息 。

“编辑”视图:不正确的“价格”值 abc 的异常,说明“价格”字段必须是一个数字。

电影控制器中的所有 HttpGet 方法都遵循类似的模式。它们获取电影对象(对于 Index获取的是对象列表)并将对象(模型)传递给视图。Create 方法将空的电影对象传递给 Create 视图。在方法的 [HttpPost] 重载中,创建、编辑、删除或以其他方式修改数据的所有方法都执行此操作。HTTP GET 方式修改数据是一种安全隐患。HTTP GET 方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。

其他资源Additional resources

上一页下一页