ABP框架入门学习【进阶】(八)——作者模块展现层
一、列表页面
TestApp.BookStore.Web项目Pages>Authors文件夹创建一个新的razor页面, Index.cshtml
, 修改文件内容如下:
Index.cshtml
@page @using TestApp.BookStore.Localization @using TestApp.BookStore.Web.Pages.Books @using TestApp.BookStore.Permissions @using Microsoft.Extensions.Localization @using Microsoft.AspNetCore.Authorization @model TestApp.BookStore.Web.Pages.Authors.IndexModel @inject IStringLocalizer<BookStoreResource> L @inject IAuthorizationService AuthorizationService @section scripts { <abp-script src="/Pages/Authors/Index.js"/> } <abp-card> <abp-card-header> <abp-row> <abp-column size-md="_6"> <abp-card-title>@L["Authors"]</abp-card-title> </abp-column> <abp-column size-md="_6" class="text-right"> @if (await AuthorizationService.IsGrantedAsync(BookStorePermissions.Books.Create)) { <abp-button id="NewAuthorButton" text="@L["NewAuthor"].Value" icon="plus" button-type="Primary"/> } </abp-column> </abp-row> </abp-card-header> <abp-card-body> <abp-table striped-rows="true" id="AuthorsTable"></abp-table> </abp-card-body> </abp-card>
和我们以前创建的图书页面一样. 它导入了一个JavaScript文件
Index.cshtml.cs
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace TestApp.BookStore.Web.Pages.Authors { public class IndexModel : PageModel { public void OnGet() { } } }
Index.js
$(function () { var l = abp.localization.getResource('BookStore'); //获取列表 var dataTable = $('#AuthorsTable').DataTable( abp.libs.datatables.normalizeConfiguration({ serverSide: true, paging: true, order: [[1, "asc"]], searching: false, scrollX: true, ajax: abp.libs.datatables.createAjax(testApp.bookStore.authors.author.getList), columnDefs: [ { title: l('Actions'), rowAction: { items: [ { text: l('Edit'), visible: abp.auth.isGranted('BookStore.Authors.Edit'), //CHECK for the PERMISSION action: function (data) { editModal.open({ id: data.record.id }); } }, { text: l('Delete'), visible: abp.auth.isGranted('BookStore.Authors.Delete'), //CHECK for the PERMISSION confirmMessage: function (data) { return l( 'AuthorDeletionConfirmationMessage', data.record.name ); }, action: function (data) { testApp.bookStore.authors.author .delete(data.record.id) .then(function () { abp.notify.info( l('SuccessfullyDeleted') ); dataTable.ajax.reload(); }); } } ] } }, { title: l('Name'), data: "name" }, { title: l('BirthDate'), data: "birthDate", render: function (data) { return luxon .DateTime .fromISO(data, { locale: abp.localization.currentCulture.name }).toLocaleString(); } }, { title: l('ShortBio'), data: "shortBio" } ] }) ); //新增操作 var createModal = new abp.ModalManager(abp.appPath + 'Authors/CreateModal'); createModal.onResult(function () { dataTable.ajax.reload(); }); $('#NewAuthorButton').click(function (e) { e.preventDefault(); createModal.open(); }); //修改操作(columnDefs新增Actions操作列) var editModal = new abp.ModalManager(abp.appPath + 'Authors/EditModal'); editModal.onResult(function () { dataTable.ajax.reload(); }); })
- 创建了一个具有
操作
,姓名
和生日
列的数据表格.Actions
列用来添加 编辑 和 删除 操作.生日
提供了一个render
函数, 使用 luxon 库格式化DateTime
值.
- 使用
abp.ModalManager
打开 新建 和 编辑 模态表单.
本地化配置
里面涉及到的文本本地化,打开项目TestApp.BookStore.Domain.Shared>Localization>BookStore>en.json,新增作者相关配置如下:
"Permission:Authors": "Author Management", "Permission:Authors.Create": "Creating new authors", "Permission:Authors.Edit": "Editing the authors", "Permission:Authors.Delete": "Deleting the authors", "Menu:Authors": "Authors", "Authors": "Authors", "AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?", "BirthDate": "Birth date", "NewAuthor": "New author"
权限配置
权限配置类似Books相关,首先本地配置见上小节,然后应用层配置,打开TestApp.BookStore.Application.Contracts>Permissions
修改BookStorePermissions如下:
namespace TestApp.BookStore.Permissions; public static class BookStorePermissions { public const string GroupName = "BookStore"; //Add your own permission names. Example: //public const string MyPermission1 = GroupName + ".MyPermission1"; public static class Books { public const string Default = GroupName + ".Books"; public const string Create = Default + ".Create"; public const string Edit = Default + ".Edit"; public const string Delete = Default + ".Delete"; } public static class Authors { public const string Default = GroupName + ".Authors"; public const string Create = Default + ".Create"; public const string Edit = Default + ".Edit"; public const string Delete = Default + ".Delete"; } }
修改BookStorePermissionDefinitionProvider如下:
using TestApp.BookStore.Localization; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Localization; namespace TestApp.BookStore.Permissions; public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider { public override void Define(IPermissionDefinitionContext context) { var myGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore")); //Define your own permissions here. Example: //myGroup.AddPermission(BookStorePermissions.MyPermission1, L("Permission:MyPermission1")); var booksPermission = myGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books")); booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create")); booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit")); booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete")); var authorsPermission = myGroup.AddPermission( BookStorePermissions.Authors.Default, L("Permission:Authors")); authorsPermission.AddChild( BookStorePermissions.Authors.Create, L("Permission:Authors.Create")); authorsPermission.AddChild( BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit")); authorsPermission.AddChild( BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete")); } private static LocalizableString L(string name) { return LocalizableString.Create<BookStoreResource>(name); } }
作者菜单配置
打开项目TestApp.BookStore.Web>Menus>BookStoreMenuContributor.cs,修改为:
using System.Threading.Tasks; using TestApp.BookStore.Localization; using TestApp.BookStore.MultiTenancy; using Volo.Abp.Identity.Web.Navigation; using Volo.Abp.SettingManagement.Web.Navigation; using Volo.Abp.TenantManagement.Web.Navigation; using Volo.Abp.UI.Navigation; using TestApp.BookStore.Permissions; namespace TestApp.BookStore.Web.Menus; public class BookStoreMenuContributor : IMenuContributor { public async Task ConfigureMenuAsync(MenuConfigurationContext context) { if (context.Menu.Name == StandardMenus.Main) { await ConfigureMainMenuAsync(context); } } private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) { var administration = context.Menu.GetAdministration(); var l = context.GetLocalizer<BookStoreResource>(); context.Menu.Items.Insert( 0, new ApplicationMenuItem( BookStoreMenus.Home, l["Menu:Home"], "~/", icon: "fas fa-home", order: 0 ) ); if (MultiTenancyConsts.IsEnabled) { administration.SetSubItemOrder(TenantManagementMenuNames.GroupName, 1); } else { administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName); } administration.SetSubItemOrder(IdentityMenuNames.GroupName, 2); administration.SetSubItemOrder(SettingManagementMenuNames.GroupName, 3); //context.Menu.AddItem( // new ApplicationMenuItem( // "BooksStore", // l["Menu:BookStore"], // icon: "fa fa-book" // ).AddItem( // new ApplicationMenuItem( // "BooksStore.Books", // l["Menu:Books"], // url: "/Books" // ) // ) // ); var bookStoreMenu = new ApplicationMenuItem( "BooksStore", l["Menu:BookStore"], icon: "fa fa-book" ); context.Menu.AddItem(bookStoreMenu); //CHECK the PERMISSION if (await context.IsGrantedAsync(BookStorePermissions.Books.Default)) { bookStoreMenu.AddItem(new ApplicationMenuItem( "BooksStore.Books", l["Menu:Books"], url: "/Books" )); } if (await context.IsGrantedAsync(BookStorePermissions.Authors.Default)) { bookStoreMenu.AddItem(new ApplicationMenuItem( "BooksStore.Authors", l["Menu:Authors"], url: "/Authors" )); } } }
运行项目,可以看到Author list以及Author Permission相关,如下图:
二、新增页面
TestApp.BookStore.Web项目Pages>Authors文件夹创建一个新的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.Authors.CreateModalModel @inject IStringLocalizer<BookStoreResource> L @{ Layout = null; } <form asp-page="/Authors/CreateModal"> <abp-modal> <abp-modal-header title="@L["NewAuthor"].Value"></abp-modal-header> <abp-modal-body> <abp-input asp-for="Author.Name" /> <abp-input asp-for="Author.BirthDate" /> <abp-input asp-for="Author.ShortBio" /> </abp-modal-body> <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> </abp-modal> </form>
之前我们已经使用ABP框架的 动态表单开发了图书页面. 这里可以使用相同的方法, 但我们希望展示如何手工完成它. 实际上, 没有那么手工化, 因为在这个例子中我们使用了 abp-input
标签简化了表单元素的创建.
你当然可以使用标准Bootstrap HTML结构, 但是这需要写很多代码. abp-input
自动添加验证, 本地化和根据数据类型生成标准元素.
CreateModal.cshtml.cs
using Microsoft.AspNetCore.Mvc; using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using TestApp.BookStore.Authors; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; namespace TestApp.BookStore.Web.Pages.Authors { public class CreateModalModel : BookStorePageModel { [BindProperty] public CreateAuthorViewModel Author { get; set; } private readonly IAuthorAppService _authorAppService; public CreateModalModel(IAuthorAppService authorAppService) { _authorAppService = authorAppService; } public void OnGet() { Author = new CreateAuthorViewModel(); } public async Task<IActionResult> OnPostAsync() { var dto = ObjectMapper.Map<CreateAuthorViewModel, CreateAuthorDto>(Author); await _authorAppService.CreateAsync(dto); return NoContent(); } public class CreateAuthorViewModel { [Required] [StringLength(AuthorConsts.MaxNameLength)] public string Name { get; set; } [Required] [DataType(DataType.Date)] public DateTime BirthDate { get; set; } [TextArea] public string ShortBio { get; set; } } } }
这个页面模型类注入和使用 IAuthorAppService
创建新作者. 它和图书创建模型类之间主要的区别是这个模型类为视图模型声明了一个新类 CreateAuthorViewModel
, 而不是重用 CreateAuthorDto
.
这么做的主要原因是展示在页面中如何使用不同的模型. 但还有一个好处: 我们为类成员添加了两个不存在于 CreateAuthorDto
中的特性:
- 为
BirthDate
添加[DataType(DataType.Date)]
特性, 这会在UI为这个属性显示一个日期选择控件. - 为
ShortBio
添加[TextArea]
特性, 这会显示一个多行文本框, 而不是标准文本框.
通过这种方式, 可以根据UI需求定制视图模型类, 而无需修改DTO. 这么做的一个结果是: 使用 ObjectMapper
将 CreateAuthorViewModel
映射到 CreateAuthorDto
. 为了完成映射, 需要在 BookStoreWebAutoMapperProfile
构造函数中加入新的映射代码:
三、编辑页面
TestApp.BookStore.Web项目Pages>Authors文件夹创建一个新的razor页面, EditModal.cshtml
, 修改文件内容如下:
EditModal.cshtml
@page @using TestApp.BookStore.Localization @using TestApp.BookStore.Web.Pages.Authors @using Microsoft.Extensions.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @model TestApp.BookStore.Web.Pages.Authors.EditModalModel @inject IStringLocalizer<BookStoreResource> L @{ Layout = null; } <form asp-page="/Authors/EditModal"> <abp-modal> <abp-modal-header title="@L["Update"].Value"></abp-modal-header> <abp-modal-body> <abp-input asp-for="Author.Id" /> <abp-input asp-for="Author.Name" /> <abp-input asp-for="Author.BirthDate" /> <abp-input asp-for="Author.ShortBio" /> @*<abp-form-content />*@ </abp-modal-body> <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer> </abp-modal> </form>
EditModal.cshtml.cs
using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using TestApp.BookStore.Authors; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; namespace TestApp.BookStore.Web.Pages.Authors { public class EditModalModel : BookStorePageModel { [BindProperty] public EditAuthorViewModel Author { get; set; } private readonly IAuthorAppService _authorAppService; public EditModalModel(IAuthorAppService authorAppService) { _authorAppService = authorAppService; } public async Task OnGetAsync(Guid id) { var authorDto = await _authorAppService.GetAsync(id); Author = ObjectMapper.Map<AuthorDto, EditAuthorViewModel>(authorDto); } public async Task<IActionResult> OnPostAsync() { await _authorAppService.UpdateAsync(Author.Id, ObjectMapper.Map<EditAuthorViewModel, UpdateAuthorDto>(Author)); return NoContent(); } public class EditAuthorViewModel { [HiddenInput] public Guid Id { get; set; } [Required] [StringLength(AuthorConsts.MaxNameLength)] public string Name { get; set; } [Required] [DataType(DataType.Date)] public DateTime BirthDate { get; set; } [TextArea] public string ShortBio { get; set; } } } }
这个类与 CreateModal.cshtml.cs
类似, 主要不同是:
- 使用
IAuthorAppService.GetAsync(...)
方法从应用层获取正在编辑的作者. EditAuthorViewModel
拥有一个额外的Id
属性, 它被[HiddenInput]
特性标记, 会为这个属性在页面上创建一个隐藏输入框.
这个类要求在 BookStoreWebAutoMapperProfile
类中添加两个对象映射声明:
using AutoMapper; using TestApp.BookStore.Authors; 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>(); // ADD a NEW MAPPING CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel, CreateAuthorDto>(); CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>(); CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel, UpdateAuthorDto>(); } }
四、映射申明
创建和编辑页映射申明,打开项目TestApp.BookStore.Web>BookStoreWebAutoMapperProfile.CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel,
// ADD a NEW MAPPING CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel, CreateAuthorDto>(); CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>(); CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel, UpdateAuthorDto>();
最终完整映射脚本如下:
using AutoMapper; using TestApp.BookStore.Authors; 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>(); // ADD a NEW MAPPING CreateMap<Pages.Authors.CreateModalModel.CreateAuthorViewModel, CreateAuthorDto>(); CreateMap<AuthorDto, Pages.Authors.EditModalModel.EditAuthorViewModel>(); CreateMap<Pages.Authors.EditModalModel.EditAuthorViewModel, UpdateAuthorDto>(); } }
以上作者模板增删改查功能结束!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库