ABP框架背景知识介绍
ABP是ASP.NET Boilerplate的简称,ABP是一个开源且文档友好的应用程序框架。
ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型。学习使用ABP框架也有一段时间了,一直想全面了解下这个框架的整个来龙去脉,并把想把它使用历程整理成一个系列出来,不过一直没有下笔来写这篇文章的开篇,就是希望能够深入了解,再深入了解一些,希望自己能够理解透彻一些,不能误人子弟,也不想和网上千篇一律的翻译官网的内容,官网的英文介绍也已经很详细了,于是我觉得还是以实际使用的过程进行一定的整理会更好。
初次了解ABP框架,对它还是非常惊艳的,它基本上是.NET 领域架构的集大成者,几乎囊括了我们.NET领域排的上名的各种技术应用,而且它本身可以支持.net framework和.net core两种技术流派,对它的介绍也是非常感兴趣。
1)ABP框架的特点
我们来大概了解下ABP框架涉及到的内容。
- 依赖注入,这个部分使用 Castle windsor (依赖注入容器)来实现依赖注入,这个也是我们经常使用IOC来处理的方式;
- Repository仓储模式,已实现了Entity Framework、NHibernate、MangoDB、内存数据库等,仓储模式可以快速实现对数据接口的调用;
- 身份验证与授权管理,可以使用声明特性的方式对用户是否登录,或者接口的权限进行验证,可以通过一个很细粒度的方式,对各个接口的调用权限进行设置;
- 数据有效性验证,ABP自动对接口的输入参数对象进行非空判断,并且可以根据属性的申请信息对属性的有效性进行校验;
- 审计日志记录,也就是记录我们对每个接口的调用记录,以及对记录的创建、修改、删除人员进行记录等处理;
- Unit Of Work工作单元模式,为应用层和仓储层的方法自动实现数据库事务,默认所有应用服务层的接口,都是以工作单元方式运行,即使它们调用了不同的存储对象处理,都是处于一个事务的逻辑里面;
- 异常处理,ABP框架提供了一整套比较完善的流程处理操作,可以很方便的对异常进行进行记录和传递;
- 日志记录,我么可以利用Log4Net进行常规的日志记录,方便我们跟踪程序处理信息和错误信息;
- 多语言/本地化支持,ABP框架对多语言的处理也是比较友好的,提供了对XML、JSON语言信息的配置处理;
- Auto Mapping自动映射,这个是ABP的很重要的对象隔离概念,通过使用AutoMaper来实现域对象和DTO对象的属性映射,可以隔离两者的逻辑关系,但是又能轻松实现属性信息的赋值;
- 动态Web API层,利用这个动态处理,可以把Application Service 直接发布为Web API层,而不需要在累赘的为每个业务对象手工创建一个Web API的控制器,非常方便;
- 动态JavaScript的AJax代理处理,可以自动创建Javascript 的代理层来更方便使用Web Api,这个在Web层使用。
除了这些重要特性外,ABP框架还有很多一些特别的功能或者概念。 - 多租户支持(每个租户的数据自动隔离,业务模块开发者不需要在保存和查询数据时写相应代码;
- 软删除支持(继承相应的基类或实现相应接口,会自动实现软删除)
- 系统设置存取管理(系统级、租户级、用户级,作用范围自动管理)
- EventBus实现领域事件(Domain Events)
- 模块以及模块的依赖关系实现插件化的模块处理等等
ABP框架主要还是基于领域驱动的理念来构建整个架构
的,其中领域驱动包含的概念有 域对象Entities、仓储对象Repositories、域服务接口层Domain Services、域事件Domain Events、应用服务接口Application Services、数据传输对象DTOs等。
ABP官方网站:http://www.aspnetboilerplate.com,从里面可以查看很详细的案例和文档说明,可以根据需要下载不同类型的基础框架。
ABP GitHub源码地址:https://github.com/aspnetboilerplate,可以下载整个基础的框架内容,以及相关的样板案例代码。
1)ABP框架应用项目的介绍
整个基础的ABP框架看似非常庞大,其实很多项目也很少内容,主要是独立封装不同的组件进行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等内容,基本上我们主要关注的内容就是Abp这个主要的项目里面,其他的是针对不同的组件应用做的封装。
而基于基础ABP框架扩展出来的ABP应用项目,则简单很多,我们也是在需要用到不同组件的时候,才考虑引入对应的基础模块进行使用,一般来说,主要还是基于仓储管理实现基于数据库的应用,因此我们主要对微软的实体框架的相关内容了解清楚即可。
!
这个项目是一个除了包含基础的人员、角色、权限、认证、配置信息的基础项目外,而如果你从这里开始,对于其中的一些继承关系的了解,会增加很多困难,因为它们基础的用户、角色等对象关系实在是很复杂。
我建议从一个简单的项目开始,也就是基于一两个特定的应用表开始的项目,因此可以参考案例项目:eventcloud 或者 sample-blog-module 项目,我们入门理解起来可能更加清楚。这里我以eventcloud项目来进行分析项目中各个层的类之间的关系。
而基于基础ABP框架扩展出来的ABP应用项目,则简单很多,我们也是在需要用到不同组件的时候,才考虑引入对应的基础模块进行使用,一般来说,主要还是基于仓储管理实现基于数据库的应用
,因此我们主要对微软的实体框架的相关内容了解清楚即可。
我们先从一个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。
先以领域层,也就是项目中的EventCloud.Core
里面的内容进行分析。
2)领域对象层的代码分析
首先,我们需要了解领域对象和数据库之间的关系的类,也就是领域实体信息,这个类非常关键,它是构建仓储模式和数据库表之间的关系的。
[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; }
......
}
这个里面定义了领域实体和表名之间的关系,其他属性也就是对应数据库的字段了
[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)
{
}
}
}
简单的话,仓储模式就可以跑起来了:
1.我们利用 IRepository<Event, Guid> 接口就可以获取对应表的很多处理接口,包括增删改查、分页等等接口,
2.不过为了进行业务逻辑的隔离,我们引入了Application Service应用层,
3.同时也引入了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里面实现以下即可。
3)字典模块业务类的简化
我们以字典模块的字典类型表来介绍。
领域业务对象接口层定义如下所示(类似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的管理界面如下所示。
3)框架的分层和文件组织
3.1 ABP项目的改进结构
ABP官网文档里面,对自定义仓储类是不推荐的(除非找到合适的借口需要做),同时对领域对象的业务管理类,也是持保留态度,认为如果只有一个应用入口的情况(我主要考虑Web API优先),因此领域业务对象也可以不用自定义,因此我们整个ABP应用框架的思路就很清晰了,同时使用标准的仓储类,基本上可以解决绝大多数的数据操作。减少自定义业务管理类的目的是降低复杂度,同时我们把DTO对象和领域对象的映射关系抽离到应有服务层的AutoMapper的Profile文件中定义,这样可以简化DTO不依赖领域对象,因此DTO和应用服务层的接口可以共享给类似Winform、UWP/WPF、控制台程序等使用,避免重复定义,这点类似我们传统的Entity层。这里我强调一点,这样改进ABP框架,并没有改变整个ABP应用框架的分层和调用规则,只是尽可能的简化和保持公用的内容。
改进后的解决方案项目结构如下所示。
以上是VS里面解决方案的项目结构,我根据项目之间的关系,整理了一个架构的图形,如下所示。
上图中,其中橘红色部分就是我们为各个层添加的类或者接口,分层上的序号是我们需要逐步处理的内容,我们来逐一解读一下各个类或者接口的内容。
3.2 项目分层的代码
我们介绍的基于领域驱动处理,第一步就是定义领域实体和数据库表之间的关系,我这里以字典模块的表来进行举例介绍。
首先我们创建字典模块里面两个表,两个表的字段设计如下所示。
而其中我们Id是业务对象的主键,所有表都是统一的,两个表之间都有一部分重复的字段,是用来做操作记录的。
这个里面我们可以记录创建的用户ID、创建时间、修改的用户ID、修改时间、删除的信息等。
1)领域对象
例如我们定义字典类型的领域对象,如下代码所示。
在这里插入代码片
[Table("TB_DictType")]
public class DictType : FullAuditedEntity<string>
{
/// <summary>
/// 类型名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 字典代码
/// </summary>
public virtual string Code { get; set; }
/// <summary>
/// 父ID
/// </summary>
public virtual string PID { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; }
/// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; }
}
其中FullAuditedEntity<string>
代表我需要记录对象的增删改时间和用户信息
,当然还有AuditedEntity和CreationAuditedEntity
基类对象,来标识记录信息的不同。
字典数据的领域对象定义如下所示。
[Table("TB_DictData")]
public class DictData : FullAuditedEntity<string>
{
/// <summary>
/// 字典类型ID
/// </summary>
[Required]
public virtual string DictType_ID { get; set; }
/// <summary>
/// 字典大类
/// </summary>
[ForeignKey("DictType_ID")]
public virtual DictType DictType { get; set; }
/// <summary>
/// 字典名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 字典值
/// </summary>
public virtual string Value { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; }
/// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; }
}
这里注意我们有一个外键DictType_ID,同时有一个DictType对象的信息,这个我们使用仓储对象操作就很方便获取到对应的字典类型对象了。
[ForeignKey("DictType_ID")]
public virtual DictType DictType { get; set; }
2)EF的仓储核心层
这个部分我们基本上不需要什么改动,我们只需要加入我们定义好的仓储对象DbSet即可,如下所示。
public class MyProjectDbContext : AbpZeroDbContext<Tenant, Role, User, MyProjectDbContext>
{
//字典内容
public virtual DbSet<DictType> DictType { get; set; }
public virtual DbSet<DictData> DictData { get; set; }
public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options)
: base(options)
{
}
}
通过上面代码,我们可以看到,我们每加入一个领域对象实体,在这里就需要增加一个DbSet的对象属性,至于它们是如何协同处理仓储模式的
,我们可以暂不关心它的机制。
3)应用服务通用层
这个项目分层里面,我们主要放置在各个模块里面公用的DTO和应用服务接口类
。
例如我们定义字典类型的DTO对象,如下所示,这里涉及的DTO,没有使用AutoMapper的标记。
/// <summary>
/// 字典对象DTO
/// </summary>
public class DictTypeDto : EntityDto<string>
{
/// <summary>
/// 类型名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 字典代码
/// </summary>
public virtual string Code { get; set; }
/// <summary>
/// 父ID
/// </summary>
public virtual string PID { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; }
/// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; }
}
字典类型的应用服务层接口定义如下所示。
public interface IDictTypeAppService : IAsyncCrudAppService<DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>
{
/// <summary>
/// 获取所有字典类型的列表集合(Key为名称,Value为ID值)
/// </summary>
/// <param name="dictTypeId">字典类型ID,为空则返回所有</param>
/// <returns></returns>
Task<Dictionary<string, string>> GetAllType(string dictTypeId);
/// <summary>
/// 获取字典类型一级列表及其下面的内容
/// </summary>
/// <param name="pid">如果指定PID,那么找它下面的记录,否则获取所有</param>
/// <returns></returns>
Task<IList<DictTypeNodeDto>> GetTree(string pid);
}
从上面的接口代码,我们可以看到,字典类型的接口基类是基于异步CRUD操作的基类接口IAsyncCrudAppService
,这个是在ABP核心项目的Abp.ZeroCore项目里面,使用它需要引入对应的项目依赖
而基于IAsyncCrudAppService
的接口定义,我们往往还需要多定义几个DTO对象,如创建对象、更新对象、删除对象、分页对象等等。
如字典类型的创建对象DTO类定义如下所示,由于操作内容没有太多差异,我们可以简单的继承自DictTypeDto即可。
/// <summary>
/// 字典类型创建对象
/// </summary>
public class CreateDictTypeDto : DictTypeDto
{
}
IAsyncCrudAppService
定义了几个通用的创建、更新、删除、获取单个对象和获取所有对象列表的接口
,接口定义如下所示。
namespace Abp.Application.Services
{
public interface IAsyncCrudAppService<TEntityDto, TPrimaryKey, in TGetAllInput, in TCreateInput, in TUpdateInput, in TGetInput, in TDeleteInput> : IApplicationService, ITransientDependency
where TEntityDto : IEntityDto<TPrimaryKey>
where TUpdateInput : IEntityDto<TPrimaryKey>
where TGetInput : IEntityDto<TPrimaryKey>
where TDeleteInput : IEntityDto<TPrimaryKey>
{
Task<TEntityDto> Create(TCreateInput input);
Task Delete(TDeleteInput input);
Task<TEntityDto> Get(TGetInput input);
Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input);
Task<TEntityDto> Update(TUpdateInput input);
}
}
而由于这个接口定义了这些通用处理接口,我们在做应用服务类的实现的时候,都往往基于基类AsyncCrudAppService,默认具有以上接口的实现。
同理,对于字典数据对象的操作类似,我们创建相关的DTO对象和应用服务层接口。
/// <summary>
/// 字典数据的DTO
/// </summary>
public class DictDataDto : EntityDto<string>
{
/// <summary>
/// 字典类型ID
/// </summary>
[Required]
public virtual string DictType_ID { get; set; }
/// <summary>
/// 字典名称
/// </summary>
[Required]
public virtual string Name { get; set; }
/// <summary>
/// 指定值
/// </summary>
public virtual string Value { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; }
/// <summary>
/// 排序
/// </summary>
public virtual string Seq { get; set; }
}
/// <summary>
/// 创建字典数据的DTO
/// </summary>
public class CreateDictDataDto : DictDataDto
{
}
/// <summary>
/// 字典数据的应用服务层接口
/// </summary>
public interface IDictDataAppService : IAsyncCrudAppService<DictDataDto, string, PagedResultRequestDto, CreateDictDataDto, DictDataDto>
{
/// <summary>
/// 根据字典类型ID获取所有该类型的字典列表集合(Key为名称,Value为值)
/// </summary>
/// <param name="dictTypeId">字典类型ID</param>
/// <returns></returns>
Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId);
/// <summary>
/// 根据字典类型名称获取所有该类型的字典列表集合(Key为名称,Value为值)
/// </summary>
/// <param name="dictType">字典类型名称</param>
/// <returns></returns>
Task<Dictionary<string, string>> GetDictByDictType(string dictTypeName);
}
4)应用服务层实现
应用服务层是整个ABP框架的灵魂
所在:
1.对内协同仓储对象实现数据的处理,
2.对外配合Web.Core、Web.Host项目提供Web API的服务(而Web.Core、Web.Host项目几乎不需要进行修改),
3.因此应用服务层就是一个非常关键的部分,需要考虑对用户登录的验证、接口权限的认证、以及对审计日志的记录处理,
以及异常的跟踪和传递,基本上应用服务层就是一个大内总管的角色,重要性不言而喻。
应用服务层只需要根据应用服务通用层的DTO和服务接口,利用标准的仓储对象进行数据的处理调用即可。
如对于字典类型的应用服务层实现类代码如下所示。
/// <summary>
/// 字典类型应用服务层实现
/// </summary>
[AbpAuthorize]
public class DictTypeAppService : MyAsyncServiceBase<DictType, DictTypeDto, string, PagedResultRequestDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService
{
/// <summary>
/// 标准的仓储对象
/// </summary>
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;
}
/// <summary>
/// 获取字典类型一级列表及其下面的内容
/// </summary>
/// <param name="pid">如果指定PID,那么找它下面的记录,否则获取所有</param>
/// <returns></returns>
public async Task<IList<DictTypeNodeDto>> GetTree(string pid)
{
//确保PID非空
pid = string.IsNullOrWhiteSpace(pid) ? "-1" : pid;
List<DictTypeNodeDto> typeNodeList = new List<DictTypeNodeDto>();
var topList = Repository.GetAllList(s => s.PID == pid).MapTo<List<DictTypeNodeDto>>();//顶级内容
foreach(var dto in topList)
{
var subList = Repository.GetAllList(s => s.PID == dto.Id).MapTo<List<DictTypeNodeDto>>();
if (subList != null && subList.Count > 0)
{
dto.Children.AddRange(subList);
}
}
return await Task.FromResult(topList);
}
}
1.我们可以看到,标准的增删改查操作,我们不需要实现,因为已经在基类应用服务类`AsyncCrudAppService`,默认具有这些接口的实现。
2.而我们在类的时候,看到一个声明的标签[AbpAuthorize],就是对这个服务层的访问,需要用户的授权登录才可以访问。
5)Web.Host Web API宿主层
如我们在Web.Host项目里面启动的Swagger接口测试页面里面,就是需要先登录的。