2023年6月20日发(作者:)
ABP开发框架前后端开发系列---(2)框架的初步介绍在前⾯随笔《》⼤概介绍了这个ABP框架的主要特点,以及介绍了我对这框架的Web API应⽤优先的⼀些看法,本篇继续探讨ABP框架的初步使⽤,也就是我们下载到的ABP框架项⽬(基于ABP基础项⽬的扩展项⽬),如果理解各个组件模块,以及如何使⽤。1)ABP框架应⽤项⽬的介绍整个基础的ABP框架看似⾮常庞⼤,其实很多项⽬也很少内容,主要是独⽴封装不同的组件进⾏使⽤,如Automaper、SignalR、MongoDB、Quartz。。。等等内容,基本上我们主要关注的内容就是Abp这个主要的项⽬⾥⾯,其他的是针对不同的组件应⽤做的封装。⽽基于基础ABP框架扩展出来的ABP应⽤项⽬,则简单很多,我们也是在需要⽤到不同组件的时候,才考虑引⼊对应的基础模块进⾏使⽤,⼀般来说,主要还是基于仓储管理实现基于数据库的应⽤,因此我们主要对微软的实体框架的相关内容了解清楚即可。这个项⽬是⼀个除了包含基础的⼈员、⾓⾊、权限、认证、配置信息的基础项⽬外,⽽如果你从这⾥开始,对于其中的⼀些继承关系的了解,会增加很多困难,因为它们基础的⽤户、⾓⾊等对象关系实在是很复杂。我建议从⼀个简单的项⽬开始,也就是基于⼀两个特定的应⽤表开始的项⽬,因此可以参考案例项⽬: 或者 项⽬,我们⼊门理解起来可能更加清楚。这⾥我以项⽬来进⾏分析项⽬中各个层的类之间的关系。我们先从⼀个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。先以领域层,也就是项⽬中的⾥⾯的内容进⾏分析。
2)领域对象层的代码分析⾸先,我们需要了解领域对象和数据库之间的关系的类,也就是领域实体信息,这个类⾮常关键,它是构建仓储模式和数据库表之间的关系的。 [Table("AppEvents")] public class Event : FullAuditedEntity, IMustHaveTenant { public virtual int TenantId { get; set; } [Required] [StringLength(MaxTitleLength)] public virtual string Title { get; protected set; } [StringLength(MaxDescriptionLength)] public virtual string Description { get; protected set; } public virtual DateTime Date { get; protected set; } public virtual bool IsCancelled { get; protected set; }
...... }这个⾥⾯定义了领域实体和表名之间的关系,其他属性也就是对应数据库的字段了[Table("AppEvents")]然后在FrameworkCore项⽬⾥⾯,加⼊这个表的DbSet对象,如下代码所⽰。namespace FrameworkCore{ public class EventCloudDbContext : AbpZeroDbContext { public virtual DbSet Events { get; set; } public virtual DbSet EventRegistrations { get; set; } public EventCloudDbContext(DbContextOptions options) : base(options) { } }}简单的话,仓储模式就可以跑起来了,我们利⽤ IRepository 接⼝就可以获取对应表的很多处理接⼝,包括增删改查、分页等等接⼝,不过为了进⾏业务逻辑的隔离,我们引⼊了Application Service应⽤层,同时也引⼊了DTO(数据传输对象)的概念,以便向应⽤层隐藏我们的领域对象信息,实现更加弹性化的处理。⼀般和领域对象对应的DTO对象定义如下所⽰。 [AutoMapFrom(typeof(Event))] public class EventListDto : FullAuditedEntityDto { public string Title { get; set; } public string Description { get; set; } public DateTime Date { get; set; } public bool IsCancelled { get; set; } public virtual int MaxRegistrationCount { get; protected set; } public int RegistrationsCount { get; set; } }其中我们需要注意实体类继承⾃FullAuditedEntityDto,它标记这个领域对象会记录创建、修改、删除的标记、时间和⼈员信息,如果需要深⼊了解这个部分,可以参考下ABP官⽹关于领域实体对象的介绍内容()。通过在类增加标记性的特性处理,我们可以从Event领域对象到EventListDto的对象实现了⾃动化的映射。这样的定义处理,⼀般来说没有什么问题,但是如果我们需要把DTO(如EventListDto)隔离和领域对象(如Event)的关系,把DTO单独抽取来⽅便公⽤,那么我们可以在应⽤服务层定义⼀个领域对象的映射⽂件来替代这种声明式的映射关系,AutoMaper的映射⽂件定义如下所⽰。 public class EventMapProfile : Profile { public EventMapProfile() { CreateMap(); CreateMap(); CreateMap(); } }这样抽取独⽴的映射⽂件,可以为我们单独抽取DTO对象和应⽤层接⼝作为⼀个独⽴项⽬提供⽅便,因为不需要依赖领域实体。如我改造项⽬的DTO层实例如下所⽰。刚才介绍了领域实体和DTO对象的映射关系,就是为了给应⽤服务层提供数据的承载。如果领域对象的逻辑处理⽐较复杂⼀些,还可以定义⼀个类似业务逻辑类(类似我们说说的BLL),⼀般ABP框架⾥⾯以Manager结尾的就是这个概念,如对于案例⾥⾯,业务逻辑接⼝和逻辑类定义如下所⽰,这⾥注意接⼝继承⾃IDomainService接⼝。 /// /// Event的业务逻辑类 /// public interface IEventManager: IDomainService { Task GetAsync(Guid id); Task CreateAsync(Event @event); void Cancel(Event @event); Task RegisterAsync(Event @event, User user); Task CancelRegistrationAsync(Event @event, User user); Task> GetRegisteredUsersAsync(Event @event); }业务逻辑类的实现如下所⽰。我们看到这个类的构造函数⾥⾯,带⼊了⼏个接⼝对象的参数,这个就是DI,依赖注⼊的概念,这些通过IOC容易进⾏构造函数的注⼊,我们只需要知道,在模块启动后,这些接⼝都可以使⽤就可以了,如果需要了解更深⼊的,可以参考ABP官⽹对于依赖注⼊的内容介绍()。这样我们对应的Application Service⾥⾯,对于Event的应⽤服务层的类EventAppService ,如下所⽰。 [AbpAuthorize] public class EventAppService : EventCloudAppServiceBase, IEventAppService { private readonly IEventManager _eventManager; private readonly IRepository _eventRepository; public EventAppService( IEventManager eventManager, IRepository eventRepository) { _eventManager = eventManager; _eventRepository = eventRepository; } ......这⾥的服务层类提供了两个接⼝注⼊,⼀个是⾃定义的事件业务对象类,⼀个是标准的仓储对象。⼤多数情况下如果是基于Web API的架构下,如果是基于数据库表的处理,我觉得领域的业务管理类也是不必要的,直接使⽤仓储的标准对象处理,已经可以满⾜⼤多数的需要了,⼀些逻辑我们可以在Application Service⾥⾯实现以下即可。
3)字典模块业务类的简化我们以字典模块的字典类型表来介绍。领域业务对象接⼝层定义如下所⽰(类似IBLL) /// /// 领域业务管理接⼝ /// public interface IDictTypeManager : IDomainService { /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// 字典类型ID,为空则返回所有 /// Task> GetAllType(string dictTypeId); }领域业务对象管理类(类似BLL) /// /// 领域业务管理类实现 /// public class DictTypeManager : DomainService, IDictTypeManager { private readonly IRepository _dictTypeRepository; public DictTypeManager(IRepository dictTypeRepository) { this._dictTypeRepository = dictTypeRepository; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// 字典类型ID,为空则返回所有 /// public async Task> GetAllType(string dictTypeId) { IList list = null; if (!OrWhiteSpace(dictTypeId)) { list = await _ListAsync(p => == dictTypeId); } else { list = await _ListAsync(); } Dictionary dict = new Dictionary(); foreach (var info in list) { if (!nsKey()) { (, ); } } return dict; } }然后领域对象的应⽤服务层接⼝实现如下所⽰ [AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase, IDictTypeAppService { private readonly IDictTypeManager _manager; private readonly IRepository _repository; public DictTypeAppService( IRepository repository,
IDictTypeManager manager) : base(repository) { _repository = repository; _manager = manager; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// public async Task> GetAllType(string dictTypeId) { var result = await _Type(dictTypeId); return result; }......这样就在应⽤服务层⾥⾯,就整合了业务逻辑类的处理,不过这样的做法,对于常规数据库的处理来说,显得有点累赘,还需要多定义⼀个业务对象接⼝和⼀个业务对象实现,同时在应⽤层接⼝⾥⾯,也需要多增加⼀个接⼝参数,总体感觉有点多余,因此我把它改为使⽤标准的仓储对象来处理就可以达到同样的⽬的了。在项⽬其中对应位置,删除字典类型的⼀个业务对象接⼝和⼀个业务对象实现,改为标准仓储对象的接⼝处理,相当于把业务逻辑⾥⾯的代码提出来放在服务层⽽已,那么在应⽤服务层的处理代码如下所⽰。 [AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase, IDictTypeAppService { private readonly IRepository _repository; public DictTypeAppService( IRepository repository) : base(repository) { _repository = repository; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// public async Task> GetAllType(string dictTypeId) { IList list = null; if (!OrWhiteSpace(dictTypeId)) { list = await ListAsync(p => == dictTypeId); } else { list = await ListAsync(); } Dictionary dict = new Dictionary(); foreach (var info in list) { if (!nsKey()) { (, ); } } return dict; }......这样我们少定义两个⽂件,以及减少协调业务类的代码,代码更加简洁和容易理解,反正最终实现都是基于仓储对象的接⼝调⽤。另外,我们继续了解项⽬,知道在项⽬是我们Web API层启动,且动态构建Web API层的服务层。它整合了Swagger对接⼝的测试使⽤。 // Swagger - Enable this line and the related lines in Configure method to enable swagger UI ggerGen(options => { rDoc("v1", new Info { Title = "MyProject API", Version = "v1" }); lusionPredicate((docName, description) => true); // Define the BearerAuth scheme that's in use urityDefinition("bearerAuth", new ApiKeyScheme() { Description = "JWT Authorization header using the Bearer scheme. Example: "Authorization: Bearer {token}"", Name = "Authorization", In = "header", Type = "apiKey" }); // Assign scope requirements to operations based on AuthorizeAttribute ionFilter(); });启动项⽬,我们可以看到Swagger的管理界⾯如下所⽰。
2023年6月20日发(作者:)
ABP开发框架前后端开发系列---(2)框架的初步介绍在前⾯随笔《》⼤概介绍了这个ABP框架的主要特点,以及介绍了我对这框架的Web API应⽤优先的⼀些看法,本篇继续探讨ABP框架的初步使⽤,也就是我们下载到的ABP框架项⽬(基于ABP基础项⽬的扩展项⽬),如果理解各个组件模块,以及如何使⽤。1)ABP框架应⽤项⽬的介绍整个基础的ABP框架看似⾮常庞⼤,其实很多项⽬也很少内容,主要是独⽴封装不同的组件进⾏使⽤,如Automaper、SignalR、MongoDB、Quartz。。。等等内容,基本上我们主要关注的内容就是Abp这个主要的项⽬⾥⾯,其他的是针对不同的组件应⽤做的封装。⽽基于基础ABP框架扩展出来的ABP应⽤项⽬,则简单很多,我们也是在需要⽤到不同组件的时候,才考虑引⼊对应的基础模块进⾏使⽤,⼀般来说,主要还是基于仓储管理实现基于数据库的应⽤,因此我们主要对微软的实体框架的相关内容了解清楚即可。这个项⽬是⼀个除了包含基础的⼈员、⾓⾊、权限、认证、配置信息的基础项⽬外,⽽如果你从这⾥开始,对于其中的⼀些继承关系的了解,会增加很多困难,因为它们基础的⽤户、⾓⾊等对象关系实在是很复杂。我建议从⼀个简单的项⽬开始,也就是基于⼀两个特定的应⽤表开始的项⽬,因此可以参考案例项⽬: 或者 项⽬,我们⼊门理解起来可能更加清楚。这⾥我以项⽬来进⾏分析项⽬中各个层的类之间的关系。我们先从⼀个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。先以领域层,也就是项⽬中的⾥⾯的内容进⾏分析。
2)领域对象层的代码分析⾸先,我们需要了解领域对象和数据库之间的关系的类,也就是领域实体信息,这个类⾮常关键,它是构建仓储模式和数据库表之间的关系的。 [Table("AppEvents")] public class Event : FullAuditedEntity, IMustHaveTenant { public virtual int TenantId { get; set; } [Required] [StringLength(MaxTitleLength)] public virtual string Title { get; protected set; } [StringLength(MaxDescriptionLength)] public virtual string Description { get; protected set; } public virtual DateTime Date { get; protected set; } public virtual bool IsCancelled { get; protected set; }
...... }这个⾥⾯定义了领域实体和表名之间的关系,其他属性也就是对应数据库的字段了[Table("AppEvents")]然后在FrameworkCore项⽬⾥⾯,加⼊这个表的DbSet对象,如下代码所⽰。namespace FrameworkCore{ public class EventCloudDbContext : AbpZeroDbContext { public virtual DbSet Events { get; set; } public virtual DbSet EventRegistrations { get; set; } public EventCloudDbContext(DbContextOptions options) : base(options) { } }}简单的话,仓储模式就可以跑起来了,我们利⽤ IRepository 接⼝就可以获取对应表的很多处理接⼝,包括增删改查、分页等等接⼝,不过为了进⾏业务逻辑的隔离,我们引⼊了Application Service应⽤层,同时也引⼊了DTO(数据传输对象)的概念,以便向应⽤层隐藏我们的领域对象信息,实现更加弹性化的处理。⼀般和领域对象对应的DTO对象定义如下所⽰。 [AutoMapFrom(typeof(Event))] public class EventListDto : FullAuditedEntityDto { public string Title { get; set; } public string Description { get; set; } public DateTime Date { get; set; } public bool IsCancelled { get; set; } public virtual int MaxRegistrationCount { get; protected set; } public int RegistrationsCount { get; set; } }其中我们需要注意实体类继承⾃FullAuditedEntityDto,它标记这个领域对象会记录创建、修改、删除的标记、时间和⼈员信息,如果需要深⼊了解这个部分,可以参考下ABP官⽹关于领域实体对象的介绍内容()。通过在类增加标记性的特性处理,我们可以从Event领域对象到EventListDto的对象实现了⾃动化的映射。这样的定义处理,⼀般来说没有什么问题,但是如果我们需要把DTO(如EventListDto)隔离和领域对象(如Event)的关系,把DTO单独抽取来⽅便公⽤,那么我们可以在应⽤服务层定义⼀个领域对象的映射⽂件来替代这种声明式的映射关系,AutoMaper的映射⽂件定义如下所⽰。 public class EventMapProfile : Profile { public EventMapProfile() { CreateMap(); CreateMap(); CreateMap(); } }这样抽取独⽴的映射⽂件,可以为我们单独抽取DTO对象和应⽤层接⼝作为⼀个独⽴项⽬提供⽅便,因为不需要依赖领域实体。如我改造项⽬的DTO层实例如下所⽰。刚才介绍了领域实体和DTO对象的映射关系,就是为了给应⽤服务层提供数据的承载。如果领域对象的逻辑处理⽐较复杂⼀些,还可以定义⼀个类似业务逻辑类(类似我们说说的BLL),⼀般ABP框架⾥⾯以Manager结尾的就是这个概念,如对于案例⾥⾯,业务逻辑接⼝和逻辑类定义如下所⽰,这⾥注意接⼝继承⾃IDomainService接⼝。 /// /// Event的业务逻辑类 /// public interface IEventManager: IDomainService { Task GetAsync(Guid id); Task CreateAsync(Event @event); void Cancel(Event @event); Task RegisterAsync(Event @event, User user); Task CancelRegistrationAsync(Event @event, User user); Task> GetRegisteredUsersAsync(Event @event); }业务逻辑类的实现如下所⽰。我们看到这个类的构造函数⾥⾯,带⼊了⼏个接⼝对象的参数,这个就是DI,依赖注⼊的概念,这些通过IOC容易进⾏构造函数的注⼊,我们只需要知道,在模块启动后,这些接⼝都可以使⽤就可以了,如果需要了解更深⼊的,可以参考ABP官⽹对于依赖注⼊的内容介绍()。这样我们对应的Application Service⾥⾯,对于Event的应⽤服务层的类EventAppService ,如下所⽰。 [AbpAuthorize] public class EventAppService : EventCloudAppServiceBase, IEventAppService { private readonly IEventManager _eventManager; private readonly IRepository _eventRepository; public EventAppService( IEventManager eventManager, IRepository eventRepository) { _eventManager = eventManager; _eventRepository = eventRepository; } ......这⾥的服务层类提供了两个接⼝注⼊,⼀个是⾃定义的事件业务对象类,⼀个是标准的仓储对象。⼤多数情况下如果是基于Web API的架构下,如果是基于数据库表的处理,我觉得领域的业务管理类也是不必要的,直接使⽤仓储的标准对象处理,已经可以满⾜⼤多数的需要了,⼀些逻辑我们可以在Application Service⾥⾯实现以下即可。
3)字典模块业务类的简化我们以字典模块的字典类型表来介绍。领域业务对象接⼝层定义如下所⽰(类似IBLL) /// /// 领域业务管理接⼝ /// public interface IDictTypeManager : IDomainService { /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// 字典类型ID,为空则返回所有 /// Task> GetAllType(string dictTypeId); }领域业务对象管理类(类似BLL) /// /// 领域业务管理类实现 /// public class DictTypeManager : DomainService, IDictTypeManager { private readonly IRepository _dictTypeRepository; public DictTypeManager(IRepository dictTypeRepository) { this._dictTypeRepository = dictTypeRepository; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// 字典类型ID,为空则返回所有 /// public async Task> GetAllType(string dictTypeId) { IList list = null; if (!OrWhiteSpace(dictTypeId)) { list = await _ListAsync(p => == dictTypeId); } else { list = await _ListAsync(); } Dictionary dict = new Dictionary(); foreach (var info in list) { if (!nsKey()) { (, ); } } return dict; } }然后领域对象的应⽤服务层接⼝实现如下所⽰ [AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase, IDictTypeAppService { private readonly IDictTypeManager _manager; private readonly IRepository _repository; public DictTypeAppService( IRepository repository,
IDictTypeManager manager) : base(repository) { _repository = repository; _manager = manager; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// public async Task> GetAllType(string dictTypeId) { var result = await _Type(dictTypeId); return result; }......这样就在应⽤服务层⾥⾯,就整合了业务逻辑类的处理,不过这样的做法,对于常规数据库的处理来说,显得有点累赘,还需要多定义⼀个业务对象接⼝和⼀个业务对象实现,同时在应⽤层接⼝⾥⾯,也需要多增加⼀个接⼝参数,总体感觉有点多余,因此我把它改为使⽤标准的仓储对象来处理就可以达到同样的⽬的了。在项⽬其中对应位置,删除字典类型的⼀个业务对象接⼝和⼀个业务对象实现,改为标准仓储对象的接⼝处理,相当于把业务逻辑⾥⾯的代码提出来放在服务层⽽已,那么在应⽤服务层的处理代码如下所⽰。 [AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase, IDictTypeAppService { private readonly IRepository _repository; public DictTypeAppService( IRepository repository) : base(repository) { _repository = repository; } /// /// 获取所有字典类型的列表集合(Key为名称,Value为ID值) /// /// public async Task> GetAllType(string dictTypeId) { IList list = null; if (!OrWhiteSpace(dictTypeId)) { list = await ListAsync(p => == dictTypeId); } else { list = await ListAsync(); } Dictionary dict = new Dictionary(); foreach (var info in list) { if (!nsKey()) { (, ); } } return dict; }......这样我们少定义两个⽂件,以及减少协调业务类的代码,代码更加简洁和容易理解,反正最终实现都是基于仓储对象的接⼝调⽤。另外,我们继续了解项⽬,知道在项⽬是我们Web API层启动,且动态构建Web API层的服务层。它整合了Swagger对接⼝的测试使⽤。 // Swagger - Enable this line and the related lines in Configure method to enable swagger UI ggerGen(options => { rDoc("v1", new Info { Title = "MyProject API", Version = "v1" }); lusionPredicate((docName, description) => true); // Define the BearerAuth scheme that's in use urityDefinition("bearerAuth", new ApiKeyScheme() { Description = "JWT Authorization header using the Bearer scheme. Example: "Authorization: Bearer {token}"", Name = "Authorization", In = "header", Type = "apiKey" }); // Assign scope requirements to operations based on AuthorizeAttribute ionFilter(); });启动项⽬,我们可以看到Swagger的管理界⾯如下所⽰。
发布评论