在 ASP.NET Core 应用中更新生成的页面Update the generated pages in an ASP.NET Core app

本文内容

作者:Rick Anderson

构架的电影应用有个不错的开始,但是展示效果还不够理想。ReleaseDate 应是 Release Date (两个词)。

在 Chrome 中打开的电影应用程序

更新生成的代码Update the generated code

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

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.ComponentModel.DataAnnotations.Schema;
  4. namespace RazorPagesMovie.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. }

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

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

浏览到 Pages/Movies,并将鼠标悬停在“编辑”链接上以查看目标 URL 。

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

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

  1. @foreach (var item in Model.Movie) {
  2. <tr>
  3. <td>
  4. @Html.DisplayFor(modelItem => item.Title)
  5. </td>
  6. <td>
  7. @Html.DisplayFor(modelItem => item.ReleaseDate)
  8. </td>
  9. <td>
  10. @Html.DisplayFor(modelItem => item.Genre)
  11. </td>
  12. <td>
  13. @Html.DisplayFor(modelItem => item.Price)
  14. </td>
  15. <td>
  16. <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
  17. <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
  18. <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
  19. </td>
  20. </tr>
  21. }
  22. </tbody>
  23. </table>

标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。在前面的代码中,AnchorTagHelper 从 Razor 页面(路由是相对的)、asp-page 和路由 ID (asp-route-id) 动态生成 HTML href 特性值。有关详细信息,请参阅页面的 URL 生成

在最喜欢的浏览器中使用“查看源”来检查生成的标记 。生成的 HTML 的一部分如下所示:

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

动态生成的链接通过查询字符串传递电影 ID(例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1)。

添加路由模板Add route template

更新“编辑”、“详细信息”和“删除”Razor 页面以使用“{id:int?}”路由模板。将上述每个页面的页面指令从 @page 更改为 @page "{id:int}"运行应用,然后查看源。生成的 HTML 会将 ID 添加到 URL 的路径部分:

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

如果对具有“{id: int}” 路由模板的页面进行的请求中不包含整数,则将返回 HTTP 404(未找到)错误。 例如,http://localhost:5000/Movies/Details 将返回 404 错误。若要使 ID 可选,请将 ? 追加到路由约束:

  1. @page "{id:int?}"

若要测试 @page "{id:int?}" 的行为:

  • 在 Pages/Movies/Details.cshtml 中将 page 指令设置为 @page "{id:int?}"
  • public async Task<IActionResult> OnGetAsync(int? id) 中(位于 Pages/Movies/Details.cshtml.cs 中)设置断点。
  • 导航到 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指令时,永远不会命中断点。路由引擎返回 HTTP 404。使用 @page "{id:int?}" 时,OnGetAsync 方法返回 NotFound (HTTP 404)。

查看并发异常处理Review concurrency exception handling

查看 Pages/Movies/Edit.cshtml.cs 文件中的 OnPostAsync 方法 :

  1. public async Task<IActionResult> OnPostAsync()
  2. {
  3. if (!ModelState.IsValid)
  4. {
  5. return Page();
  6. }
  7. _context.Attach(Movie).State = EntityState.Modified;
  8. try
  9. {
  10. await _context.SaveChangesAsync();
  11. }
  12. catch (DbUpdateConcurrencyException)
  13. {
  14. if (!MovieExists(Movie.ID))
  15. {
  16. return NotFound();
  17. }
  18. else
  19. {
  20. throw;
  21. }
  22. }
  23. return RedirectToPage("./Index");
  24. }
  25. private bool MovieExists(int id)
  26. {
  27. return _context.Movie.Any(e => e.ID == id);
  28. }

当一个客户端删除电影并且另一个客户端对电影发布更改时,前面的代码会检测并发异常。

测试 catch 块:

  • catch (DbUpdateConcurrencyException)上设置断点
  • 对电影选择“编辑” ,进行更改,但不要输入“保存” 。
  • 在其他浏览器窗口中,选择同一电影的“删除”链接,然后删除此电影。
  • 在之前的浏览器窗口中,将更改发布到电影。

生产代码可能要检测并发冲突。有关详细信息,请参阅处理并发冲突

发布和绑定审阅Posting and binding review

检查 Pages/Movies/Edit.cshtml.cs 文件 :

  1. public class EditModel : PageModel
  2. {
  3. private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
  4. public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
  5. {
  6. _context = context;
  7. }
  8. [BindProperty]
  9. public Movie Movie { get; set; }
  10. public async Task<IActionResult> OnGetAsync(int? id)
  11. {
  12. if (id == null)
  13. {
  14. return NotFound();
  15. }
  16. Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
  17. if (Movie == null)
  18. {
  19. return NotFound();
  20. }
  21. return Page();
  22. }
  23. public async Task<IActionResult> OnPostAsync()
  24. {
  25. if (!ModelState.IsValid)
  26. {
  27. return Page();
  28. }
  29. _context.Attach(Movie).State = EntityState.Modified;
  30. try
  31. {
  32. await _context.SaveChangesAsync();
  33. }
  34. catch (DbUpdateConcurrencyException)
  35. {
  36. if (!MovieExists(Movie.ID))
  37. {
  38. return NotFound();
  39. }
  40. else
  41. {
  42. throw;
  43. }
  44. }
  45. return RedirectToPage("./Index");
  46. }
  47. private bool MovieExists(int id)
  48. {
  49. return _context.Movie.Any(e => e.ID == id);
  50. }

当对 Movies/Edit 页面进行 HTTP GET 请求时(例如 http://localhost:5000/Movies/Edit/2):

  • OnGetAsync 方法从数据库提取电影并返回 Page 方法。
  • Page 方法呈现“Pages/Movies/Edit.cshtml”Razor 页面。 Pages/Movies/Edit.cshtml 文件包含模型指令 (@model RazorPagesMovie.Pages.Movies.EditModel),这使电影模型在页面上可用。
  • “编辑”表单中会显示电影的值。

当发布 Movies/Edit 页面时:

  • 此页面上的表单值将绑定到 Movie 属性。[BindProperty] 特性会启用模型绑定
  1. [BindProperty]
  2. public Movie Movie { get; set; }
  • 如果模型状态中存在错误(例如,ReleaseDate 无法被转换为日期),则会使用已提交的值重新显示表单。

  • 如果没有模型错误,则电影已保存。

“索引”、“创建”和“删除”Razor 页面中的 HTTP GET 方法遵循一个类似的模式。“创建”Razor 页面中的 HTTP POST OnPostAsync 方法遵循的模式类似于“编辑”Razor 页面中的 OnPostAsync 方法所遵循的模式。

其他资源Additional resources

上一篇:使用数据库下一篇:添加搜索

构架的电影应用有个不错的开始,但是展示效果还不够理想。ReleaseDate 应是 Release Date (两个词)。

在 Chrome 中打开的电影应用程序

更新生成的代码Update the generated code

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

  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.ComponentModel.DataAnnotations.Schema;
  4. namespace RazorPagesMovie.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. }

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

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

浏览到 Pages/Movies,并将鼠标悬停在“编辑”链接上以查看目标 URL 。

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

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

  1. @foreach (var item in Model.Movie) {
  2. <tr>
  3. <td>
  4. @Html.DisplayFor(modelItem => item.Title)
  5. </td>
  6. <td>
  7. @Html.DisplayFor(modelItem => item.ReleaseDate)
  8. </td>
  9. <td>
  10. @Html.DisplayFor(modelItem => item.Genre)
  11. </td>
  12. <td>
  13. @Html.DisplayFor(modelItem => item.Price)
  14. </td>
  15. <td>
  16. <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
  17. <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
  18. <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
  19. </td>
  20. </tr>
  21. }
  22. </tbody>
  23. </table>

标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。在前面的代码中,AnchorTagHelper 从 Razor 页面(路由是相对的)、asp-page 和路由 ID (asp-route-id) 动态生成 HTML href 特性值。有关详细信息,请参阅页面的 URL 生成

在最喜欢的浏览器中使用“查看源”来检查生成的标记 。生成的 HTML 的一部分如下所示:

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

动态生成的链接通过查询字符串传递电影 ID(例如,https://localhost:5001/Movies/Details?id=1 中的 ?id=1)。

更新“编辑”、“详细信息”和“删除”Razor 页面以使用“{id:int?}”路由模板。将上述每个页面的页面指令从 @page 更改为 @page "{id:int}"运行应用,然后查看源。生成的 HTML 会将 ID 添加到 URL 的路径部分:

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

如果对具有“{id: int}” 路由模板的页面进行的请求中不包含整数,则将返回 HTTP 404(未找到)错误。 例如,http://localhost:5000/Movies/Details 将返回 404 错误。若要使 ID 可选,请将 ? 追加到路由约束:

  1. @page "{id:int?}"

若要测试 @page "{id:int?}" 的行为:

  • 在 Pages/Movies/Details.cshtml 中将 page 指令设置为 @page "{id:int?}"
  • public async Task<IActionResult> OnGetAsync(int? id) 中(位于 Pages/Movies/Details.cshtml.cs 中)设置断点。
  • 导航到 https://localhost:5001/Movies/Details/

使用 @page "{id:int}" 指令时,永远不会命中断点。路由引擎返回 HTTP 404。使用 @page "{id:int?}" 时,OnGetAsync 方法返回 NotFound (HTTP 404)。

查看并发异常处理Review concurrency exception handling

查看 Pages/Movies/Edit.cshtml.cs 文件中的 OnPostAsync 方法 :

  1. public async Task<IActionResult> OnPostAsync()
  2. {
  3. if (!ModelState.IsValid)
  4. {
  5. return Page();
  6. }
  7. _context.Attach(Movie).State = EntityState.Modified;
  8. try
  9. {
  10. await _context.SaveChangesAsync();
  11. }
  12. catch (DbUpdateConcurrencyException)
  13. {
  14. if (!MovieExists(Movie.ID))
  15. {
  16. return NotFound();
  17. }
  18. else
  19. {
  20. throw;
  21. }
  22. }
  23. return RedirectToPage("./Index");
  24. }
  25. private bool MovieExists(int id)
  26. {
  27. return _context.Movie.Any(e => e.ID == id);
  28. }

当一个客户端删除电影并且另一个客户端对电影发布更改时,前面的代码会检测并发异常。

测试 catch 块:

  • catch (DbUpdateConcurrencyException)上设置断点
  • 对电影选择“编辑” ,进行更改,但不要输入“保存” 。
  • 在其他浏览器窗口中,选择同一电影的“删除”链接,然后删除此电影。
  • 在之前的浏览器窗口中,将更改发布到电影。

生产代码可能要检测并发冲突。有关详细信息,请参阅处理并发冲突

发布和绑定审阅Posting and binding review

检查 Pages/Movies/Edit.cshtml.cs 文件 :

  1. public class EditModel : PageModel
  2. {
  3. private readonly RazorPagesMovieContext _context;
  4. public EditModel(RazorPagesMovieContext context)
  5. {
  6. _context = context;
  7. }
  8. [BindProperty]
  9. public Movie Movie { get; set; }
  10. public async Task<IActionResult> OnGetAsync(int? id)
  11. {
  12. if (id == null)
  13. {
  14. return NotFound();
  15. }
  16. Movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
  17. if (Movie == null)
  18. {
  19. return NotFound();
  20. }
  21. return Page();
  22. }
  23. public async Task<IActionResult> OnPostAsync()
  24. {
  25. if (!ModelState.IsValid)
  26. {
  27. return Page();
  28. }
  29. _context.Attach(Movie).State = EntityState.Modified;
  30. try
  31. {
  32. await _context.SaveChangesAsync();
  33. }
  34. catch (DbUpdateConcurrencyException)
  35. {
  36. if (!_context.Movie.Any(e => e.ID == Movie.ID))
  37. {
  38. return NotFound();
  39. }
  40. else
  41. {
  42. throw;
  43. }
  44. }
  45. return RedirectToPage("./Index");
  46. }
  47. }

当对 Movies/Edit 页面进行 HTTP GET 请求时(例如 http://localhost:5000/Movies/Edit/2):

  • OnGetAsync 方法从数据库提取电影并返回 Page 方法。
  • Page 方法呈现“Pages/Movies/Edit.cshtml”Razor 页面。 Pages/Movies/Edit.cshtml 文件包含模型指令 (@model RazorPagesMovie.Pages.Movies.EditModel),这使电影模型在页面上可用。
  • “编辑”表单中会显示电影的值。

当发布 Movies/Edit 页面时:

  • 此页面上的表单值将绑定到 Movie 属性。[BindProperty] 特性会启用模型绑定
  1. [BindProperty]
  2. public Movie Movie { get; set; }
  • 如果模型状态中存在错误(例如,ReleaseDate 无法被转换为日期),则会使用已提交的值显示表单。

  • 如果没有模型错误,则电影已保存。

“索引”、“创建”和“删除”Razor 页面中的 HTTP GET 方法遵循一个类似的模式。“创建”Razor 页面中的 HTTP POST OnPostAsync 方法遵循的模式类似于“编辑”Razor 页面中的 OnPostAsync 方法所遵循的模式。

在下一教程中将添加搜索。

其他资源Additional resources

上一篇:使用数据库下一篇:添加搜索