Abp 代码生成器使用说明

Abp 代码生成器使用说明

介绍

在使用abp框架进行项目开发的时候,开始使用的是52abp的代码生成器,但是在使用的过程中,总有一些地方感觉不舒服,也许是出于程序员的强迫症吧。最终,我决定自己写一个简单的代码生成器使用。所以,也就有了这么一篇文章。

概述

我们先看看界面
在这里插入图片描述

只有这一个界面,因为只是为了生成后台的代码。从图中可以看出,我只是生成了领域服务层EntityFrameworkCore层应用层的相关代码,权限部分可以选择生成,也可用后期用到在生成。这里需要编辑的内容有:

1、实体类主键的类型,如Guidlong等;

2、实体类的中文名称,可在生成类的注释中使用,否则使用类名称;

3、选择在EditListDTO中显示的字段,勾选则生成此字段;

4、字段中文名称,实体类中默认有的会带出来,必填也是如此;

5、如果实体类中有定义字段长度,此处会自动带出,可在此修改,为 0 则不会在DTO中生成。

具体使用方式

创建一个实体类

我们以BaseCode为例子来说明。

首先,我们在领域层新建一个BaseCode文件夹,然后在此文件夹下创建BaseCode.cs类。在此类中添加相应的属性信息,具体代码如下:

public class BaseCode :Entity<Guid>,IPassivable
{
    [Required]
    [StringLength(maximumLength:50, ErrorMessage = "编码不能超过50字符")]
    [DisplayName("编码")]
    public string Code { get; set; }
    [Required]
    [StringLength(50,MinimumLength = 10,ErrorMessage ="编码名称不能超过50字符")]
    [DisplayName("编码名称")]
    public string CodeName { get; set; }
    [StringLength(200)]
    [DisplayName("描述")]
    public string Desc { get; set; }
    [Required]
    [DisplayName("是否激活")]
    public bool IsActive { get ; set; }
}

关于实体类的具体定义规则,可参考abp框架的相关说明信息

此示例代码中添加RequiredStringLength特性,主要是为了测试界面数据的绑定,其实此处可用不定义,而是放在生成的配置类中定义。

但是对于DisplayName特性,我建议您定义,因为它不仅仅是在界面上绑定显示,可用在DTO中生成相应的注释;同样的,它还会生成相应的数据表字段说明。所以此处强烈建议您给出此字段的说明。

EF层生成的配置类

让我们来看看生成的代码:

/// <summary>
/// 基础编码 EF配置,可完成必填,注释等内容的处理
/// </summary>
public class BaseCodeCfg : IEntityTypeConfiguration<BaseCodeBaseCode>
{
    /// <summary>
    /// 具体配置方法
    /// </summary>
    /// <param name="builder"></param>
    public void Configure(EntityTypeBuilder<BaseCodeBaseCode>builder)
    {
        // ABP 为数据库中架构名称,可自行修改,sql server 的默认架构是dbo
        builder.ToTable("BaseCode", "ABP").HasComment("基础编码的相关信息") ;

        builder.Property(x => x.Id).HasComment(AbpConsts.CommentPrimaryKeyGuid);

        builder.Property(x => x.Code).HasComment("编码");
        builder.Property(x => x.CodeName).HasComment("编码名称");
        builder.Property(x => x.Desc).HasComment("描述");
        builder.Property(x => x.IsActive).HasComment("是否激活");
    }
}

其实个人更加的建议,将RequiredStringLength等特性的定义放在此处,同样可在此处定义很多其他的约束,比如默认值等。

注意:生成的代码中的AbpConsts类是在领域层中生成的。

生成的项目结构如图:
在这里插入图片描述
为了使用生成配置类,我们需要在xxDbContext类中添加相关内容,此类可用在EF层的EntityFrameworkCore文件夹下找到。

添加的内容主要是两块,一个是实体类数据集属性,另一个是重写OnModelCreating方法。代码分别如下:

/* Define a DbSet for each entity of the application */
public DbSet<BaseCode.BaseCode> BaseCodes;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfiguration<BaseCode.BaseCode>(new BaseCodeCfg());

    base.OnModelCreating(modelBuilder);
}

这样在生成数据库时,相应的表字段注释也会随之生成。具体生成方式,可参考代码生成器中生成的readme.md说明文档。

让我们看下生成的数据库效果:

在这里插入图片描述

领域层(XX.Core)生成的类

在创建好的实体类上右击,在弹出的菜单中选择【Abp 代码生成器】后即可看到本文开头展示的界面。生成此层的相关代码,必须勾选【领域服务】复选框。

生成后,会在实体类的同层创建一个DomainService文件夹,并在其下生成领域服务接口和领域服务实现类。在领域层的根目录下生成一个AbpConsts.cs类和XXXDomainServiceBase.cs类。最后要说的是,还会在此层的根目录下生成一个Readme.md的说明文档。

这里简单展示下XXXDomainServiceBase.cs类的代码:

/// <summary>
/// 自定义的基础领域服务,继承自abp框架的领域服务
/// </summary>
public abstract class DemoDomainServiceBase : DomainService
{
    /// <summary>
    /// 构造函数
    /// </summary>
    protected DemoDomainServiceBase()
    {
        LocalizationSourceName = DemoConsts.LocalizationSourceName;
    }
    /* Add your common members for all your domain services. */
    /*在领域服务中添加你的自定义公共方法*/
    
}

项目结构可参考权限部分给的图片

应用层(XX.Application)生成的类

相对来说,这一层中生成的类最多。

首先我们来看看在应用层根目录下生成的XXXCrudAppServiceBase.cs类。其生成的代码如下:

/// <summary>
/// 应用层增删改查的基类,自定义的业务类都可从此类继承
/// </summary>
public abstract class DemoCrudAppServiceBase<TEntity, TEntityDto, TPrimaryKey, TGetAllInput,    TCreateInput, TUpdateInput> : AsyncCrudAppService<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput, EntityDto<TPrimaryKey>>
    where TEntity : class, IEntity<TPrimaryKey>
    where TEntityDto : IEntityDto<TPrimaryKey>
    where TUpdateInput : IEntityDto<TPrimaryKey>
{
    /// <summary>
    /// 租户管理
    /// </summary>
    public TenantManager TenantManager { get; set; }

    /// <summary>
    /// 用户管理
    /// </summary>
    public UserManager UserManager { get; set; }

    ///<summary>
    /// 构造函数
    /// </summary>
    public DemoCrudAppServiceBase(IRepository<TEntity, TPrimaryKey> repository) : base(repository)
    {
        LocalizationSourceName = DemoConsts.LocalizationSourceName;
    }

    /// <summary>
    /// 得到当前用户信息
    /// </summary>
    /// <returns></returns>
    protected virtual async Task<User> GetCurrentUserAsync()
    {
        var user = await UserManager.FindByIdAsync(AbpSession.GetUserId().ToString());
        if (user == null)
        {
            throw new Exception("There is no current user!");
        }
        return user;
    }

    /// <summary>
    /// 得到当前租户信息
    /// </summary>
    /// <returns></returns>
    protected virtual Task<Tenant> GetCurrentTenantAsync()
    {
        return TenantManager.GetByIdAsync(AbpSession.GetTenantId());
    }

    /// <summary>
    /// 检查错误
    /// </summary>
    /// <param name="identityResult"></param>
    protected virtual void CheckErrors(IdentityResult identityResult)
    {
        identityResult.CheckErrors(LocalizationManager);
    }
}

因为应用层的代码我主要是参考Abp框架的源码来处理的,而 AsyncCrudAppService<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput, EntityDto<TPrimaryKey>>是在其源码中定义好的,可以直接使用的,所以才有了这个公共父类的实现。具体可查看Abp框架的源码

其次,在应用层的根目录下创建一个Dtos文件夹,其下放了三个处理分页相关功能的类。

最主要的是应用服务相关的类,其文件结构和实体类建立时的文件结构相同,具体可参见下图:
在这里插入图片描述
如图,代码分三块:领域服务接口和其实现类,AutoMapper的映射配置类,还有就是具体的Dto类。这里的Dto类只是最基本的添加、编辑和列表及分页的类,可根据业务情况自行添加新的Dto类。

1、AutoMapper配置类的代码如下:

/// <summary>
/// automapper配置类
/// </summary>
public class BaseCodeMapProfile : Profile
{
    /// <summary>
    /// 构造函数
    /// </summary>
    public BaseCodeMapProfile()
    {
        CreateMap<BaseCode.BaseCode, BaseCodeListDto>();
        CreateMap<BaseCodeListDto, BaseCode.BaseCode>();
        CreateMap<BaseCode.BaseCode, CreateBaseCodeInputDto>();
        CreateMap<CreateBaseCodeInputDto, BaseCode.BaseCode>();
        CreateMap<BaseCodeEditDto, BaseCode.BaseCode>();
        CreateMap<BaseCode.BaseCode,BaseCodeEditDto>();
    }
}

定义的配置类直接继承了Profile类,这样只需要维护这个类中的映射关系,而不需要做其他的改动即可。这是因为在XXXApplicationModule.cs中定义了如下内容,自动完成了配置类的注入。

public override void Initialize()
{
    var thisAssembly = typeof(DemoApplicationModule).GetAssembly();
    IocManager.RegisterAssemblyByConvention(thisAssembly);
    Configuration.Modules.AbpAutoMapper().Configurators.Add(
        // Scan the assembly for classes which inherit from AutoMapper.Profile
        cfg => cfg.AddMaps(thisAssembly)
    );
}

2、关于Dtos中的类,我们以XXXEditDto类为例看看,代码如下:

/// <summary>
/// BaseCode.BaseCode 的编辑DTO
/// <see cref="BaseCode.BaseCode"/>
/// </summary>
public class BaseCodeEditDto : EntityDto<Guid>
{
    /// <summary>
    /// 编码
    /// </summary>
    [Required]
    [StringLength(50,ErrorMessage = "编码最大长度为50,最小长度为5",MinimumLength = 5)]
    public string Code { get; set; }
     
    /// <summary>
    /// 编码名称
    /// </summary>
    [Required]
    [StringLength(50,ErrorMessage = "编码名称最大长度为50,最小长度为5",MinimumLength = 5)]
    public string BaseName { get; set; }
     
    /// <summary>
    /// 备注
    /// </summary>
    [StringLength(200,ErrorMessage = "备注最大长度200")]
    public string Remark { get; set; }
     
    /// <summary>
    /// 是否激活
    /// </summary>
    [Required]
    public bool IsActive { get; set; }
     
}

从此代码中可以看出,配置界面中的关于EditDto的相关配置内容都在此有所体现。

3、而对于应用服务类的生成,其接口类如下:

/// <summary>
/// 应用层服务的接口方法
///</summary>
public interface IBaseCodeAppService : IAsyncCrudAppService<BaseCodeListDto, Guid,PagedBaseCodeResultRequestDto, CreateBaseCodeInputDto, BaseCodeEditDto>
{
}

可以看到,其继承了Abp框架中定义的IAsyncCrudAppService接口,此接口中定义好了关于增删改查的相关方法,可以直接重写即可。生成的实现类代码如下:

/// <summary>
/// 应用层服务的接口实现方法
///</summary>
[AbpAuthorize]
public class BaseCodeAppService : DemoCrudAppServiceBase<BaseCode.BaseCode, BaseCodeListDto,Guid, PagedBaseCodeResultRequestDto, CreateBaseCodeInputDto, BaseCodeEditDto>,IBaseCodeAppService
{
    private readonly IBaseCodeManager _baseCodeManager;
    /// <summary>
    /// 构造函数
    ///</summary>
    public BaseCodeAppService(IRepository<BaseCode.BaseCode, Guid> baseCodeRepository, IBaseCodeManager baseCodeManager) : base(baseCodeRepository)
    {
        _baseCodeManager = baseCodeManager;
    }
    /// <summary>
    /// 添加基础编码
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public override async Task<BaseCodeListDto> CreateAsync(CreateBaseCodeInputDto input)
    {
        var entity = ObjectMapper.Map<BaseCode.BaseCode>(input);
        await _baseCodeManager.CreateAsync(entity);
        return MapToEntityDto(entity);
    }
    /// <summary>
    /// 修改基础编码
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public override async Task<BaseCodeListDto> UpdateAsync(BaseCodeEditDto input)
    {
        var entity = await this._baseCodeManager.FindByIdAsync(input.Id);
        ObjectMapper.Map(input, entity);
        return MapToEntityDto(entity);
    }
    /// <summary>
    /// 删除基础编码
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public override async Task DeleteAsync(EntityDto<Guid> input)
    {
        await this._baseCodeManager.DeleteAsync(input.Id);
    }
    /// <summary>
    /// 得到所有的基础编码
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public override Task<PagedResultDto<BaseCodeListDto>> GetAllAsync(PagedBaseCodeResultRequestDto input)
    {
        return base.GetAllAsync(input);
    }
    /// <summary>
    /// 根据id得到指定的病区信息
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public override Task<BaseCodeListDto> GetAsync(EntityDto<Guid> input)
    {
        return base.GetAsync(input);
    }
    /// <summary>
    /// 创建过滤条件的自定义,在获取列表信息中使用
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    protected override IQueryable<BaseCode.BaseCode> CreateFilteredQuery(PagedBaseCodeResultRequestDto input)
    {
        return Repository.GetAll().WhereIf(!input.Keyword.IsNullOrWhiteSpace(), x =>                     
                    x.Code.Contains(input.Keyword) ||
                    x.BaseName.Contains(input.Keyword) ||
                    x.Remark.Contains(input.Keyword));
    }
    /// <summary>
    /// 重写排序规则
    /// </summary>
    /// <param name="query"></param>
    /// <param name="input"></param>
    /// <returns></returns>
    protected override IQueryable<BaseCode.BaseCode> ApplyPaging(IQueryable<BaseCode.BaseCode> query, PagedBaseCodeResultRequestDto input)
    {
        return base.ApplyPaging(query, input);
    }
}

权限相关的类

权限相关的类也是在领域层生成的,在实体类同层创建一个Authorization文件夹,和领域服务的文件夹也是同层的。在此文件夹下有两个类,分别是XXXAuthorizationProvider.cs类和XXXPermissions.cs类。

还有一个类放在领域层根目录的Authorization文件夹下,这个文件夹及下面的相关类是Abp框架生成的,我们只是在下面添加一个AbpPermissions.cs类。

生成的项目结构如下:
在这里插入图片描述
这里简单展示下AbpPermissions.cs类的代码:

/// <summary>
/// 项目定义的默认权限变量名称
/// </summary>
public class AbpPermissions
{
    public const string Pages = "Pages";
    public const string Pages_Administration = "Pages.Administration";
    
    // 可自定义新的权限
}

总结

此代码生成器只是实现了最基本的代码实现,对于前端和单元测试等都没有实现。对于一些细节,如“是否第一次生成”这样的,也没有进行处理。个人考虑是,因为代码中对于文件夹、文件的生成都有判断,有则不在生成,没有才会生成。在不进行模板翻译而只是判断有无的情况下,速度很快,所以没有考虑进行界面的配置。

对于项目名称结构,也只是测试了形如xxx.xxx.Core的项目,对于xxx.Core形式的命名项目没有进行测试。关于实体类层级,我只测试了一层和两层的情况,即folder/entity.cs的形式和folder1/folder2/entity.cs的形式。

我已将源代码放在github上,可自行获取代码进行查看。可在bin/debug文件夹下AbpGenerateProject.vsix文件并点击安装,安装好后就可以在vs2019中使用了。如果有改进的想法的可以自行实现或者留言讨论。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章