ABP框架入门学习(三) ——UI展现层增删改查实现
上一篇文章咋们说道新增的BOOK模块,从实体到领域层再到应用层,自动生成出来的swagger也完成,接下来咋们直接使用上面所封装的给展现层的函数
前言准备工作:
1、首先运行项目,在开发者模式(浏览器F12)测试getList和Create功能
testApp.bookStore.books
是BookAppService
转换为camelCase的命名空间。book
是BookAppService
(删除AppService
后缀并转换为驼峰式)的常规名称。getList
GetListAsync
是在基类中定义的方法的常规名称CrudAppService
(删除了Async
后缀并转换为 camelCase)。- 该
{}
参数用于向方法发送一个空对象,该GetListAsync
方法通常需要一个类型的对象,该对象PagedAndSortedResultRequestDto
用于向服务器发送分页和排序选项(所有属性都是可选的,具有默认值,因此您可以发送一个空对象)。 - 该
getList
函数返回一个promise
. 您可以将回调传递给then
(ordone
) 函数以获取从服务器返回的结果。
检查Books
数据库中的表以查看新书行。您可以尝试get
,update
并delete
自己运行。
2、文本本地化
由于我们后面会使用了很多本地化文本,所以需要将它们添加到本地化文件(en.json
在项目TestApp.BookStore.Domain.Shared下
Localization/BookStore
文件夹下),如:@L["Books"]、@L["NewBook"]等
脚本如下:
{ "culture": "en", "texts": { "Menu:Home": "Home", "Welcome": "Welcome", "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", "Menu:BookStore": "Book Store", "Menu:Books": "Books", "Actions": "Actions", "Close": "Close", "Delete": "Delete", "Edit": "Edit", "PublishDate": "Publish date", "NewBook": "New book", "Name": "Name", "Type": "Type", "Price": "Price", "CreationTime": "Creation time", "AreYouSure": "Are you sure?", "AreYouSureToDelete": "Are you sure you want to delete this item?", "Enum:BookType:0": "Undefined", "Enum:BookType:1": "Adventure", "Enum:BookType:2": "Biography", "Enum:BookType:3": "Dystopia", "Enum:BookType:4": "Fantastic", "Enum:BookType:5": "Horror", "Enum:BookType:6": "Science", "Enum:BookType:7": "Science fiction", "Enum:BookType:8": "Poetry" } }
- 本地化键名是任意的。您可以设置任何名称。我们更喜欢针对特定文本类型的一些约定;
Menu:
为菜单项添加前缀。- 使用
Enum:<enum-type>:<enum-value>
命名约定来本地化枚举成员。当你这样做时,ABP 可以在某些适当的情况下自动本地化枚举。
——————————UI项目增删改查功能开始——————————
一、创建图书页面(查)
在项目TestApp.BookStore.Web
下创建一个文件夹Books。通过右键单击 Books 文件夹然后选择Add > Razor Page菜单项来添加新的 Razor Page。将其命名为:
Index
添加书籍主菜单
打开项目TestApp.BookStore.Web项目下Menus文件夹中的BookStoreMenuContributor
类,在方法末尾添加如下代码:
context.Menu.AddItem( new ApplicationMenuItem( "BooksStore", l["Menu:BookStore"], icon: "fa fa-book" ).AddItem( new ApplicationMenuItem( "BooksStore.Books", l["Menu:Books"], url: "/Books" ) ) );
运行项目,使用用户名admin
和密码登录应用程序1q2w3E*
,您可以看到新的菜单项已添加到主菜单中:
实现书籍列表功能
@page @using TestApp.BookStore.Localization @using TestApp.BookStore.Web.Pages.Books @using Microsoft.Extensions.Localization @model TestApp.BookStore.Web.Pages.Books.IndexModel @inject IStringLocalizer<BookStoreResource> L @section scripts { <abp-script src="/Pages/Books/Index.js"/> } <abp-card> <abp-card-header> <h2>@L["Books"]</h2> </abp-card-header> <abp-card-body> <abp-table striped-rows="true" id="BooksTable"></abp-table> </abp-card-body> </abp-card>
abp-script
标签助手用于向页面添加外部脚本。script
与标准标签相比,它具有许多附加功能。它处理缩小和版本控制。查看捆绑和缩小文档以获取详细信息。abp-card
是 Twitter Bootstrap 的卡片组件的标签助手。ABP 框架提供了其他有用的标签助手,可以轻松使用大多数 bootstrap的组件。您可以使用常规 HTML 标签代替这些标签助手,但使用标签助手可减少 HTML 代码并通过 IntelliSense 的帮助防止错误并编译时间类型检查。有关更多信息,请查看标签助手文档。
Pages/Books下新增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(testapp.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); } } ] }) ); })
abp.localization.getResource
获取一个函数,该函数用于使用在服务器端定义的相同 JSON 文件来本地化文本。通过这种方式,您可以与客户端共享本地化值。abp.libs.datatables.normalizeConfiguration
是 ABP 框架定义的辅助函数。不需要使用它,但它通过为缺少的选项提供常规默认值来简化Datatables配置。abp.libs.datatables.createAjax
是另一个帮助函数,用于使 ABP 的动态 JavaScript API 代理适应Datatable的预期参数格式acme.bookStore.books.book.getList
就是之前介绍的动态JavaScript代理功能。- luxon库也是解决方案中预配置的标准库,因此您可以使用它轻松执行日期/时间操作。
运行项目,可以查看书籍列表,如下图:
二、创建新书(增)
创建模态表单
在项目TestApp.BookStore.Web下Pages/Books的文件夹下创建一个名CreateModal.cshtml的Razor页面
CreateModal.cshtml
打开CreateModal.cshtml
文件并粘贴以下代码:
@page @using TestApp.BookStore.Localization @using TestApp.BookStore.Web.Pages.Books @using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model TestApp.BookStore.Web.Pages.Books.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>
- 此模式使用
abp-dynamic-form
标签助手自动从CreateUpdateBookDto
模型类创建表单。 abp-model
属性指示模型对象,Book
在这种情况下它是属性。abp-form-content
标签助手是呈现表单控件的占位符(它是可选的,仅当您在abp-dynamic-form
标签中添加了一些其他内容时才需要,就像在这个页面中一样)。
提示:
Layout
应该null
和本例中一样,因为我们不想在通过 AJAX 加载模态框时包含所有布局。
CreateModal.cshtml.cs
打开CreateModal.cshtml.cs
文件(CreateModalModel
类)并将其替换为以下代码:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System.Threading.Tasks; using TestApp.BookStore.Books; using Microsoft.AspNetCore.Mvc; namespace TestApp.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
并添加了一些可以在页面模型类中共享的通用属性和方法。 [BindProperty]
属性上的Book
属性将发布请求数据绑定到此属性。- 此类只是
IBookAppService
在构造函数中注入 并调用处理程序CreateAsync
中的方法OnPostAsync
。 - 它在方法中创建一个新
CreateUpdateBookDto
对象OnGet
。ASP.NET Core 可以在不创建这样的新实例的情况下工作。但是,它不会为您创建实例,并且如果您的类在类构造函数中有一些默认值分配或代码执行,它们将不起作用。CreateUpdateBookDto
对于这种情况,我们为某些属性设置了默认值。
调整Index页面,添加“新书”按钮
脚本如下:
@page @using TestApp.BookStore.Localization @using TestApp.BookStore.Web.Pages.Books @using Microsoft.Extensions.Localization @model TestApp.BookStore.Web.Pages.Books.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-right"> <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>
这会在表格的右上角添加一个名为New book的新按钮:
实现新增按钮事件,修改Pages/Books/Index.js
文件
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
是一个帮助类来管理客户端的模态。它在内部使用 Twitter Bootstrap 的标准模式,但通过提供简单的 API 抽象了许多细节。createModal.onResult(...)
用于创建新书后刷新数据表。createModal.open();
用于打开模型以创建新书。
文件的最终内容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(testApp.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(); }); })
运行应用程序并使用新的模态表单添加一些新书。
三、更新书籍信息(改)
在项目TestApp.BookStore.Web下Pages/Books的文件夹下创建一个名EditModal.cshtml的Razor页面
EditModal.cshtml
将EditModal.cshtml
内容替换为以下内容:
此页面与 非常相似CreateModal.cshtml
,除了:
- 它包括一个
abp-input
用于Id
存储Id
编辑书的属性(这是一个隐藏的输入)。 - 它
Books/EditModal
用作发布 URL。
EditModal.cshtml.cs
打开EditModal.cshtml.cs
文件(EditModalModel
类)并将其替换为以下代码:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System; using System.Threading.Tasks; using TestApp.BookStore.Books; namespace TestApp.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
用于能够Id
从请求的查询字符串参数中获取值。- 在该
OnGetAsync
方法中,我们BookDto
从 中获取 ,BookAppService
并将其映射到 DTO 对象CreateUpdateBookDto
。 - 用于更新实体的
OnPostAsync
用途。BookAppService.UpdateAsync(...)
从 BookDto 映射到 CreateUpdateBookDto
为了能够映射BookDto
到CreateUpdateBookDto
,配置一个新的映射。为此,请在TestApp.BookStore.Web
项目中打开文件BookStoreWebAutoMapperProfile.cs并进行更改,如下所示:
using AutoMapper; using TestApp.BookStore.Books; namespace TestApp.BookStore.Web; public class BookStoreWebAutoMapperProfile : Profile { public BookStoreWebAutoMapperProfile() { //Define your AutoMapper configuration here for the Web project. CreateMap<BookDto, CreateUpdateBookDto>(); } }
请注意,我们将在 web 层中进行映射定义作为最佳实践,因为它仅在该层中需要。
将“操作”下拉列表添加到表中
打开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(testApp.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); } } ] }) ); //新增操作 var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); createModal.onResult(function () { dataTable.ajax.reload(); }); $('#NewBookButton').click(function (e) { e.preventDefault(); createModal.open(); }); })
- 添加了一个新
ModalManager
名称editModal
以打开编辑模式对话框。 - 在该部分的开头添加了一个新列
columnDefs
。此列用于“操作”下拉按钮。 - “编辑”操作只是调用
editModal.open()
打开编辑对话框。 - 当
editModal.onResult(...)
您关闭编辑模式时,回调会刷新数据表。
运行应用程序并编辑修改书籍信息
最终的 UI 如下所示:
四、删除书籍(删)
打开Pages/Books/Index.js
文件并将新项目添加到rowAction>
items
:
{ text: l('Delete'), confirmMessage: function (data) { return l( 'BookDeletionConfirmationMessage', data.record.name ); }, action: function (data) { testApp.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 请求以删除一本书。 abp.notify.info()
删除操作后显示通知。
由于我们使用了两个新的本地化文本(BookDeletionConfirmationMessage
和SuccessfullyDeleted
),您需要将它们添加到本地化文件(en.json
在项目TestApp.BookStore.Domain.Shared下
Localization/BookStore
文件夹下):
"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?", "SuccessfullyDeleted": "Successfully deleted!"
en.json最终脚本如下:
{ "culture": "en", "texts": { "Menu:Home": "Home", "Welcome": "Welcome", "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", "Menu:BookStore": "Book Store", "Menu:Books": "Books", "Actions": "Actions", "Close": "Close", "Delete": "Delete", "Edit": "Edit", "PublishDate": "Publish date", "NewBook": "New book", "Name": "Name", "Type": "Type", "Price": "Price", "CreationTime": "Creation time", "AreYouSure": "Are you sure?", "AreYouSureToDelete": "Are you sure you want to delete this item?", "Enum:BookType:0": "Undefined", "Enum:BookType:1": "Adventure", "Enum:BookType:2": "Biography", "Enum:BookType:3": "Dystopia", "Enum:BookType:4": "Fantastic", "Enum:BookType:5": "Horror", "Enum:BookType:6": "Science", "Enum:BookType:7": "Science fiction", "Enum:BookType:8": "Poetry", "BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?", "SuccessfullyDeleted": "Successfully deleted!" } }
最终Page/Books/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(testApp.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) { testApp.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); } } ] }) ); //新增操作 var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); createModal.onResult(function () { dataTable.ajax.reload(); }); $('#NewBookButton').click(function (e) { e.preventDefault(); createModal.open(); }); //修改操作(columnDefs新增Actions操作列) var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); editModal.onResult(function () { dataTable.ajax.reload(); }); })
运行应用程序并尝试删除书籍信息。
至此,Book板块的增删改查展现层完成,在此记录,方便读者参考学习!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库