在前面随笔《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)框架的初步介绍的更多相关文章

  1. ABP开发框架前后端开发系列---(3)框架的分层和文件组织

    在前面随笔<ABP开发框架前后端开发系列---(2)框架的初步介绍>中,我介绍了ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以便基于数据库应用的简化处理.本篇随笔进一步对 ...

  2. ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理

    在前面两篇随笔<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>和<ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程>开始 ...

  3. ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程

    在前面随笔介绍的<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>里面,介绍了如何改进和完善审计日志和登录日志的应用服务端和Winform客户端,由于篇幅限制,没有进 ...

  4. ABP开发框架前后端开发系列---(16)ABP框架升级最新版本的经验总结

    有一小段时间没有持续升级ABP框架了,最近就因应客户的需要,把ABP框架进行全面的更新,由于我们应用的ABP框架,基础部分还是会使用官方的内容,因此升级的时候需要把官方基础ABP的DLL进行全面的更新 ...

  5. ABP开发框架前后端开发系列---(10)Web API调用类的简化处理

    在较早期的随笔<ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用>已经介绍了Web API调用类的封装处理,虽然这些调用类我们可以使用代码生成工具快 ...

  6. ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用

    在前面几篇随笔介绍了我对ABP框架的改造,包括对ABP总体的介绍,以及对各个业务分层的简化,Web API 客户端封装层的设计,使得我们基于ABP框架的整体方案越来越清晰化, 也越来越接近实际的项目开 ...

  7. ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用

    在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰.上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过 ...

  8. ABP开发框架前后端开发系列---(11)菜单的动态管理

    在前面随笔<ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理>中介绍了基于ABP框架服务构建的Winform客户端,客户端通过Web API调用的方式进行获取数据,从而实现 ...

  9. ABP开发框架前后端开发系列---(12)配置模块的管理

    一般来说,一个系统或多或少都会涉及到一些系统参数或者用户信息的配置,而ABP框架也提供了一套配置信息的管理模块,ABP框架的配置信息,必须提前定义好配置的各项内容,然后才能在系统中初始化或者通过接口查 ...

随机推荐

  1. php 计算当天凌晨时间戳 以及获取其他常用时间戳

    php 计算当日凌晨时间戳 以及获取其他常用时间戳(持续补充中...) 获取当天凌晨时间戳: echo strtotime(date('Y-m-d')); 以下再列举一些获取其他常用时间戳的方法 获取 ...

  2. Python爬取全站妹子图片,差点硬盘走火了!

    在这严寒的冬日,为了点燃我们的热情,今天小编可是给大家带来了偷偷收藏了很久的好东西.大家要注意点哈,我第一次使用的时候,大意导致差点坏了大事哈! 1.所需库安装 2.网站分析 首先打开妹子图的官网(m ...

  3. bash脚本编写基础

    bash脚本编程     命令的堆砌     脚本程序:解释器解析执行     shell:交互式接口,编程环境         shell:能够提供一些内部命令,并且能通过PATH环境变量找到外部命 ...

  4. Report Server multiple value 多值选择

    一.项目需求 今天在做项目的时候,有一个需求,具体如下:在Report Server中存在一个报表,报表中有一个参数doctor_name,该参数允许多值,默认全部.但是由于前端页面医生选择时多选没有 ...

  5. Hive中集合类型

    Hive中集合类型 创建表,集合是以 - 分割的 数据文件 加载数据 查询数据 查询数组中第一个字段 再建一个表,使用map 查看数据文件 加载数据 查询数据 查询键值 创建表,struct类型 查看 ...

  6. js---post与get请求的区别

    request获取请求参数 最为常见的客户端传递参数方式有两种: 浏览器地址栏直接输入:一定是GET请求: 超链接:一定是GET请求: 表单:可以是GET,也可以是POST,这取决与<form& ...

  7. acdsee 15中文版的许可证密钥+激活方法

    按以下方法就可以使用了,进入注册表的命令是:开始-运行-输入regedit   进入注册表后按下面的步骤操作就行. ACDSee15中文版激活英文版激活码5NR9CW-SSRMMY-KFWMQU-ZP ...

  8. 一个Work Stealing Pool线程池的实现

    一.一般来说实现一个线程池主要包括以下几个组成部分: 1)线程管理器 :用于创建并管理线程池 . 2)工作线程 :线程池中实际执行任务的线程 . 在初始化线程时会预先创建好固定数目的线程在池中 ,这些 ...

  9. 21 段实用便捷的 PHP 代码

    PHP 是目前使用最广泛的基于 Web 的编程语言,驱动着数以百万计的网站,其中也包括如 Facebook 等一些大型站点.这里收集了 21 段实用便捷的 PHP 代码摘录,对每种类型的 PHP 开发 ...

  10. 精通CSS高级Web标准解决方案(2-2 可视化格式模型之定位概述)

    视觉格式化模型 块级元素(块框).行内元素(行内框),可以使用display改变生成的框的类型,display:block让行内元素(比如<a>)表现的跟块级元素一样,display:no ...