ABP开发框架前后端开发系列---(2)框架的初步介绍
在前面随笔《ABP开发框架前后端开发系列---(1)框架的总体介绍》大概介绍了这个ABP框架的主要特点,以及介绍了我对这框架的Web API应用优先的一些看法,本篇继续探讨ABP框架的初步使用,也就是我们下载到的ABP框架项目(基于ABP基础项目的扩展项目),如果理解各个组件模块,以及如何使用。
1)ABP框架应用项目的介绍
整个基础的ABP框架看似非常庞大,其实很多项目也很少内容,主要是独立封装不同的组件进行使用,如Automaper、SignalR、MongoDB、Quartz。。。等等内容,基本上我们主要关注的内容就是Abp这个主要的项目里面,其他的是针对不同的组件应用做的封装。

而基于基础ABP框架扩展出来的ABP应用项目,则简单很多,我们也是在需要用到不同组件的时候,才考虑引入对应的基础模块进行使用,一般来说,主要还是基于仓储管理实现基于数据库的应用,因此我们主要对微软的实体框架的相关内容了解清楚即可。

这个项目是一个除了包含基础的人员、角色、权限、认证、配置信息的基础项目外,而如果你从这里开始,对于其中的一些继承关系的了解,会增加很多困难,因为它们基础的用户、角色等对象关系实在是很复杂。
我建议从一个简单的项目开始,也就是基于一两个特定的应用表开始的项目,因此可以参考案例项目:eventcloud 或者 sample-blog-module 项目,我们入门理解起来可能更加清楚。这里我以eventcloud项目来进行分析项目中各个层的类之间的关系。

我们先从一个关系图来了解下框架下的领域驱动模块中的各个类之间的关系。

先以领域层,也就是项目中的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)
{
}
}
}
简单的话,仓储模式就可以跑起来了,我们利用 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里面实现以下即可。
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的管理界面如下所示。

ABP开发框架前后端开发系列---(2)框架的初步介绍的更多相关文章
- ABP开发框架前后端开发系列---(3)框架的分层和文件组织
在前面随笔<ABP开发框架前后端开发系列---(2)框架的初步介绍>中,我介绍了ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以便基于数据库应用的简化处理.本篇随笔进一步对 ...
- ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理
在前面两篇随笔<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>和<ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程>开始 ...
- ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程
在前面随笔介绍的<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>里面,介绍了如何改进和完善审计日志和登录日志的应用服务端和Winform客户端,由于篇幅限制,没有进 ...
- ABP开发框架前后端开发系列---(16)ABP框架升级最新版本的经验总结
有一小段时间没有持续升级ABP框架了,最近就因应客户的需要,把ABP框架进行全面的更新,由于我们应用的ABP框架,基础部分还是会使用官方的内容,因此升级的时候需要把官方基础ABP的DLL进行全面的更新 ...
- ABP开发框架前后端开发系列---(10)Web API调用类的简化处理
在较早期的随笔<ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用>已经介绍了Web API调用类的封装处理,虽然这些调用类我们可以使用代码生成工具快 ...
- ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用
在前面几篇随笔介绍了我对ABP框架的改造,包括对ABP总体的介绍,以及对各个业务分层的简化,Web API 客户端封装层的设计,使得我们基于ABP框架的整体方案越来越清晰化, 也越来越接近实际的项目开 ...
- ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用
在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰.上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过 ...
- ABP开发框架前后端开发系列---(11)菜单的动态管理
在前面随笔<ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理>中介绍了基于ABP框架服务构建的Winform客户端,客户端通过Web API调用的方式进行获取数据,从而实现 ...
- ABP开发框架前后端开发系列---(12)配置模块的管理
一般来说,一个系统或多或少都会涉及到一些系统参数或者用户信息的配置,而ABP框架也提供了一套配置信息的管理模块,ABP框架的配置信息,必须提前定义好配置的各项内容,然后才能在系统中初始化或者通过接口查 ...
随机推荐
- 【laravel】Laravel 5 TokenMismatchException on PHP 5.6.9
When I realized this was only happening in IE and Chrome, but not Firefox, it led me to the fix. The ...
- Python基础——类
创建类 class people: '帮助信息:dsafdaf' #所有实例都会共享的 number=100 #构造函数,初始化的方法,当创建一个类的时候,首先会调用它 def __init__(se ...
- LeetCode(189) Rotate Array
题目 Rotate an array of n elements to the right by k steps. For example, with n = 7 and k = 3, the arr ...
- UVa 1629 DP Cake slicing
题意: 一块n×m的蛋糕上有若干个樱桃,要求切割若干次以后,每块蛋糕上有且仅有1个樱桃.求最小的切割长度. 分析: d(u, d, l, r)表示切割矩形(u, d, l, r)所需要的最小切割长度. ...
- [git 学习篇] git checkout 撤销修改
git status 查看当前创库情况 liuzhipeng@exdroid43:~/pad/pad-test$ git status 位于分支 master 您的分支与上游分支 'origin/ma ...
- [git 学习篇]版本回退
再次修改readme.txt ,并将其提交成功 $ git add readme.txt $ git commit -m "append GPL" [master ] append ...
- Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined)
我真的是太菜了 A. Perfect Squares time limit per test 1 second memory limit per test 256 megabytes input st ...
- Linux基础之Linux简介
Linux(英语发音:/ˈlɪnəks/ lin-əks)是一种自由和开放源代码的类UNIX操作系统. Linux简介 Linux(英语发音:/ˈlɪnəks/ lin-əks)是一种自由和开放源代码 ...
- 都系坤坤-微信助手V 0.1,解放双手发信息
端午节刚过,相信大家在端午节都收到不少微信祝福信息,有复制长篇大论的祝福语群发的,有人工手打的简单祝福群发,我更喜欢人工手打带上称呼的祝福信息,这样看起来更加亲切. 那么问题来了,当你的通讯录里好友多 ...
- Java学习——个人错误集(1)
[General] 1.构造函数没有返回值,连void也没有. [多态Polymorphism]