在前面随笔《ABP开发框架先后端开发系列---(1)框架的整体介绍》大概介绍了这个ABP框架的主要特色,以及介绍了我对这框架的Web API应用优先的一些见解,本篇继续探讨ABP框架的初步使用,也就是咱们下载到的ABP框架项目(基于ABP基础项目的扩展项目),若是理解各个组件模块,以及如何使用。html
整个基础的ABP框架看似很是庞大,其实不少项目也不多内容,主要是独立封装不一样的组件进行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等内容,基本上咱们主要关注的内容就是Abp这个主要的项目里面,其余的是针对不一样的组件应用作的封装。git
而基于基础ABP框架扩展出来的ABP应用项目,则简单不少,咱们也是在须要用到不一样组件的时候,才考虑引入对应的基础模块进行使用,通常来讲,主要仍是基于仓储管理实现基于数据库的应用,所以咱们主要对微软的实体框架的相关内容了解清楚便可。github
这个项目是一个除了包含基础的人员、角色、权限、认证、配置信息的基础项目外,而若是你从这里开始,对于其中的一些继承关系的了解,会增长不少困难,由于它们基础的用户、角色等对象关系实在是很复杂。数据库
我建议从一个简单的项目开始,也就是基于一两个特定的应用表开始的项目,所以能够参考案例项目:eventcloud 或者 sample-blog-module 项目,咱们入门理解起来可能更加清楚。这里我以eventcloud项目来进行分析项目中各个层的类之间的关系。后端
咱们先从一个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。api
先以领域层,也就是项目中的EventCloud.Core里面的内容进行分析。架构
首先,咱们须要了解领域对象和数据库之间的关系的类,也就是领域实体信息,这个类很是关键,它是构建仓储模式和数据库表之间的关系的。框架
[Table("AppEvents")] public class Event : FullAuditedEntity<Guid>, 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; } ...... }
这个里面定义了领域实体和表名之间的关系,其余属性也就是对应数据库的字段了async
[Table("AppEvents")]
而后在EventCloud.EntityFrameworkCore项目里面,加入这个表的DbSet对象,以下代码所示。函数
namespace EventCloud.EntityFrameworkCore { public class EventCloudDbContext : AbpZeroDbContext<Tenant, Role, User, EventCloudDbContext> { public virtual DbSet<Event> Events { get; set; } public virtual DbSet<EventRegistration> EventRegistrations { get; set; } public EventCloudDbContext(DbContextOptions<EventCloudDbContext> options) : base(options) { } } }
简单的话,仓储模式就能够跑起来了,咱们利用 IRepository<Event, Guid> 接口就能够获取对应表的不少处理接口,包括增删改查、分页等等接口,不过为了进行业务逻辑的隔离,咱们引入了Application Service应用层,同时也引入了DTO(数据传输对象)的概念,以便向应用层隐藏咱们的领域对象信息,实现更加弹性化的处理。通常和领域对象对应的DTO对象定义以下所示。
[AutoMapFrom(typeof(Event))] public class EventListDto : FullAuditedEntityDto<Guid> { 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<Guid>,它标记这个领域对象会记录建立、修改、删除的标记、时间和人员信息,若是须要深刻了解这个部分,能够参考下ABP官网关于领域实体对象的介绍内容(Entities)。
经过在类增长标记性的特性处理,咱们能够从Event领域对象到EventListDto的对象实现了自动化的映射。这样的定义处理,通常来讲没有什么问题,可是若是咱们须要把DTO(如EventListDto)隔离和领域对象(如Event)的关系,把DTO单独抽取来方便公用,那么咱们能够在应用服务层定义一个领域对象的映射文件来替代这种声明式的映射关系,AutoMaper的映射文件定义以下所示。
public class EventMapProfile : Profile { public EventMapProfile() { CreateMap<EventListDto, Event>(); CreateMap<EventDetailOutput, Event>(); CreateMap<EventRegistrationDto, EventRegistration>(); } }
这样抽取独立的映射文件,能够为咱们单独抽取DTO对象和应用层接口做为一个独立项目提供方便,由于不须要依赖领域实体。如我改造项目的DTO层实例以下所示。
刚才介绍了领域实体和DTO对象的映射关系,就是为了给应用服务层提供数据的承载。
若是领域对象的逻辑处理比较复杂一些,还能够定义一个相似业务逻辑类(相似咱们说说的BLL),通常ABP框架里面以Manager结尾的就是这个概念,如对于案例里面,业务逻辑接口和逻辑类定义以下所示,这里注意接口继承自IDomainService接口。
/// <summary> /// Event的业务逻辑类 /// </summary> public interface IEventManager: IDomainService { Task<Event> GetAsync(Guid id); Task CreateAsync(Event @event); void Cancel(Event @event); Task<EventRegistration> RegisterAsync(Event @event, User user); Task CancelRegistrationAsync(Event @event, User user); Task<IReadOnlyList<User>> GetRegisteredUsersAsync(Event @event); }
业务逻辑类的实现以下所示。
咱们看到这个类的构造函数里面,带入了几个接口对象的参数,这个就是DI,依赖注入的概念,这些经过IOC容易进行构造函数的注入,咱们只须要知道,在模块启动后,这些接口均可以使用就能够了,若是须要了解更深刻的,能够参考ABP官网对于依赖注入的内容介绍(Dependency Injection)。
这样咱们对应的Application Service里面,对于Event的应用服务层的类EventAppService ,以下所示。
[AbpAuthorize] public class EventAppService : EventCloudAppServiceBase, IEventAppService { private readonly IEventManager _eventManager; private readonly IRepository<Event, Guid> _eventRepository; public EventAppService( IEventManager eventManager, IRepository<Event, Guid> eventRepository) { _eventManager = eventManager; _eventRepository = eventRepository; } ......
这里的服务层类提供了两个接口注入,一个是自定义的事件业务对象类,一个是标准的仓储对象。
大多数状况下若是是基于Web API的架构下,若是是基于数据库表的处理,我以为领域的业务管理类也是没必要要的,直接使用仓储的标准对象处理,已经能够知足大多数的须要了,一些逻辑咱们能够在Application Service里面实现如下便可。
咱们以字典模块的字典类型表来介绍。
领域业务对象接口层定义以下所示(相似IBLL)
/// <summary> /// 领域业务管理接口 /// </summary> public interface IDictTypeManager : IDomainService { /// <summary> /// 获取全部字典类型的列表集合(Key为名称,Value为ID值) /// </summary> /// <param name="dictTypeId">字典类型ID,为空则返回全部</param> /// <returns></returns> Task<Dictionary<string, string>> GetAllType(string dictTypeId); }
领域业务对象管理类(相似BLL)
/// <summary> /// 领域业务管理类实现 /// </summary> public class DictTypeManager : DomainService, IDictTypeManager { private readonly IRepository<DictType, string> _dictTypeRepository; public DictTypeManager(IRepository<DictType, string> dictTypeRepository) { this._dictTypeRepository = dictTypeRepository; } /// <summary> /// 获取全部字典类型的列表集合(Key为名称,Value为ID值) /// </summary> /// <param name="dictTypeId">字典类型ID,为空则返回全部</param> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { IList<DictType> list = null; if (!string.IsNullOrWhiteSpace(dictTypeId)) { list = await _dictTypeRepository.GetAllListAsync(p => p.PID == dictTypeId); } else { list = await _dictTypeRepository.GetAllListAsync(); } Dictionary<string, string> dict = new Dictionary<string, string>(); foreach (var info in list) { if (!dict.ContainsKey(info.Name)) { dict.Add(info.Name, info.Id); } } return dict; } }
而后领域对象的应用服务层接口实现以下所示
[AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase<DictType, DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService { private readonly IDictTypeManager _manager; private readonly IRepository<DictType, string> _repository; public DictTypeAppService( IRepository<DictType, string> repository, IDictTypeManager manager) : base(repository) { _repository = repository; _manager = manager; } /// <summary> /// 获取全部字典类型的列表集合(Key为名称,Value为ID值) /// </summary> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { var result = await _manager.GetAllType(dictTypeId); return result; } ......
这样就在应用服务层里面,就整合了业务逻辑类的处理,不过这样的作法,对于常规数据库的处理来讲,显得有点累赘,还须要多定义一个业务对象接口和一个业务对象实现,同时在应用层接口里面,也须要多增长一个接口参数,整体感受有点多余,所以我把它改成使用标准的仓储对象来处理就能够达到一样的目的了。
在项目其中对应位置,删除字典类型的一个业务对象接口和一个业务对象实现,改成标准仓储对象的接口处理,至关于把业务逻辑里面的代码提出来放在服务层而已,那么在应用服务层的处理代码以下所示。
[AbpAuthorize] public class DictTypeAppService : MyAsyncServiceBase<DictType, DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService { private readonly IRepository<DictType, string> _repository; public DictTypeAppService( IRepository<DictType, string> repository) : base(repository) { _repository = repository; } /// <summary> /// 获取全部字典类型的列表集合(Key为名称,Value为ID值) /// </summary> /// <returns></returns> public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { IList<DictType> list = null; if (!string.IsNullOrWhiteSpace(dictTypeId)) { list = await Repository.GetAllListAsync(p => p.PID == dictTypeId); } else { list = await Repository.GetAllListAsync(); } Dictionary<string, string> dict = new Dictionary<string, string>(); foreach (var info in list) { if (!dict.ContainsKey(info.Name)) { dict.Add(info.Name, info.Id); } } return dict; } ......
这样咱们少定义两个文件,以及减小协调业务类的代码,代码更加简洁和容易理解,反正最终实现都是基于仓储对象的接口调用。
另外,咱们继续了解项目,知道在Web.Host项目是咱们Web API层启动,且动态构建Web API层的服务层。它整合了Swagger对接口的测试使用。
// Swagger - Enable this line and the related lines in Configure method to enable swagger UI services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Title = "MyProject API", Version = "v1" }); options.DocInclusionPredicate((docName, description) => true); // Define the BearerAuth scheme that's in use options.AddSecurityDefinition("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 options.OperationFilter<SecurityRequirementsOperationFilter>(); });
启动项目,咱们能够看到Swagger的管理界面以下所示。