ASP.NET Core MVC / Razor Pages 教程 - 第二章

关于本教程

这是ASP.NET CoreMVC / Razor Pages系列教程的第二章. 共有三章:

你也可以观看由ABP社区成员为本教程录制的视频课程.

新增 Book 实体

通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示:

bookstore-create-dialog

新建 modal form

Acme.BookStore.Web 项目的 Pages/Books 目录下新建一个 CreateModal.cshtml Razor页面:

bookstore-add-create-dialog

CreateModal.cshtml.cs

打开 CreateModal.cshtml.cs 代码文件,用如下代码替换 CreateModalModel 类的实现:

  1. using System.Threading.Tasks;
  2. using Microsoft.AspNetCore.Mvc;
  3. namespace Acme.BookStore.Web.Pages.Books
  4. {
  5. public class CreateModalModel : BookStorePageModel
  6. {
  7. [BindProperty]
  8. public CreateUpdateBookDto Book { get; set; }
  9. private readonly IBookAppService _bookAppService;
  10. public CreateModalModel(IBookAppService bookAppService)
  11. {
  12. _bookAppService = bookAppService;
  13. }
  14. public async Task<IActionResult> OnPostAsync()
  15. {
  16. await _bookAppService.CreateAsync(Book);
  17. return NoContent();
  18. }
  19. }
  20. }
  • 该类派生于 BookStorePageModel 而非默认的 PageModel. BookStorePageModel 继承了 PageModel 并且添加了一些可以被你的page model类使用的通用属性和方法.
  • Book 属性上的 [BindProperty] 特性将post请求提交上来的数据绑定到该属性上.
  • 该类通过构造函数注入了 IBookAppService 应用服务,并且在 OnPostAsync 处理程序中调用了服务的 CreateAsync 方法.
CreateModal.cshtml

打开 CreateModal.cshtml 文件并粘贴如下代码:

  1. @page
  2. @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
  3. @model Acme.BookStore.Web.Pages.Books.CreateModalModel
  4. @{
  5. Layout = null;
  6. }
  7. <abp-dynamic-form abp-model="Book" data-ajaxForm="true" asp-page="/Books/CreateModal">
  8. <abp-modal>
  9. <abp-modal-header title="@L["NewBook"].Value"></abp-modal-header>
  10. <abp-modal-body>
  11. <abp-form-content />
  12. </abp-modal-body>
  13. <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
  14. </abp-modal>
  15. </abp-dynamic-form>
  • 这个 modal 使用 abp-dynamic-form Tag Helper 根据 CreateBookViewModel 类自动构建了表单.
    • abp-model 指定了 Book 属性为模型对象.
    • data-ajaxForm 设置了表单通过AJAX提交,而不是经典的页面回发.
    • abp-form-content tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 abp-dynamic-form 中像本示例这样添加了其他内容才需要).

添加 “New book” 按钮

打开 Pages/Books/Index.cshtml 并按如下代码修改 abp-card-header :

  1. <abp-card-header>
  2. <abp-row>
  3. <abp-column size-md="_6">
  4. <h2>@L["Books"]</h2>
  5. </abp-column>
  6. <abp-column size-md="_6" class="text-right">
  7. <abp-button id="NewBookButton"
  8. text="@L["NewBook"].Value"
  9. icon="plus"
  10. button-type="Primary" />
  11. </abp-column>
  12. </abp-row>
  13. </abp-card-header>

如下图所示,只是在表格 右上方 添加了 New book 按钮:

bookstore-new-book-button

打开 Pages/book/index.jsdatatable 配置代码后面添加如下代码:

  1. var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
  2. createModal.onResult(function () {
  3. dataTable.ajax.reload();
  4. });
  5. $('#NewBookButton').click(function (e) {
  6. e.preventDefault();
  7. createModal.open();
  8. });
  • abp.ModalManager 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节.

现在,你可以 运行程序 通过新的 modal form 来创建书籍了.

编辑更新已存在的 Book 实体

Acme.BookStore.Web 项目的 Pages/Books 目录下新建一个名叫 EditModal.cshtml 的Razor页面:

bookstore-add-edit-dialog

EditModal.cshtml.cs

展开 EditModal.cshtml,打开 EditModal.cshtml.cs 文件( EditModalModel 类) 并替换成以下代码:

  1. using System;
  2. using System.Threading.Tasks;
  3. using Microsoft.AspNetCore.Mvc;
  4. namespace Acme.BookStore.Web.Pages.Books
  5. {
  6. public class EditModalModel : BookStorePageModel
  7. {
  8. [HiddenInput]
  9. [BindProperty(SupportsGet = true)]
  10. public Guid Id { get; set; }
  11. [BindProperty]
  12. public CreateUpdateBookDto Book { get; set; }
  13. private readonly IBookAppService _bookAppService;
  14. public EditModalModel(IBookAppService bookAppService)
  15. {
  16. _bookAppService = bookAppService;
  17. }
  18. public async Task OnGetAsync()
  19. {
  20. var bookDto = await _bookAppService.GetAsync(Id);
  21. Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);
  22. }
  23. public async Task<IActionResult> OnPostAsync()
  24. {
  25. await _bookAppService.UpdateAsync(Id, Book);
  26. return NoContent();
  27. }
  28. }
  29. }
  • [HiddenInput][BindProperty] 是标准的 ASP.NET Core MVC 特性.这里启用 SupportsGet 从Http请求的查询字符串中获取Id的值.
  • OnGetAsync 方法中,将 BookAppService.GetAsync 方法返回的 BookDto 映射成 CreateUpdateBookDto 并赋值给Book属性.
  • OnPostAsync 方法直接使用 BookAppService.UpdateAsync 来更新实体.

BookDto到CreateUpdateBookDto对象映射

为了执行BookDtoCreateUpdateBookDto对象映射,请打开Acme.BookStore.Web项目中的BookStoreWebAutoMapperProfile.cs并更改它,如下所示:

  1. using AutoMapper;
  2. namespace Acme.BookStore.Web
  3. {
  4. public class BookStoreWebAutoMapperProfile : Profile
  5. {
  6. public BookStoreWebAutoMapperProfile()
  7. {
  8. CreateMap<BookDto, CreateUpdateBookDto>();
  9. }
  10. }
  11. }
  • 刚刚添加了CreateMap<BookDto, CreateUpdateBookDto>();作为映射定义.

EditModal.cshtml

EditModal.cshtml 页面内容替换成如下代码:

  1. @page
  2. @using Acme.BookStore.Web.Pages.Books
  3. @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
  4. @model EditModalModel
  5. @{
  6. Layout = null;
  7. }
  8. <abp-dynamic-form abp-model="Book" data-ajaxForm="true" asp-page="/Books/EditModal">
  9. <abp-modal>
  10. <abp-modal-header title="@L["Update"].Value"></abp-modal-header>
  11. <abp-modal-body>
  12. <abp-input asp-for="Id" />
  13. <abp-form-content />
  14. </abp-modal-body>
  15. <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
  16. </abp-modal>
  17. </abp-dynamic-form>

这个页面内容和 CreateModal.cshtml 非常相似,除了以下几点:

  • 它包含id属性的abp-input, 用于存储编辑书的 id (它是隐藏的Input)
  • 此页面指定的post地址是Books/EditModal, 并用文本 Update 作为 modal 标题.

为表格添加 “操作(Actions)” 下拉菜单

我们将为表格每行添加下拉按钮 (“Actions”) . 最终效果如下:

bookstore-book-table-actions

打开 Pages/Books/Index.cshtml 页面,并按下方所示修改表格部分的代码:

  1. <abp-table striped-rows="true" id="BooksTable">
  2. <thead>
  3. <tr>
  4. <th>@L["Actions"]</th>
  5. <th>@L["Name"]</th>
  6. <th>@L["Type"]</th>
  7. <th>@L["PublishDate"]</th>
  8. <th>@L["Price"]</th>
  9. <th>@L["CreationTime"]</th>
  10. </tr>
  11. </thead>
  12. </abp-table>
  • 只是为”Actions”增加了一个 th 标签.

打开 Pages/book/index.js 并用以下内容进行替换:

  1. $(function () {
  2. var l = abp.localization.getResource('BookStore');
  3. var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
  4. var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
  5. var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({
  6. processing: true,
  7. serverSide: true,
  8. paging: true,
  9. searching: false,
  10. autoWidth: false,
  11. scrollCollapse: true,
  12. order: [[1, "asc"]],
  13. ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
  14. columnDefs: [
  15. {
  16. rowAction: {
  17. items:
  18. [
  19. {
  20. text: l('Edit'),
  21. action: function (data) {
  22. editModal.open({ id: data.record.id });
  23. }
  24. }
  25. ]
  26. }
  27. },
  28. { data: "name" },
  29. { data: "type" },
  30. { data: "publishDate" },
  31. { data: "price" },
  32. { data: "creationTime" }
  33. ]
  34. }));
  35. createModal.onResult(function () {
  36. dataTable.ajax.reload();
  37. });
  38. editModal.onResult(function () {
  39. dataTable.ajax.reload();
  40. });
  41. $('#NewBookButton').click(function (e) {
  42. e.preventDefault();
  43. createModal.open();
  44. });
  45. });
  • 通过 abp.localization.getResource('BookStore') 可以在客户端使用服务器端定义的相同的本地化语言文本.
  • 添加了一个名为 createModal 的新的 ModalManager 来打开创建用的 modal 对话框.
  • 添加了一个名为 editModal 的新的 ModalManager 来打开编辑用的 modal 对话框.
  • columnDefs 起始处新增一列用于显示 “Actions” 下拉按钮.
  • “New Book”动作只需调用createModal.open来打开创建对话框.
  • “Edit” 操作只是简单调用 editModal.open 来打开编辑对话框.

现在,你可以运行程序,通过编辑操作来更新任一个book实体.

删除一个已有的Book实体

打开 Pages/book/index.js 文件,在 rowAction items 下新增一项:

  1. {
  2. text: l('Delete'),
  3. confirmMessage: function (data) {
  4. return l('BookDeletionConfirmationMessage', data.record.name);
  5. },
  6. action: function (data) {
  7. acme.bookStore.book
  8. .delete(data.record.id)
  9. .then(function() {
  10. abp.notify.info(l('SuccessfullyDeleted'));
  11. dataTable.ajax.reload();
  12. });
  13. }
  14. }
  • confirmMessage 用来在实际执行 action 之前向用户进行确认.
  • 通过javascript代理方法 acme.bookStore.book.delete 执行一个AJAX请求来删除一个book实体.
  • abp.notify.info 用来在执行删除操作后显示一个toastr通知信息.

最终的 index.js 文件内容如下所示:

  1. $(function () {
  2. var l = abp.localization.getResource('BookStore');
  3. var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
  4. var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');
  5. var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({
  6. processing: true,
  7. serverSide: true,
  8. paging: true,
  9. searching: false,
  10. autoWidth: false,
  11. scrollCollapse: true,
  12. order: [[1, "asc"]],
  13. ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList),
  14. columnDefs: [
  15. {
  16. rowAction: {
  17. items:
  18. [
  19. {
  20. text: l('Edit'),
  21. action: function (data) {
  22. editModal.open({ id: data.record.id });
  23. }
  24. },
  25. {
  26. text: l('Delete'),
  27. confirmMessage: function (data) {
  28. return l('BookDeletionConfirmationMessage', data.record.name);
  29. },
  30. action: function (data) {
  31. acme.bookStore.book
  32. .delete(data.record.id)
  33. .then(function() {
  34. abp.notify.info(l('SuccessfullyDeleted'));
  35. dataTable.ajax.reload();
  36. });
  37. }
  38. }
  39. ]
  40. }
  41. },
  42. { data: "name" },
  43. { data: "type" },
  44. { data: "publishDate" },
  45. { data: "price" },
  46. { data: "creationTime" }
  47. ]
  48. }));
  49. createModal.onResult(function () {
  50. dataTable.ajax.reload();
  51. });
  52. editModal.onResult(function () {
  53. dataTable.ajax.reload();
  54. });
  55. $('#NewBookButton').click(function (e) {
  56. e.preventDefault();
  57. createModal.open();
  58. });
  59. });

打开Acme.BookStore.Domain.Shared项目中的en.json并添加以下行:

  1. "BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?",
  2. "SuccessfullyDeleted": "Successfully deleted"

运行程序并尝试删除一个book实体.

下一章

查看本教程的 下一章 .