ABP Framework

发布时间 2023-07-13 17:41:21作者: 摧残一生

ABP Framework

模板运行

安装并下载模板

安装ABP CLI,第一步是安装ABP CLI

dotnet tool install -g Volo.Abp.Cli

然后使用 abp new 命令在空文件夹中创建新解决方案:

// 项目名称为AbpBlazor.BookStore
// UI模板使用BlazorServer模式,默认为MVC
// 数据库使用MySql(EF framework)
// 添加了移动端(react-native)
abp new AbpBlazor.BookStore -u blazor-server -dbms MySQL -m react-native -csf

修改数据库

AbpBlazor.BookStore.DbMigrator和AbpBlazor.BookStore.Blazor中的appsettings.json

将数据库名称和数据库密码修改正确

初始化数据库

由于Abp使用了EF Framework,可直接通过code first进行数据库生成。

选择AbpBlazor.BookStore.DbMigrator项目并设置为启动项目

image-20230705181442702

点击F5或者Ctrl+F5进行数据库的更新,更新完毕系统会自动推出运行。

运行ABP模板

将AbpBlazor.BookStore.Blazor设置为启动项,然后运行。

运行成功后浏览器会出现下图页面:

模板各项目

分层图

各层依赖关系

.Domain.Shared 项目

项目包含常量,枚举和其他对象,这些对象实际上是领域层的一部分,但是解决方案中所有的层/项目中都会使用到.

例如 BookType 枚举和 BookConsts 类 (可能是 Book 实体用到的常数字段,像MaxNameLength)都适合放在这个项目中.

  • 该项目不依赖解决方案中的其他项目. 其他项目直接或间接依赖该项目

.Domain 项目

解决方案的领域层. 它主要包含 实体, 集合根, 领域服务, 值类型, 仓储接口 和解决方案的其他领域对象.

例如 Book 实体和 IBookRepository 接口都适合放在这个项目中.

  • 它依赖 .Domain.Shared 项目,因为项目中会用到它的一些常量,枚举和定义其他对象.

.Application.Contracts 项目

项目主要包含 应用服务 interfaces 和应用层的 数据传输对象 (DTO). 它用于分离应用层的接口和实现. 这种方式可以将接口项目做为约定包共享给客户端.

例如 IBookAppService 接口和 BookCreationDto 类都适合放在这个项目中.

  • 它依赖 .Domain.Shared 因为它可能会在应用接口和DTO中使用常量,枚举和其他的共享对象.

.Application 项目

项目包含 .Application.Contracts 项目的 应用服务 接口实现.

例如 BookAppService 类适合放在这个项目中.

  • 它依赖 .Application.Contracts 项目, 因为它需要实现接口与使用DTO.
  • 它依赖 .Domain 项目,因为它需要使用领域对象(实体,仓储接口等)执行应用程序逻辑.

.EntityFrameworkCore 项目

这是集成EF Core的项目. 它定义了 DbContext 并实现 .Domain 项目中定义的仓储接口.

  • 它依赖 .Domain 项目,因为它需要引用实体和仓储接口.

只有在你使用了EF Core做为数据库提供程序时,此项目才会可用. 如果选择的是其他数据库提供程序那么项目的名称会改变

.EntityFrameworkCore.DbMigrations 项目

包含解决方案的EF Core数据库迁移. 它有独立的 DbContext 来专门管理迁移.

ABP是一个模块化的框架,理想的设计是让每个模块都有自己的 DbContext 类. 这时用于迁移的 DbContext 就会发挥作用. 它将所有的 DbContext 配置统一到单个模型中以维护单个数据库的模式. 对于更高级的场景,可以程序可以拥有多个数据库(每个数据库有一个或多个模块表)和多个迁移DbContext(每个都维护不同的数据库模式)

需要注意,迁移 DbContext 仅用于数据库迁移,而不在运行时使用.

  • 它依赖 .EntityFrameworkCore 项目,因为它重用了应用程序的 DbContext 配置 .

只有在你使用了EF Core做为数据库提供程序时,此项目才会可用. 参阅Entity Framework Core迁移指南了解这个项目的详细信息.

.DbMigrator 项目

这是一个控制台应用程序,它简化了在开发和生产环境执行数据库迁移的操作.当你使用它时;

  • 必要时创建数据库(没有数据库时).
  • 应用未迁移的数据库迁移.
  • 初始化种子数据(当你需要时).

这个项目有自己的 appsettings.json 文件. 所以如果要更改数据库连接字符串,请记得也要更改此文件.

初始化种子数据很重要,ABP具有模块化的种子数据基础设施. 种子数据的更多信息,请参阅文档.

虽然创建数据库和应用迁移似乎只对关系数据库有用,但即使你选择NoSQL数据库提供程序(如MongoDB),也会生成此项目. 这时,它会为应用程序提供必要的初始数据.

  • 它依赖 .EntityFrameworkCore.DbMigrations 项目 (针对EF Core),因为它需要访问迁移文件.
  • 它依赖 .Application.Contracts 项目,因为它需要访问权限定义在初始化种子数据时为管理员用户赋予所有权限.

.HttpApi 项目

用于定义API控制器.

大多数情况下,你不需要手动定义API控制器,因为ABP的动态API功能会根据你的应用层自动创建API控制器. 但是,如果你需要编写API控制器,那么它是最合适的地方.

  • 它依赖 .Application.Contracts 项目,因为它需要注入应用服务接口.

.HttpApi.Client 项目

定义C#客户端代理使用解决方案的HTTP API项目. 可以将上编辑共享给第三方客户端,使其轻松的在DotNet应用程序中使用你的HTTP API(其他类型的应用程序可以手动或使用其平台的工具来使用你的API).

ABP有动态 C# API 客户端功能,所以大多数情况下你不需要手动的创建C#客户端代理.

.HttpApi.Client.ConsoleTestApp 项目是一个用于演示客户端代理用法的控制台应用程序.

  • 它依赖 .Application.Contracts 项目,因为它需要使用应用服务接口和DTO.

如果你不需要为API创建动态C#客户端代理,可以删除此项目和依赖项

.Web 项目

包含应用程序的用户界面(UI).如果使用ASP.NET Core MVC UI, 它包括Razor页面,javascript文件,样式文件,图片等...

包含应用程序主要的 appsettings.json 配置文件,用于配置数据库连接字符串和应用程序的其他配置

  • 依赖 .HttpApi 项目,因为UI层需要使用解决方案的API和应用服务接口.

如果查看 .Web.csproj 源码, 你会看到对 .Application.EntityFrameworkCore.DbMigrations 项目的引用.

在编写UI层时实际上不需要这些引用. 因为UI层通常不依赖于EF Core或应用层的实现. 这个启动模板已经为分层部署做好了准备,API层托管在不同与UI层的服务器中.

但是如果你不选择 --tiered 选项, .Web项目会有这些引用,以便能够将Web,Api和应用层托管在单个应用程序站点.

你可以在表示层中使用领域实体和仓储,但是根据DDD的理论,这被认为是一种不好的做法.

Test 项目

解决方案有多个测试项目,每一层都会有一个:

  • .Domain.Tests 用于测试领域层.
  • .Application.Tests 用于测试应用层.
  • .EntityFrameworkCore.Tests 用于测试EF Core配置与自定义仓储.
  • .Web.Tests 用于测试UI(适用于ASP.NET Core MVC UI).
  • .TestBase 所有测试项目的基础(共享)项目.

此外, .HttpApi.Client.ConsoleTestApp 是一个控制台应用程序(不是自动化测试项目),它用于演示.Net应用程序中HTTP API的用法.

测试项目是用于做集成测试的:

  • 它完全集成到ABP框架和应用程序的所有服务.
  • 如果数据库提供程序是EF Core,测试项目会使用SQLite内存数据库,如果是MongoDB,它使用Mongo2Go库.
  • 授权被禁用,任何的应用服务都可以在测试中轻松调用.

你依然可以编写单元测试,只不过它很难写(因为你需要准备mock/fake对象),但它的运行速度更快(因为只测试单个类并跳过所有初始化过程).

ABP功能添加流程

创建服务端代码

服务端实体部分(M层)

服务端主要包括了实体类、枚举类、实体与数据库的映射、迁移数据库、添加模块初始数据、更新数据库

创建实体

在***.Domain中创建一个模块命名的文件夹(命名空间),并创建该模块的实体类

该实体类继承于AuditedAggregateRoot

说明:

  1. ABP提供了两个基类: AggregateRootEntity,其中Aggregate Root是默认驱动设计,可以直接查询和处理根实体,而AuditedAggregateRoot是在Root的基础上添加了审计属性(创建时间,创建人,最后更新时间等)
  2. Guid是主键类型
创建枚举

在***.Domain.Shared中创建一个模块命名的文件夹(命名空间),并创建该模块的枚举类(如果没有则不用)

实体与程序建立关系

通过EFCore 将程序需实体建立关系

在***.EntityFrameworkCore项目中的.DbContext类中添加DbSet属性

public class ***StoreDbContext : AbpDbContext<BookStoreDbContext>
{
    //添加的实体类
    public DbSet<实体> 实体s { get; set; }
}
实体与数据库建立关系

在***.EntityFrameworkCore项目中的.DbContext类的OnModelCreating方法中添加实体的映射代码

 protected override void OnModelCreating(ModelBuilder builder)
 {
     //创建一个人实体类
    builder.Entity<实体>(b =>
    {
        //命名规范,其中***StoreConsts为用于表的架构和表前缀的常量值,为了统一
        b.ToTable(***StoreConsts.DbTablePrefix + "Books",
        ***StoreConsts.DbSchema);
        // 配置/映射集成属性
        b.ConfigureByConvention(); 
        b.Property(x => x.Name).IsRequired().HasMaxLength(128);
    });
}
添加数据迁移类

执行命令前需生成解决方案来确保实体类被调用

在***.EntityFrameworkCore项目下通过命令行窗口运行一下命令

// 用于创建实体类的表---code first模式
dotnet ef migrations add Created_实体类_Entity

成功后会在***.EntityFrameworkCore的Migrations中新增一个类

向表里添加数据

*.Domain 项目下创建 IDataSeedContributor 的派生类

using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace 命名空间
{
    public class ***StoreDataSeederContributor
        : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<实体, Guid> _repository;
        public BookStoreDataSeederContributor(IRepository<实体, Guid> repository)
        {
            _repository = repository;
        }
        public async Task SeedAsync(DataSeedContext context)
        {
            //如果该数据库中没有数据,则创建一条
            if (await _repository.GetCountAsync() <= 0)
            {
                // 插入一条数据
                await _repository.InsertAsync(
                    new 实体
                    {
                        属性1 = "属性1",
                        属性2 = 枚举类.Dystopia                      
                    },
                    autoSave: true
                );
            }
        }
    }
}
更新数据库

运行***.DbMigrator

应用程序部分的创建(VM层)

主要用于创建DTO和应用服务的实现

创建DTO

在***Application.Contracts 项目中创建 实体文件夹(命名空间), 并在其中添加名为 实体 的DTO类,基类为AuditedEntityDto<Guid>

说明:

  1. DOT是在表示层和应用层之间传递数据
  2. AuditedEntityDto中也有审计属性
AutoMapper映射

该功能的作用是让实体和DTO类相互自动转换,在***.Application项目的.ApplicationAutoMapperProfile类中定义映射

public class ***ApplicationAutoMapperProfile : Profile
{
    public ***ApplicationAutoMapperProfile()
    {
        // 此DTO暂定用于列表显示
        CreateMap<实体, 实体Dto>();
    }
}

所有实体和DTO映射都要定义

例如创建一个用于传递创建/编辑的DTO

public class CreateUpdateBookDto
{
    //必填
    [Required]
    //长度最大128
    [StringLength(128)]
    public string Name { get; set; }

    [Required]
	// 枚举类型
    public ***Type Type { get; set; } = ***Type.Undefined;

    [Required]
    // 事件类型
    [DataType(DataType.Date)]
    public DateTime PublishDate { get; set; } = DateTime.Now;

    [Required]
    public float Price { get; set; }
}

不要忘记在***.ApplicationAutoMapperProfile类中增加映射。

创建应用服务接口

在***.Application.Contracts项目中创建模块的命名空间,然后添加:I实体AppService类

public interface I实体AppService :
        ICrudAppService< //Defines CRUD methods
            实体Dto, 				   			 //用于展示
            Guid, 							  //主键
            PagedAndSortedResultRequestDto,   //用于分页和排序
            CreateUpdate实体Dto> 				//用于添加和修改 
            {}
  • 框架定义应用程序服务的接口不是必需的. 但是,它被建议作为最佳实践.
  • ICrudAppService定义了常见的CRUD方法:GetAsync,GetListAsync,CreateAsync,UpdateAsyncDeleteAsync. 从这个接口扩展不是必需的,你可以从空的IApplicationService接口继承并手动定义自己的方法(将在下一部分中完成).
  • ICrudAppService有一些变体, 你可以在每个方法中使用单独的DTO(例如使用不同的DTO进行创建和更新).
应用接口的实现

在***.Application项目中创建模块文件夹,并添加接口的实现

public class 实体AppService :
        CrudAppService<
            实体类, 
            实体Dto, //用于展示
            Guid, 
            PagedAndSortedResultRequestDto, 
            CreateUpdateBookDto>, 
        I实体AppService //实现接口
    {
        public 实体AppService(IRepository<实体, Guid> repository)
            : base(repository)
        {

        }
    }
添加页面
本地化

在.Domain.Shared项目的Localization下有本地换配置文件,前台部分的变量都需要在该处进行设置---页面中L后面的变量

创建页面

.Blazor项目下的Pages文件夹中创建模块,并创建一个razor组件,命名为“实体.razor”。

添加主菜单

Blazor项目中的***MenuContributor类,在 ConfigureMainMenuAsync 方法的底部添加如下代码:

context.Menu.AddItem(
    new ApplicationMenuItem(
        "创建菜单",
        l["Menu:创建菜单"],//菜单名称
        icon: "fa fa-book"
    ).AddItem(
        new ApplicationMenuItem(
            "左侧菜单",
            l[""],//名称
            url: "/实体"   //url
        )
    )
);
添加列表

在创建的“实体.razor”中,修改代码

@page "/实体"  								@*实体页面引用*@
@using Volo.Abp.Application.Dtos			 @*基类引用*@	
@using Acme.BookStore.实体                    @*实体类(***.Domain项目中的model)*@
@using Acme.BookStore.Localization			 @*本地化的基础引用*@
@using Microsoft.Extensions.Localization     @*本地化的基础引用*@
@inject IStringLocalizer<BookStoreResource> L   @*依赖注入,作为对象L的声明,可以使用本地化,*.Domain.Shared项目中Localization下的资源*@
@inherits AbpCrudPageBase<I实体AppService, 实体Dto, Guid, PagedAndSortedResultRequestDto, CreateUpdate实体Dto>
   @*内部使用的相关类*@
<Card>
    <CardHeader>  
        @*标题,后期会加入新建按钮,资源名称在Domain.Shared项目中Localization中*@
        <h2>@L["资源名称"]</h2>
    </CardHeader>
    <CardBody> @*卡片的主体部分 DataGrid标签,所需实体类为展示的’实体Dto‘,数据类型为Entities,向后台发送OnDataGridReadAsync读取数据, TotalCount和PaeSize为AbpCrudPageBase中的预定义数据*@
        <DataGrid TItem="实体Dto"
                  Data="Entities"
                  ReadData="OnDataGridReadAsync"
                  TotalItems="TotalCount"
                  ShowPager="true"
                  PageSize="PageSize">
            <DataGridColumns>
            	@* L["Name"]如果资源中没有Name,则会显示Name;使用DisplayTemplate标签来限定显示的内容模板,例如枚举类型,时间显示方式*@
                <DataGridColumn TItem="实体Dto"
                                Field="@nameof(实体Dto.Name)"
                                Caption="@L["Name"]"></DataGridColumn>
                <DataGridColumn TItem="实体Dto"
                                Field="@nameof(实体Dto.Type)"
                                Caption="@L["Type"]">
                    <DisplayTemplate>
                        @L[$"Enum:实体Type.{Enum.GetName(context.Type)}"]
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="实体Dto"
                                Field="@nameof(实体Dto.PublishDate)"
                                Caption="@L["PublishDate"]">
                    <DisplayTemplate>
                        @context.PublishDate.ToShortDateString()
                    </DisplayTemplate>
                </DataGridColumn>
                <DataGridColumn TItem="实体Dto"
                                Field="@nameof(实体Dto.Price)"
                                Caption="@L["Price"]">
                </DataGridColumn>
                <DataGridColumn TItem="实体Dto"
                                Field="@nameof(实体Dto.CreationTime)"
                                Caption="@L["CreationTime"]">
                    <DisplayTemplate>
                        @context.CreationTime.ToLongDateString()
                    </DisplayTemplate>
                </DataGridColumn>
            </DataGridColumns>
        </DataGrid>
    </CardBody>
</Card>
在页面中增加-增删改
实体创建的实现
  1. 页面上新增“New按钮”

    在“实体.razor”中修改标签

    <CardHeader>
        @* 创建一个按钮,并添加OpenCreateModalAsync事件 *@
        <Row Class="justify-content-between">
            <Column ColumnSize="ColumnSize.IsAuto">
                <h2>@L["实体名称"]</h2>
            </Column>
            <Column ColumnSize="ColumnSize.IsAuto">
                <Button Color="Color.Primary"
                        Clicked="OpenCreateModalAsync">@L["New"]</Button>
            </Column>
        </Row>
    </CardHeader>
    
  2. 创建新建的弹框

    @* 与后台的CreateModal变量关联(Modal类) *@
    <Modal @ref="@CreateModal">
        <ModalBackdrop />
        @* 是否剧中 *@
        <ModalContent IsCentered="true">
            <Form>
                <ModalHeader>
                    @* 弹框名称 *@
                    <ModalTitle>@L["New"]</ModalTitle>
                    @* 触发的事件,取消新增 *@
                    <CloseButton Clicked="CloseCreateModalAsync"/>
                </ModalHeader>
                <ModalBody>
                    <Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
                        <Validation MessageLocalizer="@LH.Localize">
                            @* 添加名称 *@
                            <Field>
                                <FieldLabel>@L["Name"]</FieldLabel>
                                @* 输入框绑定的是NewEntity实体类的Name属性 *@
                                <TextEdit @bind-Text="@NewEntity.Name">
                                    @* 不能为空 *@
                                    <Feedback>
                                        <ValidationError/>
                                    </Feedback>
                                </TextEdit>
                            </Field>
                        </Validation>
                        @* 枚举类型 *@
                        <Field>
                            <FieldLabel>@L["Type"]</FieldLabel>
                            <Select TValue="实体枚举类(对应的枚举类)" @bind-SelectedValue="@NewEntity.Type">
                                @foreach (int typeValue in Enum.GetValues(typeof(实体枚举类)))
                                {
                                    <SelectItem TValue="实体枚举类" Value="@((实体枚举类) typeValue)">
                                        @L[$"Enum:实体枚举类.{Enum.GetName((实体枚举类)typeValue)}"]
                                    </SelectItem>
                                }
                            </Select>
                        </Field>
                        @* 时间选择 *@
                        <Field>
                            <FieldLabel>@L["PublishDate"]</FieldLabel>
                            <DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
                        </Field>
                        @* 只能输入数字和小数 *@
                        <Field>
                            <FieldLabel>@L["Price"]</FieldLabel>
                            <NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
                        </Field>
                    </Validations>
                </ModalBody>
                <ModalFooter>
                    <Button Color="Color.Secondary"
                            Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
                    @* 保存按钮 *@
                    <Button Color="Color.Primary"
                            Type="@ButtonType.Submit"
                            PreventDefaultOnSubmit="true"
                            Clicked="CreateEntityAsync">@L["Save"]</Button>
                </ModalFooter>
            </Form>
        </ModalContent>
    </Modal>
    
  3. 后台代码

    AbpCrudPageBase基类已全部实现

更新功能的代码
  1. 新增编辑按钮

    在每一列添加下拉菜单,下拉中展示Edit按钮。

    DataGridColumns 中添加 DataGridEntityActionsColumn标签,第一个

    DataGridColumn标签上方添加:

    @* 按钮触发OpenEditModalAsync方法 *@
    <DataGridEntityActionsColumn TItem="实体Dto" @ref="@EntityActionsColumn">
        <DisplayTemplate>
            <EntityActions TItem="实体to" EntityActionsColumn="@EntityActionsColumn">
                <EntityAction TItem="实体Dto"
                              Text="@L["Edit"]"
                              Clicked="() => OpenEditModalAsync(context)" />
            </EntityActions>
        </DisplayTemplate>
    </DataGridEntityActionsColumn>
    
  2. 增加编辑对话框

    <Modal @ref="@EditModal">
        <ModalBackdrop />
        <ModalContent IsCentered="true">
            <Form>
                <ModalHeader>
                    <ModalTitle>@EditingEntity.Name</ModalTitle>
                    <CloseButton Clicked="CloseEditModalAsync"/>
                </ModalHeader>
                <ModalBody>
                    <Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
                        <Validation MessageLocalizer="@LH.Localize">
                            <Field>
                                <FieldLabel>@L["Name"]</FieldLabel>
                                <TextEdit @bind-Text="@EditingEntity.Name">
                                    <Feedback>
                                        <ValidationError/>
                                    </Feedback>
                                </TextEdit>
                            </Field>
                        </Validation>
                        <Field>
                            <FieldLabel>@L["Type"]</FieldLabel>
                            <Select TValue="实体Type" @bind-SelectedValue="@EditingEntity.Type">
                                @foreach (int typeValue in Enum.GetValues(typeof(实体Type)))
                                {
                                    <SelectItem TValue="实体Type" Value="@((实体Type) typeValue)">
                                        @L[$"Enum:实体Type.{Enum.GetName((实体Type)typeValue)}"]
                                    </SelectItem>
                                }
                            </Select>
                        </Field>
                        <Field>
                            <FieldLabel>@L["PublishDate"]</FieldLabel>
                            <DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
                        </Field>
                        <Field>
                            <FieldLabel>@L["Price"]</FieldLabel>
                            <NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
                        </Field>
                    </Validations>
                </ModalBody>
                <ModalFooter>
                    <Button Color="Color.Secondary"
                            Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
                    <Button Color="Color.Primary"
                            Type="@ButtonType.Submit"
                            PreventDefaultOnSubmit="true"
                            Clicked="UpdateEntityAsync">@L["Save"]</Button>
                </ModalFooter>
            </Form>
        </ModalContent>
    </Modal>
    
  3. 定义CreateUpdate实体Dto类映射

    在AutoMapper中添加实体DtoCreateUpdate实体Dto类的映射

    .Blazor 项目中的 BookStoreBlazorAutoMapperProfile , 替换成以下内容:

    namespace ***.Blazor
    {
        public class ***BlazorAutoMapperProfile : Profile
        {
            public ***BlazorAutoMapperProfile()
            {
                CreateMap<实体Dto, CreateUpdate实体Dto>();
            }
        }
    }
    
  4. 使用内部自带的后台方法

删除功能的代码
  1. 添加删除按钮

    在编辑的下面新增一个EntityActions标签

    <EntityAction TItem="实体Dto"
                  Text="@L["Delete"]"
                  Clicked="() => DeleteEntityAsync(context)"
                  ConfirmationMessage="() => GetDeleteConfirmationMessage(context)" />
    
    • DeleteEntityAsync 定义在基类中. 通过向服务器发起请求删除实体.
    • ConfirmationMessage 执行操作前显示确认消息的回调函数.
    • GetDeleteConfirmationMessage 定义在基类中. 你可以覆写这个方法 (或传递其它值给 ConfirmationMessage 参数) 以定制本地化消息.

授权部分

定义权限

设置好定义的权限,默认为使用实体名称作为权限名,他们通常是一个常量

打开***.Application.Contracts 项目中的 ***Permissions 类 (位于 Permissions 文件夹) 并替换为以下代码:

namespace ***.Permissions
{
    public static class ***Permissions
    {
        public const string GroupName = "一般为默认,可修改";

        //创建了四个常量,分别是显示,新建,修改和删除
        public static class 实体
        {
            public const string Default = GroupName + ".实体";
            public const string Create = Default + ".Create";
            public const string Edit = Default + ".Edit";
            public const string Delete = Default + ".Delete";
        }
    }
}
定义权限

打开***.Application.Contracts 项目中的 ***PermissionDefinitionProvider类 (位于Permissions` 文件夹) 并替换为以下代码:

using ***..Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;

namespace ***.Permissions
{
    // 将设置的权限常量进行权限定义
    public class ***PermissionDefinitionProvider : PermissionDefinitionProvider
    {
        // 声明权限
        public override void Define(IPermissionDefinitionContext context)
        {
            // 添加一个权限组
            var group = context.AddGroup(***Permissions.GroupName, L("Permission:实体"));

            // 定义实体的权限,该权限为父权限
            var permission = group.AddPermission(***Permissions.实体.Default, L("Permission:实体"));
            // 添加子权限内容
            permission.AddChild(***Permissions.实体.Create, L("Permission:实体.Create"));
            permission.AddChild(***Permissions.实体.Edit, L("Permission:实体.Edit"));
            permission.AddChild(***Permissions.实体.Delete, L("Permission:实体.Delete"));
        }

        private static LocalizableString L(string name)
        {
            // 创建本地化资源
            return LocalizableString.Create<***Resource>(name);
        }
    }
}
本地换权限名称

上述代码中Permission:实体等所有Permission:的都需要在本地化中创建相关的显示信息,新增内容如下:

"Permission:权限组": "可自定义内容",
"Permission:实体`": "可自定义内容",
"Permission:实体`.Create": "Creating...",
"Permission:实体`.Edit": "Editing..",
"Permission:实体`.Delete": "Deleting.."
授权

打开BookAppService 类, 设置策略名称为上面定义的权限名称.

using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore.Books
{
    public class ***AppService :
        CrudAppService<
            实体, 
            实体Dto, 
            Guid, 
            PagedAndSortedResultRequestDto, 
            CreateUpdate实体Dto>, 
        I实体AppService 
    {
        public 实体AppService(IRepository<实体, Guid> repository)
            : base(repository)
        {
            GetPolicyName = ***Permissions.实体.Default;
            GetListPolicyName = ***Permissions.实体.Default;
            CreatePolicyName = ***Permissions.实体.Create;
            UpdatePolicyName = ***Permissions.实体.Edit;
            DeletePolicyName = ***Permissions.实体.Delete;
        }
    }
}
权限授权页面

使用admin登录后,可在*管理 -> Identity -> 角色* 页面中设置相关权限

添加集成测试

***.Application.Tests 项目中添加一个 实体AppService_Tests

using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;

namespace 命名空间
{ 
    public class 实体AppService_Tests : ***ApplicationTestBase
    {
        private readonly I实体AppService _appService;

        public 实体AppService_Tests()
        {
            // 注入service
            _appService = GetRequiredService<I实体AppService>();
        }

        [Fact]
        public async Task Should_Get_List()
        {
            // 获得列表
            var result = await _appService.GetListAsync(
                new PagedAndSortedResultRequestDto()
            );

            // 通过注解判断是否正确
            result.TotalCount.ShouldBeGreaterThan(0);
            result.Items.ShouldContain(b => b.Name == "名字");
        }
    }
}