本文共 9300 字,大约阅读时间需要 31 分钟。
在使用abp框架进行项目开发的时候,开始使用的是52abp的代码生成器,但是在使用的过程中,总有一些地方感觉不舒服,也许是出于程序员的强迫症吧。最终,我决定自己写一个简单的代码生成器使用。所以,也就有了这么一篇文章。
我们先看看界面
只有这一个界面,因为只是为了生成后台的代码。从图中可以看出,我只是生成了领域服务层,EntityFrameworkCore层和应用层的相关代码,权限部分可以选择生成,也可用后期用到在生成。这里需要编辑的内容有:
1、实体类主键的类型,如Guid
,long
等;
2、实体类的中文名称,可在生成类的注释中使用,否则使用类名称;
3、选择在Edit和List的DTO中显示的字段,勾选则生成此字段;
4、字段中文名称,实体类中默认有的会带出来,必填也是如此;
5、如果实体类中有定义字段长度,此处会自动带出,可在此修改,为 0
则不会在DTO中生成。
我们以BaseCode为例子来说明。
首先,我们在领域层新建一个BaseCode文件夹,然后在此文件夹下创建BaseCode.cs类。在此类中添加相应的属性信息,具体代码如下:
public class BaseCode :Entity,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; }}
关于实体类的具体定义规则,可参考。
此示例代码中添加Required和StringLength特性,主要是为了测试界面数据的绑定,其实此处可用不定义,而是放在生成的配置类中定义。
但是对于DisplayName特性,我建议您定义,因为它不仅仅是在界面上绑定显示,可用在DTO
中生成相应的注释;同样的,它还会生成相应的数据表字段说明。所以此处强烈建议您给出此字段的说明。
让我们来看看生成的代码:
////// 基础编码 EF配置,可完成必填,注释等内容的处理/// public class BaseCodeCfg : IEntityTypeConfiguration{ /// /// 具体配置方法 /// /// public void Configure(EntityTypeBuilderbuilder) { // 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("是否激活"); }}
其实个人更加的建议,将Required和StringLength等特性的定义放在此处,同样可在此处定义很多其他的约束,比如默认值等。
注意:生成的代码中的AbpConsts类是在领域层中生成的。
生成的项目结构如图:
EF
层的EntityFrameworkCore文件夹下找到。 添加的内容主要是两块,一个是实体类数据集属性,另一个是重写OnModelCreating方法。代码分别如下:
/* Define a DbSet for each entity of the application */public DbSetBaseCodes;
protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.ApplyConfiguration(new BaseCodeCfg()); base.OnModelCreating(modelBuilder);}
这样在生成数据库时,相应的表字段注释也会随之生成。具体生成方式,可参考代码生成器中生成的readme.md
说明文档。
让我们看下生成的数据库效果:
在创建好的实体类上右击,在弹出的菜单中选择【Abp 代码生成器】后即可看到本文开头展示的界面。生成此层的相关代码,必须勾选【领域服务】复选框。
生成后,会在实体类的同层创建一个DomainService文件夹,并在其下生成领域服务接口和领域服务实现类。在领域层的根目录下生成一个AbpConsts.cs类和XXXDomainServiceBase.cs类。最后要说的是,还会在此层的根目录下生成一个Readme.md
的说明文档。
这里简单展示下XXXDomainServiceBase.cs类的代码:
////// 自定义的基础领域服务,继承自abp框架的领域服务/// public abstract class DemoDomainServiceBase : DomainService{ ////// 构造函数 /// protected DemoDomainServiceBase() { LocalizationSourceName = DemoConsts.LocalizationSourceName; } /* Add your common members for all your domain services. */ /*在领域服务中添加你的自定义公共方法*/ }
项目结构可参考权限部分给的图片
相对来说,这一层中生成的类最多。
首先我们来看看在应用层根目录下生成的XXXCrudAppServiceBase.cs类。其生成的代码如下:
////// 应用层增删改查的基类,自定义的业务类都可从此类继承/// public abstract class DemoCrudAppServiceBase: AsyncCrudAppService > where TEntity : class, IEntity where TEntityDto : IEntityDto where TUpdateInput : IEntityDto { /// /// 租户管理 /// public TenantManager TenantManager { get; set; } ////// 用户管理 /// public UserManager UserManager { get; set; } ////// 构造函数 /// public DemoCrudAppServiceBase(IRepositoryrepository) : base(repository) { LocalizationSourceName = DemoConsts.LocalizationSourceName; } /// /// 得到当前用户信息 /// ///protected virtual async Task GetCurrentUserAsync() { var user = await UserManager.FindByIdAsync(AbpSession.GetUserId().ToString()); if (user == null) { throw new Exception("There is no current user!"); } return user; } /// /// 得到当前租户信息 /// ///protected virtual Task GetCurrentTenantAsync() { return TenantManager.GetByIdAsync(AbpSession.GetTenantId()); } /// /// 检查错误 /// /// protected virtual void CheckErrors(IdentityResult identityResult) { identityResult.CheckErrors(LocalizationManager); }}
因为应用层的代码我主要是参考Abp
框架的源码来处理的,而 AsyncCrudAppService<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput, EntityDto<TPrimaryKey>>
是在其源码中定义好的,可以直接使用的,所以才有了这个公共父类的实现。具体可查看。
其次,在应用层的根目录下创建一个Dtos文件夹,其下放了三个处理分页相关功能的类。
最主要的是应用服务相关的类,其文件结构和实体类建立时的文件结构相同,具体可参见下图:
1、AutoMapper配置类的代码如下:
////// automapper配置类/// public class BaseCodeMapProfile : Profile{ ////// 构造函数 /// public BaseCodeMapProfile() { CreateMap(); CreateMap (); CreateMap (); CreateMap (); CreateMap (); CreateMap (); }}
定义的配置类直接继承了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类为例看看,代码如下:
////// BaseCode.BaseCode 的编辑DTO/// public class BaseCodeEditDto : EntityDto/// { /// /// 编码 /// [Required] [StringLength(50,ErrorMessage = "编码最大长度为50,最小长度为5",MinimumLength = 5)] public string Code { get; set; } ////// 编码名称 /// [Required] [StringLength(50,ErrorMessage = "编码名称最大长度为50,最小长度为5",MinimumLength = 5)] public string BaseName { get; set; } ////// 备注 /// [StringLength(200,ErrorMessage = "备注最大长度200")] public string Remark { get; set; } ////// 是否激活 /// [Required] public bool IsActive { get; set; } }
从此代码中可以看出,配置界面中的关于EditDto的相关配置内容都在此有所体现。
3、而对于应用服务类的生成,其接口类如下:
////// 应用层服务的接口方法/// public interface IBaseCodeAppService : IAsyncCrudAppService{ }
可以看到,其继承了Abp框架
中定义的IAsyncCrudAppService接口,此接口中定义好了关于增删改查的相关方法,可以直接重写即可。生成的实现类代码如下:
////// 应用层服务的接口实现方法/// [AbpAuthorize]public class BaseCodeAppService : DemoCrudAppServiceBase,IBaseCodeAppService{ private readonly IBaseCodeManager _baseCodeManager; /// /// 构造函数 /// public BaseCodeAppService(IRepositorybaseCodeRepository, IBaseCodeManager baseCodeManager) : base(baseCodeRepository) { _baseCodeManager = baseCodeManager; } /// /// 添加基础编码 /// /// ///public override async Task CreateAsync(CreateBaseCodeInputDto input) { var entity = ObjectMapper.Map (input); await _baseCodeManager.CreateAsync(entity); return MapToEntityDto(entity); } /// /// 修改基础编码 /// /// ///public override async Task UpdateAsync(BaseCodeEditDto input) { var entity = await this._baseCodeManager.FindByIdAsync(input.Id); ObjectMapper.Map(input, entity); return MapToEntityDto(entity); } /// /// 删除基础编码 /// /// ///public override async Task DeleteAsync(EntityDto input) { await this._baseCodeManager.DeleteAsync(input.Id); } /// /// 得到所有的基础编码 /// /// ///public override Task > GetAllAsync(PagedBaseCodeResultRequestDto input) { return base.GetAllAsync(input); } /// /// 根据id得到指定的病区信息 /// /// ///public override Task GetAsync(EntityDto input) { return base.GetAsync(input); } /// /// 创建过滤条件的自定义,在获取列表信息中使用 /// /// ///protected override IQueryable 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)); } /// /// 重写排序规则 /// /// /// ///protected override IQueryable ApplyPaging(IQueryable query, PagedBaseCodeResultRequestDto input) { return base.ApplyPaging(query, input); }}
权限相关的类也是在领域层生成的,在实体类同层创建一个Authorization文件夹,和领域服务的文件夹也是同层的。在此文件夹下有两个类,分别是XXXAuthorizationProvider.cs类和XXXPermissions.cs类。
还有一个类放在领域层根目录的Authorization文件夹下,这个文件夹及下面的相关类是Abp框架
生成的,我们只是在下面添加一个AbpPermissions.cs类。
生成的项目结构如下:
////// 项目定义的默认权限变量名称/// 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的形式。
我已将源代码放在上,可自行获取代码进行查看。可在bin/debug文件夹下AbpGenerateProject.vsix
文件并点击安装,安装好后就可以在vs2019
中使用了。如果有改进的想法的可以自行实现或者留言讨论。
转载地址:http://ibwhj.baihongyu.com/