创建,更新和删除图书
创建新书籍
创建 modal form
在 Acme.BookStore.Web
项目的 Pages/Books
目录下新建一个 CreateModal.cshtml
Razor页面:
CreateModal.cshtml.cs
打开 CreateModal.cshtml.cs
代码文件(CreateModalModel
类),替换成以下代码:
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;namespace Acme.BookStore.Web.Pages.Books
{public class CreateModalModel : BookStorePageModel{[BindProperty]public CreateUpdateBookDto Book { get; set; }private readonly IBookAppService _bookAppService;public CreateModalModel(IBookAppService bookAppService){_bookAppService = bookAppService;}public void OnGet(){Book = new CreateUpdateBookDto();}public async Task<IActionResult> OnPostAsync(){await _bookAppService.CreateAsync(Book);return NoContent();}}
}
- 该类派生于
BookStorePageModel
而非默认的PageModel
.BookStorePageModel
间接继承了PageModel
并且添加了一些可以被你的page model类使用的通用属性和方法. Book
属性上的[BindProperty]
特性将post请求提交上来的数据绑定到该属性上.- 该类通过构造函数注入了
IBookAppService
应用服务,并且在OnPostAsync
处理程序中调用了服务的CreateAsync
方法. - 它在
OnGet
方法中创建一个新的CreateUpdateBookDto
对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中赋值一些默认值或执行一些代码,它们将无法工作. 对于这种情况,我们为某些CreateUpdateBookDto
属性设置了默认值.
CreateModal.cshtml
打开 CreateModal.cshtml
文件并粘贴如下代码:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model CreateModalModel
@inject IStringLocalizer<BookStoreResource> L
@{Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/CreateModal"><abp-modal><abp-modal-header title="@L["NewBook"].Value"></abp-modal-header><abp-modal-body><abp-form-content /></abp-modal-body><abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer></abp-modal>
</abp-dynamic-form>
- 这个 modal 使用
abp-dynamic-form
tag Helper 根据CreateUpdateBookDto
类自动构建了表单. abp-model
指定了Book
属性为模型对象.abp-form-content
tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在abp-dynamic-form
中像本示例这样添加了其他内容才需要).
添加 "New book" 按钮
打开 Pages/Books/Index.cshtml
并按如下代码修改 abp-card-header
:
<abp-card-header><abp-row><abp-column size-md="_6"><abp-card-title>@L["Books"]</abp-card-title></abp-column><abp-column size-md="_6" class="text-end"><abp-button id="NewBookButton"text="@L["NewBook"].Value"icon="plus"button-type="Primary"/></abp-column></abp-row>
</abp-card-header>
Index.cshtml
的内容最终如下所示:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@model IndexModel
@inject IStringLocalizer<BookStoreResource> L
@section scripts
{<abp-script src="/Pages/Books/Index.js"/>
}<abp-card><abp-card-header><abp-row><abp-column size-md="_6"><abp-card-title>@L["Books"]</abp-card-title></abp-column><abp-column size-md="_6" class="text-end"><abp-button id="NewBookButton"text="@L["NewBook"].Value"icon="plus"button-type="Primary"/></abp-column></abp-row></abp-card-header><abp-card-body><abp-table striped-rows="true" id="BooksTable"></abp-table></abp-card-body>
</abp-card>
在图书页面加入了 一个添加按钮
打开 Pages/Book/Index.js
在 datatable
配置代码后面添加如下代码:
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');createModal.onResult(function () {dataTable.ajax.reload();
});$('#NewBookButton').click(function (e) {e.preventDefault();createModal.open();
});
abp.ModalManager
是一个在客户端管理modal的辅助类.它内部使用了Twitter Bootstrap的标准modal组件,但通过简化的API抽象了许多细节.createModal.onResult(...)
用于在创建书籍后刷新数据表格.createModal.open();
用于打开modal创建新书籍.
Index.js
的内容最终如下所示:
$(function () {var l = abp.localization.getResource('BookStore');var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({serverSide: true,paging: true,order: [[1, "asc"]],searching: false,scrollX: true,ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),columnDefs: [{title: l('Name'),data: "name"},{title: l('Type'),data: "type",render: function (data) {return l('Enum:BookType:' + data);}},{title: l('PublishDate'),data: "publishDate",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString();}},{title: l('Price'),data: "price"},{title: l('CreationTime'), data: "creationTime",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString(luxon.DateTime.DATETIME_SHORT);}}]}));var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');createModal.onResult(function () {dataTable.ajax.reload();});$('#NewBookButton').click(function (e) {e.preventDefault();createModal.open();});
});
现在,你可以 运行程序 通过新的 modal form 来创建书籍了.
更新书籍
在 Acme.BookStore.Web
项目的 Pages/Books
目录下新建一个名叫 EditModal.cshtml
的Razor页面:
EditModal.cshtml.cs
打开 EditModal.cshtml.cs
文件(EditModalModel
类) 并替换成以下代码:
using System;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Microsoft.AspNetCore.Mvc;namespace Acme.BookStore.Web.Pages.Books
{public class EditModalModel : BookStorePageModel{[HiddenInput][BindProperty(SupportsGet = true)]public Guid Id { get; set; }[BindProperty]public CreateUpdateBookDto Book { get; set; }private readonly IBookAppService _bookAppService;public EditModalModel(IBookAppService bookAppService){_bookAppService = bookAppService;}public async Task OnGetAsync(){var bookDto = await _bookAppService.GetAsync(Id);Book = ObjectMapper.Map<BookDto, CreateUpdateBookDto>(bookDto);}public async Task<IActionResult> OnPostAsync(){await _bookAppService.UpdateAsync(Id, Book);return NoContent();}}
}
[HiddenInput]
和[BindProperty]
是标准的 ASP.NET Core MVC 特性.这里启用SupportsGet
从Http请求的查询字符串参数中获取Id的值.- 在
OnGetAsync
方法中, 我们从BookAppService
获得BookDto
,并将它映射成DTO对象CreateUpdateBookDto
. OnPostAsync
方法直接使用BookAppService.UpdateAsync
来更新实体.
BookDto 到 CreateUpdateBookDto 对象映射
为了执行 BookDto
到 CreateUpdateBookDto
对象映射,请打开 Acme.BookStore.Web
项目中的 BookStoreWebAutoMapperProfile.cs
并更改它,如下所示:
using Acme.BookStore.Books;
using AutoMapper;namespace Acme.BookStore.Web;public class BookStoreWebAutoMapperProfile : Profile
{public BookStoreWebAutoMapperProfile(){//Define your AutoMapper configuration here for the Web project.CreateMap<BookDto, CreateUpdateBookDto>();}
}
我们添加了 CreateMap<BookDto, CreateUpdateBookDto>();
作为映射定义.
请注意,我们在Web层中进行映射定义是一种最佳实践,因为仅在该层中需要它.
EditModal.cshtml
将 EditModal.cshtml
页面内容替换成如下代码:
@page
@using Acme.BookStore.Localization
@using Acme.BookStore.Web.Pages.Books
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@model EditModalModel
@inject IStringLocalizer<BookStoreResource> L
@{Layout = null;
}
<abp-dynamic-form abp-model="Book" asp-page="/Books/EditModal"><abp-modal><abp-modal-header title="@L["Update"].Value"></abp-modal-header><abp-modal-body><abp-input asp-for="Id" /><abp-form-content /></abp-modal-body><abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer></abp-modal>
</abp-dynamic-form>
这个页面内容和 CreateModal.cshtml
非常相似,除了以下几点:
- 它包含
id
属性的abp-input
, 用于存储被编辑书籍的id
(它是隐藏的Input) - 此页面指定的post地址是
Books/EditModal
.
为表格添加 "操作(Actions)" 下拉菜单
我们将为表格每行添加下拉按钮 ("Actions"):
打开 Pages/Books/Index.js
页面,并按下方所示修改表格部分的代码:
$(function () {var l = abp.localization.getResource('BookStore');var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({serverSide: true,paging: true,order: [[1, "asc"]],searching: false,scrollX: true,ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),columnDefs: [{title: l('Actions'),rowAction: {items:[{text: l('Edit'),action: function (data) {editModal.open({ id: data.record.id });}}]}},{title: l('Name'),data: "name"},{title: l('Type'),data: "type",render: function (data) {return l('Enum:BookType:' + data);}},{title: l('PublishDate'),data: "publishDate",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString();}},{title: l('Price'),data: "price"},{title: l('CreationTime'), data: "creationTime",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString(luxon.DateTime.DATETIME_SHORT);}}]}));createModal.onResult(function () {dataTable.ajax.reload();});editModal.onResult(function () {dataTable.ajax.reload();});$('#NewBookButton').click(function (e) {e.preventDefault();createModal.open();});
});
- 增加了一个新的
ModalManager
名为editModal
打开编辑模态框. - 在
columnDefs
部分的开头添加了一个新列,用于"Actions"下拉按钮. - "Edit" 动作简单地调用
editModal.open()
打开编辑模态框. editModal.onResult(...)
当你关闭编程模态框时进行回调刷新数据表格.
效果图如上图所示
删除书籍
打开 Pages/book/index.js
文件,在 rowAction
items
下新增一项:
{text: l('Delete'),confirmMessage: function (data) {return l('BookDeletionConfirmationMessage', data.record.name);},action: function (data) {acme.bookStore.books.book.delete(data.record.id).then(function() {abp.notify.info(l('SuccessfullyDeleted'));dataTable.ajax.reload();});}
}
confirmMessage
执行action
前向用户进行确认.acme.bookStore.books.book.delete(...)
执行一个AJAX请求删除一个book.abp.notify.info
执行删除操作后显示一个通知信息.
由于我们使用了两个新的本地化文本(BookDeletionConfirmationMessage
和SuccesslyDeleted
),因此你需要将它们添加到本地化文件(Acme.BookStore.Domain.Shared
项目的Localization/BookStore
文件夹下的en.json
):
"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?",
"SuccessfullyDeleted": "Successfully deleted!"
Index.js
的内容最终如下所示:
$(function () {var l = abp.localization.getResource('BookStore');var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal');var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({serverSide: true,paging: true,order: [[1, "asc"]],searching: false,scrollX: true,ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList),columnDefs: [{title: l('Actions'),rowAction: {items:[{text: l('Edit'),action: function (data) {editModal.open({ id: data.record.id });}},{text: l('Delete'),confirmMessage: function (data) {return l('BookDeletionConfirmationMessage',data.record.name);},action: function (data) {acme.bookStore.books.book.delete(data.record.id).then(function() {abp.notify.info(l('SuccessfullyDeleted'));dataTable.ajax.reload();});}}]}},{title: l('Name'),data: "name"},{title: l('Type'),data: "type",render: function (data) {return l('Enum:BookType:' + data);}},{title: l('PublishDate'),data: "publishDate",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString();}},{title: l('Price'),data: "price"},{title: l('CreationTime'), data: "creationTime",render: function (data) {return luxon.DateTime.fromISO(data, {locale: abp.localization.currentCulture.name}).toLocaleString(luxon.DateTime.DATETIME_SHORT);}}]}));createModal.onResult(function () {dataTable.ajax.reload();});editModal.onResult(function () {dataTable.ajax.reload();});$('#NewBookButton').click(function (e) {e.preventDefault();createModal.open();});
});
你可以运行程序并尝试删除一本书.
如下图所示
到这里一个基本的CURD 就完成了