Abp Framework手动实践

发布时间 2023-07-15 18:13:08作者: 摧残一生

上一章节有很大一部分是使用默认的CRID进行操作的,本章节将手动进行各层的编写,本次以Student为模块进行开发。

领域层

实体

***.Domain项目中创建Student文件夹,并在文件夹中创建Student的实体类,该实体类继承FullAuditedAggregateRoot<Guid>类,主键为Guid类型。

Student共有三个属性,分别为姓名,出生日期和地址。

public class Student : FullAuditedAggregateRoot<Guid>
{
	// 姓名不能外部设置,当new一个实例或调用ChangeName时才能修改姓名
    public string Name { get; private set; }
    public DateTime Birthday { get; set; }
    public string Address { get; set; }

    private Student()
    {}

    internal Student(
        Guid id,
        // 不能为null
        [NotNull] string name,
        DateTime birthday,
        // 可以为null
        [CanBeNull] string address = null)
    : base(id)
    {
        SetName(name);
        Birthday = birthday;
        Address = address;
    }

    internal Student ChangeName([NotNull] string name)
    {
        SetName(name);
        return this;
    }
	// 设置姓名
    private void SetName([NotNull] string name)
    {
        Name = Check.NotNullOrWhiteSpace(
            name,
            nameof(name),
            maxLength: StudentConsts.MaxNameLength
        );
    }
}
  • FullAuditedAggregateRoot<Guid> 继承使得实体支持软删除 (指实体被删除时, 它并没有从数据库中被删除, 而只是被标记删除), 实体也具有了审计 属性

字段检查

为了避免某个名称过长,或使用了枚举等,可以在***.Domain.Shared项目中添加限定条件,例如姓名最大长度不能超过5个汉字,可在此项目中添加Student文件夹,然后创建一个StudentConsts

public static class StudentConsts{
	public const int MaxNameLength = 5;
}

此类会在DTOs(数据传输类)中使用。

领域服务

因为实体类中的构造函数访问级别是internal的,因此只能在领域层访问他(也就是同一个命名空间中),因此在***.Doamin项目的Student文件夹中创建StudentManager类,用来创建Student类和修改学生姓名。

StudentManager

public class StudentManager : DomainService
{
	private readonly IStudentRepository _studentRepository;
	// 初始化时从仓库中获得学生的操作类
    public StudentManager(IStudentRepository studentRepository)
    {
	    _studentRepository = studentRepository;
    }

	// 创建学生实体
    public async Task<Student> CreateAsync(
        [NotNull] string name,
        DateTime brithday,
        [CanBeNull] string address = null)
    {
    	// 判断学生姓名是否为空
	    Check.NotNullOrWhiteSpace(name, nameof(name));
        // 从数据库中查找是否存在同名的学生
        var existingStudent = await _studentRepository.FindByNameAsync(name);
        // 如果存在就返回异常
        if (existingStudent != null)
        {
	        throw new StudentAlreadyExistsException(name);
        }
		// 不存在,则进行创建并返回
        return new Student(
            GuidGenerator.Create(),
            name,
            birthDate,
            Address
        );
    }
	// 传入实体类和修改后的学生姓名
    public async Task ChangeNameAsync(
        [NotNull] Student student,
        [NotNull] string newName)
    {
    	// 判断两个参数都不能为空
    	Check.NotNull(student, nameof(student));
    	Check.NotNullOrWhiteSpace(newName, nameof(newName));
		
    	var existingStudent = await _studentRepository.FindByNameAsync(newName);
    	// 如果未存储,就直接返回;如果已存在但主键不同,则报异常
    	// 如果存在,且主键相同,就修改学生姓名
        if (existingStudent != null && existingStudent.Id != student.Id)
        {
        throw new StudentAlreadyExistsException(newName);
        }

        student.ChangeName(newName);
    }
}

StudentManager是一中可控方式来进行业务操作的,非必要的核心业务不能放到领域方法中,这样子可以隔绝数据库与应用层的交互。

在该代码中提到了抛出异常StudentAlreadyExistsException,需要在***.Domain的Student文件夹中创建

 public class StudentAlreadyExistsException : BusinessException
 {
     public StudentAlreadyExistsException(string name)
     : base(DomainErrorCodes.StudentAlreadyExists)
     {
     	WithData("name", name);
     }
 }
  • BusinessException 是一个特殊的异常类型. 在需要时抛出领域相关异常是一个好的实践. ABP框架会自动处理它, 并且它也容易本地化. WithData(...) 方法提供额外的数据给异常对象

DomainErrorCodes.StudentAlreadyExists为唯一的错误编号,例如404,500等,该错误编号应放到Domain.Shared项目中,在**DomainErrorCodes中添加错误码

public static class ***DomainErrorCodes
{
	public const string StudentAlreadyExists = "StudentError:00001";
}

StudentError:00001是一个字符串,可以对他进行本地化处理,即在**Domain.Shared项目中的Localization/项目名称/en.json或zh-Hans.json中添加

"StudentError:00001":"学生姓名已存在"

IStudentRepository

在StudentManager中注入了IStudentRepository,因此我们需要定义它,在***.Domain中添加Student文件夹,并创建这个接口

// 
public interface IStudentRepository : IRepository<Student, Guid>
{
    // 创建通过查找学生姓名找到学生的实体
    Task<Student> FindByNameAsync(string name);
	// 通过获得学生的列表
    // 其中可以通过参数进行排序,查询,返回最大条数等
    Task<List<Student>> GetListAsync(
        int skipCount,
        int maxResultCount,
        string sorting,
        string filter = null
    );
}
  • IStudentRepository 扩展了标准 IRepository<Student, Guid> 接口, 所以所有的标准 repository 方法对于 IStudentRepository 都是可用的.
  • IRepository集成了IQueryable,其中会有很多接口,可直接使用。

数据库集成

DB Context

在EF Core中添加Student的声明(***.EntityFrameworkCore项目中的DbContext)

public DbSet<Student> Student{get;set;}

在该类的OnModelCreating方法中加入学生类的转换方法

builder.Entity<Student>(b =>
{
    b.ToTable(***Consts.DbTablePrefix + "Students",
        ***Consts.DbSchema);

    b.ConfigureByConvention();

    b.Property(x => x.Name)
        .IsRequired()
        .HasMaxLength(StudentConsts.MaxNameLength);

    b.HasIndex(x => x.Name);
});

创建数据库迁移

按照code first模式,需要创建一个表,因此在***.EntityFrameworkCore项目下打开Command命令行程序,输入一下命令,创建一个迁移类

dotnet ef migrations add Added_Students

如果为更新命令,则运行如下命令

dotnet ef database update

实现IStudentRepository

***.EntityFrameworkCore项目中创建一个Student文件夹,然后创建一个新类:EfCoreStudentRepository

public class EfCoreStudentRepository
: EfCoreRepository<***DbContext, Student, Guid>,
IStudentRepository
{
    public EfCoreStudentRepository(
    IDbContextProvider<***DbContext> dbContextProvider)
    : base(dbContextProvider)
    {
    }

    public async Task<Student> FindByNameAsync(string name)
    {
        var dbSet = await GetDbSetAsync();
        return await dbSet.FirstOrDefaultAsync(student => student.Name == name);
    }

    public async Task<List<Student>> GetListAsync(
    int skipCount,
    int maxResultCount,
    string sorting,
    string filter = null)
    {
        var dbSet = await GetDbSetAsync();
        	// WhereIf:第一个参数为true时执行查询
            return await dbSet
                .WhereIf(
                    !filter.IsNullOrWhiteSpace(),
                    student => student.Name.Contains(filter)
                )
                // 可以是 字段名,字段名+ASC/DESC
                .OrderBy(sorting)
                .Skip(skipCount)
                .Take(maxResultCount)
                .ToListAsync();
    }
}

应用服务层

IStudentAppService

首先创建应用服务的接口,在***.Application.Contracts项目中创建Student文件夹,然后再文件夹中创建IStudentAppService

public interface IStudentAppService : IApplicationService
{
    Task<StudentDto> GetAsync(Guid id);
    Task<PagedResultDto<StudentDto>> GetListAsync(GetStudentListDto input);
    Task<StudentDto> CreateAsync(CreateStudentDto input);
    Task UpdateAsync(Guid id, UpdateStudentDto input);
    Task DeleteAsync(Guid id);
}
  • IApplicationService 是一个常规接口, 所有应用服务都继承自它, 所以 ABP 框架可以识别它们.
  • Student 实体中定义标准方法用于CRUD操作.
  • PagedResultDto 是一个ABP框架中预定义的 DTO 类. 它拥有一个 Items 集合 和一个 TotalCount 属性, 用于返回分页结果.
  • 优先从 CreateAsync 方法返回 StudentDto (新创建的作者), 虽然在这个程序中没有这么做 - 这里只是展示一种不同用法.

StudentDto

该类用于展示(查询时将Model转换为StudentDto)

IStudentAppService同级创建StudentDto

public class StudentDto : EntityDto<Guid>
{
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public string Address { get; set; }
}
  • EntityDto<T> 只有一个类型为指定泛型参数的 Id 属性. 你可以自己创建 Id 属性, 而不是继承自 EntityDto<T>.

GetStudentListDto

用于检索条件的Dto,与StudentDto类同级,一下Dot相同

public class GetStudentListDto : PagedAndSortedResultRequestDto
{
	public string? Filter { get; set; }
}
  • Filter 用于搜索学生姓名. 当为 null 时会触发whereIf的第一个参数为false,则为查询全部.
  • PagedAndSortedResultRequestDto 具有标准分页和排序属性: int MaxResultCount, int SkipCountstring Sorting.

CreateStudentDto

新增时可以通过该类转换为Student实体类

 public class CreateStudentDto
 {
     // 通过使用标记进行验证
     [Required]
     // 设置的姓名最大长度
     [StringLength(StudentConsts.MaxNameLength)]
     public string Name { get; set; }
     [Required]
     public DateTime Birthday { get; set; }
     public string Address { get; set; }
}

UpdateStudentDto

更新时用的Dto

public class UpdateStudentDto
{
    [Required]
    [StringLength(StudentConsts.MaxNameLength)]
    public string Name { get; set; }
    [Required]
    public DateTime Birthday { get; set; }
    public string Address { get; set; }
}

StudentAppService

实现IStudentAppService接口,在***.Application项目的Student文件夹中创建一个新类StudentAppService

//检查权限(策略)的声明式方法, 用来给当前用户授权
[Studentize(***Permissions.Students.Default)]
public class StudentAppService : ApplicationService, IStudentAppService
{
	// 注入 IStudentRepository 和 StudentManager 以使用服务方法
    private readonly IStudentRepository _studentRepository;
    private readonly StudentManager _studentManager;

    public StudentAppService(
    IStudentRepository studentRepository,
    StudentManager studentManager)
    {
        _studentRepository = studentRepository;
        _studentManager = studentManager;
    }
	//这个方法根据 Id 获得 Student 实体, 使用 对象到对象映射 转换为 StudentDto. 这需要配置AutoMapper
    public async Task<StudentDto> GetAsync(Guid id)
    {
        var student = await _studentRepository.GetAsync(id);
        return ObjectMapper.Map<Student, StudentDto>(student);
    }
    /// <summary>
    /// 使用 IStudentRepository.GetListAsync 从数据库中获得分页的, 排序的和过滤的学生姓名列表. 这里只是说明如何创建自定义repository方法.
    /// 直接查询 StudentRepository, 得到学生的数量. 如果客户端发送了过滤条件, 会得到过滤后的作学生数量.
    /// 最后, 通过映射 Student 列表到 StudentDto 列表, 返回分页后的结果.
    /// </summary>
    public async Task<PagedResultDto<StudentDto>> GetListAsync(GetStudentListDto input)
    {
        if (input.Sorting.IsNullOrWhiteSpace())
        {
            input.Sorting = nameof(Student.Name);
        }

        var students = await _studentRepository.GetListAsync(
            input.SkipCount,
            input.MaxResultCount,
            input.Sorting,
            input.Filter
        );

        var totalCount = input.Filter == null
            ? await _studentRepository.CountAsync()
            : await _studentRepository.CountAsync(
                student => student.Name.Contains(input.Filter));

        return new PagedResultDto<StudentDto>(
            totalCount,
            ObjectMapper.Map<List<Student>, List<StudentDto>>(students)
        );
    }
    [Studentize(***Permissions.Students.Create)]
    public async Task<StudentDto> CreateAsync(CreateStudentDto input)
    {
        var student = await _studentManager.CreateAsync(
            input.Name,
            input.Birthday,
            input.Address
        );

        await _studentRepository.InsertAsync(student);

        return ObjectMapper.Map<Student, StudentDto>(student);
    }
    
    [Studentize(***Permissions.Students.Edit)]
    public async Task UpdateAsync(Guid id, UpdateStudentDto input)
    {
        var student = await _studentRepository.GetAsync(id);

        if (student.Name != input.Name)
        {
            await _studentManager.ChangeNameAsync(student, input.Name);
        }

        student.Birthday = input.Birthday;
        student.Address = input.Address;

        await _studentRepository.UpdateAsync(student);
    }
    
    [Studentize(***Permissions.Students.Delete)]
    public async Task DeleteAsync(Guid id)
    {
        await _studentRepository.DeleteAsync(id);
    }
}

权限定义

在上述的CreateAsync,UpdateAsync,DeleteAsync方法中都Studentize标签,用于为需要额外的权限。因此需要在***.Application.Contracts项目中的***Permissions类(Permissions文件夹)中添加Student的权限

public static class Student
{
    public const string Default = GroupName + ".Student";
    public const string Create = Default + ".Create";
    public const string Edit = Default + ".Edit";
    public const string Delete = Default + ".Delete";
}

打开同一项目中***PermissionDefinitionProvider,在Define方法结尾处增加一下代码

var studentsPermission = storeGroup.AddPermission(
    ***Permissions.Students.Default, L("Permission:Students"));

studentsPermission.AddChild(
    ***Permissions.Students.Create, L("Permission:Students.Create"));

studentsPermission.AddChild(
    ***Permissions.Students.Edit, L("Permission:Students.Edit"));

studentsPermission.AddChild(
    ***Permissions.Students.Delete, L("Permission:Students.Delete"));

***.Domain.Shared项目中的Localization/***添加本地化。

对象映射

StudentAppService 使用 ObjectMapperStudent 对象 转换为 StudentDto 对象. 所以, 我们需要在 AutoMapper 配置中定义映射.

***.Application项目中的***ApplicationAutoMapperProfile类中加入构造函数

CreateMap<Student, StudentDto>();

初始化时添加数据

***.Domain项目的**DataSeederContributor

  1. 添加两个变量

    private readonly IStudentRepository _studentRepository;
    private readonly StudentManager _studentManager;
    
  2. 构造方法修改为

    public ***DataSeederContributor(
    IStudentRepository studentRepository,
    StudentManager studentManager)
    { 
        _studentRepository = studentRepository;
        _studentManager = studentManager;
    }
    
  3. SeedAsync方法中添加

    if (await _studentRepository.GetCountAsync() <= 0)
    {
        await _studentRepository.InsertAsync(
            await _studentManager.CreateAsync(
            "张三",
            new DateTime(2020, 12, 21),
            "中国"
            )
        );
    }
    

执行初始化任务

运行**.DbMigrator控制台应用程序

界面

添加Razor组件

***.Blazor项目的Page文件夹中创建一个Students.razor的页面

@page "/students"
@using ***.Students
@using ***.Localization
@using Volo.Abp.AspNetCore.Components.Web
@inherits ***ComponentBase
@inject IStudentAppService StudentAppService
@inject AbpBlazorMessageLocalizerHelper<***Resource> LH
<Card>
    <CardHeader>
        <Row>
            <Column ColumnSize="ColumnSize.Is6">
                <h2>@L["Students"]</h2>
            </Column>
            <Column ColumnSize="ColumnSize.Is6">
                <Paragraph Alignment="TextAlignment.Right">
                    @if (CanCreateStudent)
                    {
                        <Button Color="Color.Primary"
                                Clicked="OpenCreateStudentModal">
                            @L["NewStudent"]
                        </Button>
                    }
                </Paragraph>
            </Column>
        </Row>
    </CardHeader>
    <CardBody>
        <DataGrid TItem="StudentDto"
                  Data="StudentList"
                  ReadData="OnDataGridReadAsync"
                  TotalItems="TotalCount"
                  ShowPager="true"
                  PageSize="PageSize">
            <DataGridColumns>
                <DataGridColumn Width="150px"
                                TItem="StudentDto"
                                Field="@nameof(StudentDto.Id)"
                                Sortable="false"
                                Caption="@L["Actions"]">
                    <DisplayTemplate>
                        <Dropdown>
                            <DropdownToggle Color="Color.Primary">
                                @L["Actions"]
                            </DropdownToggle>
                            <DropdownMenu>
                                @if (CanEditStudent)
                                {
                                    <DropdownItem Clicked="() => OpenEditStudentModal(context)">
                                        @L["Edit"]
                                    </DropdownItem>
                                }
                                @if (CanDeleteStudent)
                                {
                                    <DropdownItem Clicked="() => DeleteStudentAsync(context)">
                                        @L["Delete"]
                                    </DropdownItem>
                                }
                            </DropdownMenu>
                        </Dropdown>
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="StudentDto"
                                Field="@nameof(StudentDto.Name)"
                                Caption="@L["Name"]"></DataGridColumn>
                <DataGridColumn TItem="StudentDto"
                                Field="@nameof(StudentDto.Birthday)"
                                Caption="@L["Birthday"]">
                    <DisplayTemplate>
                        @context.Birthday.ToShortDateString()
                    </DisplayTemplate>
                </DataGridColumn>
            </DataGridColumns>
        </DataGrid>
    </CardBody>
</Card>

<Modal @ref="CreateStudentModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <Form>
            <ModalHeader>
                <ModalTitle>@L["NewStudent"]</ModalTitle>
                <CloseButton Clicked="CloseCreateStudentModal" />
            </ModalHeader>
            <ModalBody>
                <Validations @ref="@CreateValidationsRef" Model="@NewStudent" ValidateOnLoad="false">
                    <Validation MessageLocalizer="@LH.Localize">
                        <Field>
                            <FieldLabel>@L["Name"]</FieldLabel>
                            <TextEdit @bind-Text="@NewStudent.Name">
                                <Feedback>
                                    <ValidationError/>
                                </Feedback>
                            </TextEdit>
                        </Field>
                    </Validation>
                    <Field>
                        <FieldLabel>@L["Birthday"]</FieldLabel>
                        <DateEdit TValue="DateTime" @bind-Date="@NewStudent.Birthday"/>
                    </Field>
                    <Validation MessageLocalizer="@LH.Localize">
                        <Field>
                            <FieldLabel>@L["Address"]</FieldLabel>
                            <MemoEdit Rows="5" @bind-Text="@NewStudent.Address">
                                <Feedback>
                                    <ValidationError/>
                                </Feedback>
                            </MemoEdit>
                        </Field>
                    </Validation>
                </Validations>
            </ModalBody>
            <ModalFooter>
                <Button Color="Color.Secondary"
                        Clicked="CloseCreateStudentModal">
                    @L["Cancel"]
                </Button>
                <Button Color="Color.Primary"
                        Type="@ButtonType.Submit"
                        PreventDefaultOnSubmit="true"
                        Clicked="CreateStudentAsync">
                    @L["Save"]
                </Button>
            </ModalFooter>
        </Form>
    </ModalContent>
</Modal>

<Modal @ref="EditStudentModal">
    <ModalBackdrop />
    <ModalContent IsCentered="true">
        <Form>
            <ModalHeader>
                        <ModalTitle>@EditingStudent.Name</ModalTitle>
                        <CloseButton Clicked="CloseEditStudentModal" />
                    </ModalHeader>
            <ModalBody>
                <Validations @ref="@EditValidationsRef" Model="@EditingStudent" ValidateOnLoad="false">
                    <Validation MessageLocalizer="@LH.Localize">
                        <Field>
                            <FieldLabel>@L["Name"]</FieldLabel>
                            <TextEdit @bind-Text="@EditingStudent.Name">
                                <Feedback>
                                    <ValidationError/>
                                </Feedback>
                            </TextEdit>
                        </Field>
                    </Validation>
                    <Field>
                        <FieldLabel>@L["Birthday"]</FieldLabel>
                        <DateEdit TValue="DateTime" @bind-Date="@EditingStudent.Birthday"/>
                    </Field>
                    <Validation>
                        <Field>
                            <FieldLabel>@L["Address"]</FieldLabel>
                            <MemoEdit Rows="5" @bind-Text="@EditingStudent.Address">
                                <Feedback>
                                    <ValidationError/>
                                </Feedback>
                            </MemoEdit>
                        </Field>
                    </Validation>
                </Validations>
            </ModalBody>
            <ModalFooter>
                <Button Color="Color.Secondary"
                        Clicked="CloseEditStudentModal">
                    @L["Cancel"]
                </Button>
                <Button Color="Color.Primary"
                        Type="@ButtonType.Submit"
                        PreventDefaultOnSubmit="true"
                        Clicked="UpdateStudentAsync">
                    @L["Save"]
                </Button>
            </ModalFooter>
        </Form>
    </ModalContent>
</Modal>
  • 这些代码类似 Books.razor, 除了不继承自 AbpCrudPageBase, 它使用自己的实现.
  • 注入 IStudentAppService , 从UI使用服务器端的HTTP APIs . 我们可以直接注入应用服务接口并在 动态 C# HTTP API 客户端代理系统的帮助下像使用普通的方法一样使用它们, 动态 C# HTTP API 客户端代理系统会为我们调用REST API. 参考下面的 Students 类获得使用方法.
  • 注入 IStudentizationService 检查 权限.
  • 注入 IObjectMapper 进行 对象到对象映射.

添加Razor.cs类

在Student.razor同级创建Student.razor.cs文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ***.Students;
using ***.Permissions;
using Blazorise;
using Blazorise.DataGrid;
using Microsoft.AspNetCore.Studentization;
using Volo.Abp.Application.Dtos;

namespace ***.Blazor.Pages
{
    public partial class Students
    {
        private IReadOnlyList<StudentDto> StudentList { 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 CanCreateStudent { get; set; }
        private bool CanEditStudent { get; set; }
        private bool CanDeleteStudent { get; set; }

        private CreateStudentDto NewStudent { get; set; }

        private Guid EditingStudentId { get; set; }
        private UpdateStudentDto EditingStudent { get; set; }

        private Modal CreateStudentModal { get; set; }
        private Modal EditStudentModal { get; set; }

        private Validations CreateValidationsRef;

        private Validations EditValidationsRef;

        public Students()
        {
            NewStudent = new CreateStudentDto();
            EditingStudent = new UpdateStudentDto();
        }

        protected override async Task OnInitializedAsync()
        {
            await SetPermissionsAsync();
            await GetStudentsAsync();
        }

        private async Task SetPermissionsAsync()
        {
            CanCreateStudent = await StudentizationService                .IsGrantedAsync(***Permissions.Students.Create);
            CanEditStudent = await StudentizationService                .IsGrantedAsync(***Permissions.Students.Edit);
            CanDeleteStudent = await StudentizationService                .IsGrantedAsync(***Permissions.Students.Delete);
        }

        private async Task GetStudentsAsync()
        {
            var result = await StudentAppService.GetListAsync(
                new GetStudentListDto
                {
                    MaxResultCount = PageSize,
                    SkipCount = CurrentPage * PageSize,
                    Sorting = CurrentSorting
                }
            );

            StudentList = result.Items;
            TotalCount = (int)result.TotalCount;
        }

        private async Task OnDataGridReadAsync(DataGridReadDataEventArgs<StudentDto> e)
        {
            CurrentSorting = e.Columns
                .Where(c => c.Direction != SortDirection.None)
                .Select(c => c.Field + (c.Direction == SortDirection.Descending ? " DESC" : ""))
                .JoinAsString(",");
            CurrentPage = e.Page - 1;

            await GetStudentsAsync();

            await InvokeAsync(StateHasChanged);
        }

        private void OpenCreateStudentModal()
        {
            CreateValidationsRef.ClearAll();

            NewStudent = new CreateStudentDto();
            CreateStudentModal.Show();
        }

        private void CloseCreateStudentModal()
        {
            CreateStudentModal.Hide();
        }

        private void OpenEditStudentModal(StudentDto student)
        {
            EditValidationsRef.ClearAll();

            EditingStudentId = student.Id;
            EditingStudent = ObjectMapper.Map<StudentDto, UpdateStudentDto>(student);
            EditStudentModal.Show();
        }

        private async Task DeleteStudentAsync(StudentDto student)
        {
            var confirmMessage = L["StudentDeletionConfirmationMessage", student.Name];
            if (!await Message.Confirm(confirmMessage))
            {
                return;
            }
            await StudentAppService.DeleteAsync(student.Id);
            await GetStudentsAsync();
        }

        private void CloseEditStudentModal()
        {
            EditStudentModal.Hide();
        }

        private async Task CreateStudentAsync()
        {
            if (CreateValidationsRef.ValidateAll())
            {
                await StudentAppService.CreateAsync(NewStudent);
                await GetStudentsAsync();
                CreateStudentModal.Hide();
            }
        }

        private async Task UpdateStudentAsync()
        {
            if (EditValidationsRef.ValidateAll())
            {
                await StudentAppService.UpdateAsync(EditingStudentId, EditingStudent);
                await GetStudentsAsync();
                EditStudentModal.Hide();
            }
        }
    }
}

添加映射

***.Blazor 项目中的 ***AutoMapperProfile.cs中加入

CreateMap<StudentDto, UpdateStudentDto>();

加入主菜单

***.Blozor项目的Menus文件中的***.MenuContributor.csConfigureMainMenuAsync方法结尾加入

if (await context.IsGrantedAsync(***Permissions.Students.Default))
{
    ***Menu.AddItem(new ApplicationMenuItem(
        "Student",
        l["Menu:Students"],
        // 对应Razor组件的名称
        url: "/students"
    ));
}