Web 应用开发教程 - 第 9 部分:作者:用户界面
简介
本部分介绍如何为前几部分引入的 Author 实体创建 CRUD 页面。
作者管理页面
Authors Razor 组件
在 Acme.BookStore.Blazor 项目中创建一个新的 Razor 组件页面 /Pages/Authors.razor,内容如下:
@page "/authors"
@using Acme.BookStore.Authors
@using Acme.BookStore.Localization
@using Volo.Abp.AspNetCore.Components.Web
@inherits BookStoreComponentBase
@inject IAuthorAppService AuthorAppService
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
<Card>
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Authors"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
@if (CanCreateAuthor)
{
<Button Color="Color.Primary"
Clicked="OpenCreateAuthorModal">
@L["NewAuthor"]
</Button>
}
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="AuthorDto"
Data="AuthorList"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn Width="150px"
TItem="AuthorDto"
Field="@nameof(AuthorDto.Id)"
Sortable="false"
Caption="@L["Actions"]">
<DisplayTemplate>
<Dropdown>
<DropdownToggle Color="Color.Primary">
@L["Actions"]
</DropdownToggle>
<DropdownMenu>
@if (CanEditAuthor)
{
<DropdownItem Clicked="() => OpenEditAuthorModal(context)">
@L["Edit"]
</DropdownItem>
}
@if (CanDeleteAuthor)
{
<DropdownItem Clicked="() => DeleteAuthorAsync(context)">
@L["Delete"]
</DropdownItem>
}
</DropdownMenu>
</Dropdown>
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="AuthorDto"
Field="@nameof(AuthorDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="AuthorDto"
Field="@nameof(AuthorDto.BirthDate)"
Caption="@L["BirthDate"]">
<DisplayTemplate>
@context.BirthDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="CreateAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewAuthor"]</ModalTitle>
<CloseButton Clicked="CloseCreateAuthorModal" />
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewAuthor" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewAuthor.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@NewAuthor.BirthDate"/>
</Field>
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-Text="@NewAuthor.ShortBio">
<Feedback>
<ValidationError/>
</Feedback>
</MemoEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="EditAuthorModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingAuthor.Name</ModalTitle>
<CloseButton Clicked="CloseEditAuthorModal" />
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingAuthor" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingAuthor.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["BirthDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="@EditingAuthor.BirthDate"/>
</Field>
<Validation>
<Field>
<FieldLabel>@L["ShortBio"]</FieldLabel>
<MemoEdit Rows="5" @bind-Text="@EditingAuthor.ShortBio">
<Feedback>
<ValidationError/>
</Feedback>
</MemoEdit>
</Field>
</Validation>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditAuthorModal">
@L["Cancel"]
</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateAuthorAsync">
@L["Save"]
</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
- 此代码与
Books.razor类似,只是它不从AbpCrudPageBase继承,而是使用自己的实现。 - 注入
IAuthorAppService以便从 UI 使用服务器端 HTTP API。通过动态 C# HTTP API 客户端代理系统的帮助,我们可以直接注入应用服务接口并像常规方法调用一样使用,该系统为我们执行 REST API 调用。请参阅下面的Authors类以查看用法。
在 Pages 文件夹下创建一个新的代码隐藏文件 Authors.razor.cs,内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Acme.BookStore.Authors;
using Acme.BookStore.Permissions;
using Blazorise;
using Blazorise.DataGrid;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Blazor.Pages;
public partial class Authors
{
private IReadOnlyList<AuthorDto> AuthorList { get; set; }
private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount;
private int CurrentPage { get; set; }
private string CurrentSorting { get; set; }
private int TotalCount { get; set; }
private bool CanCreateAuthor { get; set; }
private bool CanEditAuthor { get; set; }
private bool CanDeleteAuthor { get; set; }
private CreateAuthorDto NewAuthor { get; set; }
private Guid EditingAuthorId { get; set; }
private UpdateAuthorDto EditingAuthor { get; set; }
private Modal CreateAuthorModal { get; set; }
private Modal EditAuthorModal { get; set; }
private Validations CreateValidationsRef;
private Validations EditValidationsRef;
public Authors()
{
NewAuthor = new CreateAuthorDto();
EditingAuthor = new UpdateAuthorDto();
}
protected override async Task OnInitializedAsync()
{
await SetPermissionsAsync();
await GetAuthorsAsync();
}
private async Task SetPermissionsAsync()
{
CanCreateAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Create);
CanEditAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Edit);
CanDeleteAuthor = await AuthorizationService
.IsGrantedAsync(BookStorePermissions.Authors.Delete);
}
private async Task GetAuthorsAsync()
{
var result = await AuthorAppService.GetListAsync(
new GetAuthorListDto
{
MaxResultCount = PageSize,
SkipCount = CurrentPage * PageSize,
Sorting = CurrentSorting
}
);
AuthorList = result.Items;
TotalCount = (int)result.TotalCount;
}
private async Task OnDataGridReadAsync(DataGridReadDataEventArgs<AuthorDto> e)
{
CurrentSorting = e.Columns
.Where(c => c.SortDirection != SortDirection.Default)
.Select(c => c.Field + (c.SortDirection == SortDirection.Descending ? " DESC" : ""))
.JoinAsString(",");
CurrentPage = e.Page - 1;
await GetAuthorsAsync();
await InvokeAsync(StateHasChanged);
}
private void OpenCreateAuthorModal()
{
CreateValidationsRef.ClearAll();
NewAuthor = new CreateAuthorDto();
CreateAuthorModal.Show();
}
private void CloseCreateAuthorModal()
{
CreateAuthorModal.Hide();
}
private void OpenEditAuthorModal(AuthorDto author)
{
EditValidationsRef.ClearAll();
EditingAuthorId = author.Id;
EditingAuthor = ObjectMapper.Map<AuthorDto, UpdateAuthorDto>(author);
EditAuthorModal.Show();
}
private async Task DeleteAuthorAsync(AuthorDto author)
{
try
{
var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name];
if (!await Message.Confirm(confirmMessage))
{
return;
}
await AuthorAppService.DeleteAsync(author.Id);
await GetAuthorsAsync();
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
private void CloseEditAuthorModal()
{
EditAuthorModal.Hide();
}
private async Task CreateAuthorAsync()
{
try
{
if (await CreateValidationsRef.ValidateAll())
{
await AuthorAppService.CreateAsync(NewAuthor);
await GetAuthorsAsync();
CreateAuthorModal.Hide();
}
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
private async Task UpdateAuthorAsync()
{
try
{
if (await EditValidationsRef.ValidateAll())
{
await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor);
await GetAuthorsAsync();
EditAuthorModal.Hide();
}
}
catch(Exception ex)
{
await HandleErrorAsync(ex);
}
}
}
此类主要定义了 Authors.razor 页面使用的属性和方法。
对象映射
Authors 类在 OpenEditAuthorModal 方法中使用了 IObjectMapper。因此,我们需要定义此映射。
打开 Acme.BookStore.Blazor 项目中的 BookStoreBlazorMappers.cs 文件,并在类中添加以下映射:
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
using Acme.BookStore.Authors;
//...
[Mapper]
public partial class AuthorDtoToUpdateAuthorDtoMapper : MapperBase<AuthorDto, UpdateAuthorDto>
{
public override partial UpdateAuthorDto Map(AuthorDto source);
public override partial void Map(AuthorDto source, UpdateAuthorDto destination);
}
添加到主菜单
打开 Acme.BookStore.Blazor 项目中的 BookStoreMenuContributor.cs 文件,并将以下代码添加到 ConfigureMainMenuAsync 方法的末尾:
context.Menu.AddItem(new ApplicationMenuItem(
"BooksStore.Authors",
l["Menu:Authors"],
url: "/authors"
).RequirePermissions(BookStorePermissions.Books.Default));
本地化配置
我们应该完成上面使用的本地化配置。打开 Acme.BookStore.Domain.Shared 项目 Localization/BookStore 文件夹下的 en.json 文件,并添加以下条目:
"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"
运行应用程序
运行并登录到应用程序。如果你在 书店 菜单下没有看到 Authors 菜单项,这意味着你还没有权限。 进入 identity/roles 页面,点击 *Actions* 按钮,并为 admin 角色选择 *Permissions* 操作:
如你所见,admin 角色目前还没有 *Author Management* 权限。点击复选框并保存模态窗体以授予必要的权限。刷新页面后,你将在主菜单的 *书店* 下看到 *Authors* 菜单项:
就是这样!这是一个功能完整的 CRUD 页面,你可以创建、编辑和删除作者。
提示:在定义新权限后,如果运行
.DbMigrator控制台应用程序,它会自动将这些新权限授予 admin 角色,你无需手动授予权限。
抠丁客




