OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架。这个框架使用最新稳定版的.NetCore SDK(当前是.NET Core 2.2),对 AspNetCore 的配置、依赖注入、日志、缓存、实体框架、Mvc(WebApi)、身份认证、权限授权等模块进行更高一级的自动化封装,并规范了一套业务实现的代码结构与操作流程,使 .Net Core 框架更易于应用到实际项目开发中
向 API层 提供各个实体的数据查询的 IQueryable
接收 API层 传入的 IInputDto 参数,完成实体的 新增、更新、删除 等业务操作
接收 API层 传入的参数,处理各种 模块级别 的综合业务
处理完业务之后,将数据通过 数据仓储IRepository 更新到数据库
向 事件总线 模块发布业务处理事件,触发订阅的业务事件
向 API 层返回业务操作结果
整个过程如下图所示:
服务层代码布局
服务层代码布局分析
一个业务模块,是负责完成一系列功能的,这些功能相互之间具有密切的关联性,所以对于一个模块来说,业务服务是一个整体,不应把他们再按单个实体拆分开来。
OSharp 的业务模块代码结构设计,也是根据这一原则来设计的。设计规则如下:
服务接口IBlogsContract:一个模块的业务服务共享一个服务接口,接口中包含模块的综合业务服务,也包含模块的各个实体的查询数据集、新增、更新、删除等自有业务服务。
服务实现BlogsService:服务实现使用 分部类partial 设计,例如本例中的博客模块业务,文件拆分如下:
BlogsService.cs:博客模块服务实现类的主文件,负责各实体的仓储服务注入,辅助服务注入,模块综合业务实现
BlogsService.Blog.cs:博客模块服务的博客实体服务实现类,负责博客实体的 查询数据集、增改删 业务实现
BlogsService.Post.cs:博客模块服务的文章实体服务实现类,负责文章实体的 查询数据集、增改删 业务实现
模块入口BlogsPack:定义模块的级别、启动顺序、执行服务添加、模块初始化等功能
综上,服务层代码布局如下所示:
src # 源代码文件夹
└─Liuliu.Blogs.Core # 项目核心工程
└─Blogs # 博客模块文件夹
├─Events # 业务事件文件夹
│ ├─VerifyBlogEventData.cs # 审核博客事件数据
│ └─VerifyBlogEventHandler.cs # 审核博客事件处理器
├─BlogsPack.cs # 博客模块入口类
├─BlogsService.cs # 博客服务类
├─BlogsService.Blog.cs # 博客模块-博客服务类
├─BlogsService.Post.cs # 博客模块-文章服务类
└─IBlogsContract.cs # 博客模块服务接口
服务接口 IBlogsContract
接口定义分析
数据查询
对于数据查询,业务层只向 API 层开放一个 IQueryable
额外的,根据一定条件判断一个数据是否存在 这种需求经常会用到(例如在新增或修改一个要求唯一的字符串时,需要异步检查输入的字符串是否已存在),因此设计一个 检查实体是否存在CheckEntityExists 的服务很有必要。
!!!node
对于新增、更新、删除操作,除非很确定一次只操作一条记录除外,为了支持可能的批量操作,设计上都应把服务层的 增改删 操作设计为数组型参数的批量操作,同时使用 params 关键字使操作支持单个数据操作。
数据变更
对于每一个实体,服务层按 业务需求分析 的要求定义必要的 新增、更新、删除 等操作,OSharp框架定义了一个 业务操作结果信息类 OperationResult 来封装业务操作结果,这个结果可以返回 操作结果类型(成功/错误/未变化/不存在/验证失败)、返回消息、返回附加数据 等丰富的信息,API层 接受操作结果后可进行相应的处理。
博客模块的接口定义
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#服务层> 的需求分析,我们需要给 博客Blog 实体定义 申请开通、开通审核、更新、删除 服务,给 文章Post 实体类定义 新增、更新、删除 服务。
接口定义如下:
///
/// 业务契约接口:博客模块
///
public interface IBlogsContract
{
#region 博客信息业务
///
/// 获取 博客信息查询数据集
///
IQueryable
///
/// 检查博客信息是否存在
///
/// 检查谓语表达式
/// 更新的博客信息编号
///
Task
///
/// 申请博客信息
///
/// 申请博客信息DTO信息
///
Task
///
/// 审核博客信息
///
/// 博客编号
/// 是否通过
///
Task
///
/// 更新博客信息
///
/// 包含更新信息的博客信息DTO信息
///
Task
///
/// 删除博客信息
///
/// 要删除的博客信息编号
///
Task
#endregion
#region 文章信息业务
///
/// 获取 文章信息查询数据集
///
IQueryable
///
/// 检查文章信息是否存在
///
/// 检查谓语表达式
/// 更新的文章信息编号
///
Task
///
/// 添加文章信息
///
/// 要添加的文章信息DTO信息
///
Task
///
/// 更新文章信息
///
/// 包含更新信息的文章信息DTO信息
///
Task
///
/// 删除文章信息
///
/// 要删除的文章信息编号
///
Task
#endregion
}
服务实现 BlogsService
依赖服务注入方式分析
服务层的业务实现,通过向服务实现类注入 数据仓储IRepository
///
/// 业务服务实现:博客模块
///
public class BlogsService : IBlogsContract
{
private readonly IRepository
private readonly IRepository
private readonly IRepository
private readonly IRepository
private readonly IRepository
private readonly IEventBus _eventBus;
///
/// 初始化一个
///
public BlogsService(IRepository
IRepository
IRepository
IRepository
IRepository
IEventBus eventBus)
{
_blogRepository = blogRepository;
_postRepository = postRepository;
_userRepository = userRepository;
_roleRepository = roleRepository;
_userRoleRepository = userRoleRepository;
_eventBus = eventBus;
}
}
构造函数注入带来的性能影响
每个仓储都使用构造函数注入的话,如果模块的业务比较复杂,涉及的实体比较多(比如十几个实体是很经常的事),就会造成每次实例化 BlogsService 类的实例的时候,都需要去实例化很多个依赖服务,而实际上 一次业务执行只执行服务中的某个方法,可能也就用到其中的一两个依赖服务,这就造成了很多不必要的额外工作,也就是性能损耗。
依赖服务注入的性能优化
如果 不考虑业务服务的可测试性(单元测试通常需要Mock依赖服务)的话,在构造函数中只注入 IServiceProvider 实例,然后在业务代码中使用 serviceProvider.GetService
///
/// 业务服务实现:博客模块
///
public class BlogsService : IBlogsContract
{
private readonly IServiceProvider _serviceProvider;
///
/// 初始化一个
///
public BlogsService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
///
/// 获取 博客仓储对象
///
protected IRepository
///
/// 获取 文章仓储对象
///
protected IRepository
///
/// 获取 用户仓储对象
///
protected IRepository
///
/// 获取 角色仓储对象
///
protected IRepository
///
/// 获取 角色仓储对象
///
protected IRepository
///
/// 获取 事件总线对象
///
protected IEventBus EventBus => _serviceProvider.GetService
}
各个依赖服务改为属性的存在方式,并且可访问性为 protected,这就保证了依赖服务的安全性。依赖服务使用 serviceProvider.GetService
增改删操作的简化
常规批量操作的弊端
直接通过 数据仓储IRepository
由 dto.Id 查找出相应的文章实体 entity,如果不存在,中止操作并返回
进行更新前的数据检查
检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性,中止操作并返回
检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截,检查不通过,中止操作并返回
使用 AutoMapper 将 dto 的值更新到 entity
进行其他关联实体的更新
比如添加文章的编辑记录
比如给当前操作人加积分
将 entity 的更新提交到数据库
整个过程实现代码如下:
///
/// 更新文章信息
///
/// 包含更新信息的文章信息DTO信息
///
public virtual async Task
{
Check.Validate
int count = 0;
foreach (PostInputDto dto in dtos)
{
Post entity = await PostRepository.GetAsync(dto.Id);
if (entity == null)
{
return new OperationResult(OperationResultType.QueryNull, $"编号为{dto.Id}的文章信息无法找到");
}
// todo:
// 在这里要检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性
// 在这里要检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截
entity = dto.MapTo(entity);
// todo:
// 在这里要进行其他实体的关联更新,比如添加文章的编辑记录
count += await PostRepository.UpdateAsync(entity);
}
if (count > 0)
{
return new OperationResult(OperationResultType.Success, $"{dtos.Length}个文章信息更新成功");
}
return OperationResult.NoChanged;
}
批量操作改进
这是个重复性很大的繁琐工作,整个流程中只有第2步和第4步是变化的,其余步骤都相对固定。为了简化这类操作,我们可以将第2、4步骤变化点封装起来,使用 委托 将操作内容作为参数传进来。
OSharp在 数据仓储IRepository
例如批量更新,实现如下:
///
/// 异步以DTO为载体批量更新实体
///
///
/// 更新DTO信息集合
/// 更新信息合法性检查委托
/// 由DTO到实体的转换委托
///
public virtual async Task
Func
Func
{
List
foreach (TEditDto dto in dtos)
{
try
{
TEntity entity = await _dbSet.FindAsync(dto.Id);
if (entity == null)
{
return new OperationResult(OperationResultType.QueryNull);
}
if (checkAction != null)
{
await checkAction(dto, entity);
}
entity = dto.MapTo(entity);
if (updateFunc != null)
{
entity = await updateFunc(dto, entity);
}
entity = CheckUpdate(entity)[0];
_dbContext.Update
}
catch (OsharpException e)
{
return new OperationResult(OperationResultType.Error, e.Message);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return new OperationResult(OperationResultType.Error, e.Message);
}
names.AddIfNotNull(GetNameValue(dto));
}
int count = await _dbContext.SaveChangesAsync(_cancellationTokenProvider.Token);
return count > 0
? new OperationResult(OperationResultType.Success,
names.Count > 0
? "信息“{0}”更新成功".FormatWith(names.ExpandAndToString())
: "{0}个信息更新成功".FormatWith(dtos.Count))
: new OperationResult(OperationResultType.NoChanged);
}
如上高亮代码,此方法定义了 Func
///
/// 更新文章信息
///
/// 包含更新信息的文章信息DTO信息
///
public virtual async Task
{
Check.Validate
return await PostRepository.UpdateAsync(dtos, async (dto, entity) =>
{
// todo:
// 在这里要检查 dto 的合法性,比如文章标题要求唯一,dto.Title 就要验证唯一性
// 在这里要检查 entity 的合法性,比如文章已锁定,就不允许编辑,要进行拦截
},
async (dto, entity) =>
{
// todo:
// 在这里要进行其他实体的关联更新,比如添加文章的编辑记录
return entity;
});
}
如果没有必要做额外的 更新前检查 和更新后的 关联更新,上面的批量更新可以简化到极致:
///
/// 更新文章信息
///
/// 包含更新信息的文章信息DTO信息
///
public virtual async Task
{
Check.Validate
return await PostRepository.UpdateAsync(dtos);
}
服务层的事务管理
事务开启与重用
OSharp的数据层在一次业务处理请求中遇到数据的 新增、更新、删除 操作并第一次执行 SaveChanges 操作时,会自动开启手动事务,以后再次执行 SaveChanges 操作时,会直接使用 同一连接对象 的现有事务,以保证一次业务请求的操作都自在一个事务内。
public override int SaveChanges()
{
// ...
//开启或使用现有事务
BeginOrUseTransaction();
int count = base.SaveChanges();
// ...
return count;
}
事务提交
为了方便事务管理和不同的服务层之间的事务同步,OSharp框架默认的事务提交是在 API 层通过 MVC 的 UnitOfWorkAttribute 特性来提交的。
///
/// 新用户注册
///
/// 注册信息
///
[HttpPost]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[ModuleInfo]
[Description("用户注册")]
public async Task
{
// ...
}
当然,你也可以不在 API 层标注 [UnitOfWorkAttribute],而是在需要的时候通过 IUnitOfWork.Commit() 手动提交事务
IUnitOfWork unitOfWork = HttpContext.RequestServices.GetUnitOfWork
unitOfWork.Commit();
业务服务事件订阅与发布
业务服务事件,是通过 事件总线EventBus 来实现的,OSharp构建了一个简单的事件总线基础建设,可以很方便地订阅和发布业务事件。
订阅事件
订阅事件很简单,只需要定义一组配套的 事件数据EventData 和相应的 事件处理器EventHandler,即可完成事件订阅的工作。
IEventData
事件数据EventData 是业务服务发布事件时向事件总线传递的数据,每一种业务,都有特定的事件数据,一个事件数据可触发多个事件处理器
定义一个事件数据,需要实现 IEventData 接口
///
/// 定义事件数据,所有事件都要实现该接口
///
public interface IEventData
{
///
/// 获取 事件编号
///
Guid Id { get; }
///
/// 获取 事件发生的时间
///
DateTime EventTime { get; }
///
/// 获取或设置 事件源,触发事件的对象
///
object EventSource { get; set; }
}
EventDataBase
为了方便 事件数据 的定义,OSharp定义了一个通用事件数据基类EventDataBase,继承此基类,只需要添加事件触发需要的业务数据即可
///
/// 事件源数据信息基类
///
public abstract class EventDataBase : IEventData
{
///
/// 初始化一个
///
protected EventDataBase()
{
Id = Guid.NewGuid();
EventTime = DateTime.Now;
}
///
/// 获取 事件编号
///
public Guid Id { get; }
///
/// 获取 事件发生时间
///
public DateTime EventTime { get; }
///
/// 获取或设置 触发事件的对象
///
public object EventSource { get; set; }
}
IEventHandler
业务事件的处理逻辑,是通过 事件处理器 EventHandler 来实现的,事件处理器应遵从 单一职责 原则,一个处理器只做一件事,业务服务层发布一项 事件数据,可触发多个 事件处理器
///
/// 定义事件处理器,所有事件处理都要实现该接口
/// EventBus中,Handler的调用是同步执行的,如果需要触发就不管的异步执行,可以在实现EventHandler的Handle逻辑时使用Task.Run
///
[IgnoreDependency]
public interface IEventHandler
{
///
/// 是否可处理指定事件
///
/// 事件源数据
///
bool CanHandle(IEventData eventData);
///
/// 事件处理
///
/// 事件源数据
void Handle(IEventData eventData);
///
/// 异步事件处理
///
/// 事件源数据
/// 异步取消标识
///
Task HandleAsync(IEventData eventData, CancellationToken cancelToken = default(CancellationToken));
}
泛型事件处理器
///
/// 定义泛型事件处理器
/// EventBus中,Handler的调用是同步执行的,如果需要触发就不管的异步执行,可以在实现EventHandler的Handle逻辑时使用Task.Run
///
///
[IgnoreDependency]
public interface IEventHandler
{
///
/// 事件处理
///
/// 事件源数据
void Handle(TEventData eventData);
///
/// 异步事件处理
///
/// 事件源数据
/// 异步取消标识
Task HandleAsync(TEventData eventData, CancellationToken cancelToken = default(CancellationToken));
}
EventHandlerBase
同样的,为了方便 事件处理器 的定义,OSharp定义了一个通用的事件处理器基类EventHandlerBase
///
/// 事件处理器基类
///
public abstract class EventHandlerBase
{
///
/// 是否可处理指定事件
///
/// 事件源数据
///
public virtual bool CanHandle(IEventData eventData)
{
return eventData.GetType() == typeof(TEventData);
}
///
/// 事件处理
///
/// 事件源数据
public virtual void Handle(IEventData eventData)
{
if (!CanHandle(eventData))
{
return;
}
Handle((TEventData)eventData);
}
///
/// 异步事件处理
///
/// 事件源数据
/// 异步取消标识
///
public virtual Task HandleAsync(IEventData eventData, CancellationToken cancelToken = default(CancellationToken))
{
if (!CanHandle(eventData))
{
return Task.FromResult(0);
}
return HandleAsync((TEventData)eventData, cancelToken);
}
///
/// 事件处理
///
/// 事件源数据
public abstract void Handle(TEventData eventData);
///
/// 异步事件处理
///
/// 事件源数据
/// 异步取消标识
///
public virtual Task HandleAsync(TEventData eventData, CancellationToken cancelToken = default(CancellationToken))
{
return Task.Run(() => Handle(eventData), cancelToken);
}
}
发布事件
事件的发布,就相当简单了,只需要实例化一个事件数据EventData的实例,然后通过IEventBus.Publish(eventData)即可发布事件,触发该EventData的所有订阅处理器
XXXEventData eventData = new XXXEventData()
{
// ...
};
EventBus.Publish(eventData);
博客模块的业务事件实现
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#博客业务需求分析> 的需求分析的第二条,审核博客之后需要发邮件通知用户,发邮件属于审核博客业务计划外的需求,使用 业务事件 来实现正当其时。
审核博客业务事件数据
///
/// 审核博客事件数据
///
public class VerifyBlogEventData : EventDataBase
{
///
/// 获取或设置 博客名称
///
public string BlogName { get; set; }
///
/// 获取或设置 用户名
///
public string UserName { get; set; }
///
/// 获取或设置 审核是否通过
///
public bool IsEnabled { get; set; }
}
审核博客业务事件处理器
///
/// 审核博客事件处理器
///
public class VerifyBlogEventHandler : EventHandlerBase
{
private readonly ILogger _logger;
///
/// 初始化一个
///
public VerifyBlogEventHandler(IServiceProvider serviceProvider)
{
_logger = serviceProvider.GetService
}
///
/// 事件源数据
public override void Handle(VerifyBlogEventData eventData)
{
_logger.LogInformation(
$"触发 审核博客事件处理器,用户“{eventData.UserName}”的博客“{eventData.BlogName}”审核结果:{(eventData.IsEnabled ? "通过" : "未通过")}");
}
}
博客模块的服务实现
回到我们的 Liuliu.Blogs 项目,根据 <业务模块设计#服务层> 的需求分析,综合使用OSharp框架提供的基础建设,博客模块的业务服务实现如下:
BlogsService.cs
///
/// 业务服务实现:博客模块
///
public partial class BlogsService : IBlogsContract
{
private readonly IServiceProvider _serviceProvider;
///
/// 初始化一个
///
public BlogsService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
///
/// 获取 博客仓储对象
///
protected IRepository
///
/// 获取 文章仓储对象
///
protected IRepository
///
/// 获取 用户仓储对象
///
protected IRepository
}
BlogsService.Blog.cs
public partial class BlogsService
{
///
/// 获取 博客信息查询数据集
///
public virtual IQueryable
///
/// 检查博客信息是否存在
///
/// 检查谓语表达式
/// 更新的博客信息编号
///
public virtual Task
{
Check.NotNull(predicate, nameof(predicate)www.chenghgongs.com);
return BlogRepository.CheckExistsAsync(predicate, id);
}
///
/// 申请博客信息
///
/// 申请博客信息DTO信息
///
public virtual async Task
{
Check.Validate(dto, nameof(dto)www.hnxinhe.cn);
// 博客是以当前用户的身份来申请的
ClaimsPrincipal principal = _serviceProvider.GetCurrentUser();
if (principal == null || !principal.Identity.IsAuthenticated)
{
return new OperationResult(OperationResultType.Error, "用户未登录或登录已失效");
}
int userId = principal.Identity.GetUserId
User user = await UserRepository.GetAsync(userId);
if (user == null)
{
return new OperationResult(OperationResultType.QueryNull, $"编号为“{userId}”的用户信息不存在");
}
Blog blog = BlogRepository.TrackQuery(m => m.UserId == userId).FirstOrDefault();
if (blog != null)
{
return new OperationResult(OperationResultType.Error, "当前用户已开通博客,不能重复申请");
}
if (await CheckBlogExists(m => m.Url == dto.Url))
{
return new OperationResult(OperationResultType.Error, $"Url 为“{dto.Url}”的博客已存在,不能重复添加");
}
blog = dto.MapTo
blog.UserId = userId;
int count = await BlogRepository.InsertAsync(blog);
return count > 0
? new OperationResult(OperationResultType.Success, "博客申请成功")
: OperationResult.NoChanged;
}
///
/// 审核博客信息
///
/// 博客编号
/// 是否通过
///
public virtual async Task
{
Blog blog = await BlogRepository.GetAsync(www.yunyouuyL.com );
if (blog == null)
{
return new OperationResult(OperationResultType.QueryNull, $"编号为“{id}”的博客信息不存在");
}
// 更新博客
blog.IsEnabled = isEnabled;
int count = await BlogRepository.UpdateAsync(blog);
User user = await UserRepository.GetAsync(blog.UserId);
if (user == null)
{
return new OperationResult(OperationResultType.QueryNull, $"编号为“{blog.UserId}”的用户信息不存在");
}
// 如果开通博客,给用户开通博主身份
if (isEnabled)
{
// 查找博客主的角色,博主角色名可由配置系统获得
const string roleName = "博主";
// 用于CUD操作的实体,要用 TrackQuery 方法来查询出需要的数据,不能用 Query,因为 Query 会使用 AsNoTracking
Role role = RoleRepository.TrackQuery(m => m.Name == roleName).FirstOrDefault();
if (role == null)
{
return new OperationResult(OperationResultType.QueryNull, $"名称为“{roleName}”的角色信息不存在");
}
UserRole userRole = UserRoleRepository.TrackQuery(m => m.UserId == user.Id && m.RoleId == role.Id)
.FirstOrDefault();
if (userRole == null)
{
userRole = new UserRole(www.baihuiyulegw.com) { UserId = user.Id, RoleId = role.Id, IsLocked = false };
count += await UserRoleRepository.InsertAsync(userRole);
}
}
OperationResult result = count > 0
? new OperationResult(OperationResultType.Success, $"博客“{blog.Display}”审核 {(isEnabled ? "通过" : "未通过")}")
: OperationResult.NoChanged;
if (result.Succeeded)
{
VerifyBlogEventData eventData = new VerifyBlogEventData()
{
BlogName = blog.Display,
UserName = user.NickName,
IsEnabled = isEnabled
};
EventBus.Publish(eventData);
}
return result;
}
///
/// 更新博客信息
///
/// 包含更新信息的博客信息DTO信息
///
public virtual Task
{
return BlogRepository.UpdateAsync(dtos, async (dto, entity) =>
{
if (await BlogRepository.CheckExistsAsync(www.furggw.com => m.Url == dto.Url, dto.Id))
{
throw new OsharpException($"Url为“{dto.Url}”的博客已存在,不能重复");
}
});
}
///
/// 删除博客信息
///
/// 要删除的博客信息编号
///
public virtual Task
{
return BlogRepository.DeleteAsync(ids, entity =>
{
if (PostRepository.Query(m => m.BlogId == entity.Id).Any())
{
throw new OsharpException($"博客“{entity.Display}”中还有文章未删除,请先删除所有文章,再删除博客");
}
return Task.FromResult(0);
});
}
}
BlogsService.Post.cs
public partial class BlogsService
{
///
/// 获取 文章信息查询数据集
///
public virtual IQueryable
///
/// 检查文章信息是否存在
///
/// 检查谓语表达式
/// 更新的文章信息编号
///
public virtual Task
{
Check.NotNull(predicate, nameof(predicate));
return PostRepository.CheckExistsAsync(www.yunsengyule.com predicate, id);
}
///
/// 添加文章信息
///
/// 要添加的文章信息DTO信息
///
public virtual async Task
{
Check.Validate
if (dtos.Length == 0)
{
return OperationResult.NoChanged;
}
// 文章是以当前用户身份来添加的
ClaimsPrincipal principal = _serviceProvider.GetCurrentUser();
if (principal == null || !principal.Identity.IsAuthenticated)
{
throw new OsharpException("用户未登录或登录已失效");
}
// 检查当前用户的博客状态
int userId = principal.Identity.GetUserId
Blog blog = BlogRepository.TrackQuery(m => m.UserId == userId).FirstOrDefault();
if (blog == null || !blog.IsEnabled)
{
throw new OsharpException("当前用户的博客未开通,无法添加文章");
}
// 没有前置检查,checkAction为null
return await PostRepository.InsertAsync(dtos, null, (dto, entity) =>
{
// 给新建的文章关联博客和作者
entity.BlogId = blog.Id;
entity.UserId = userId;
return Task.FromResult(entity);
});
}
///
/// 更新文章信息
///
/// 包含更新信息的文章信息DTO信息
///
public virtual Task
{
Check.Validate
return PostRepository.UpdateAsync(dtos);
}
///
/// 删除文章信息
///
/// 要删除的文章信息编号
///
public virtual Task
{
Check.NotNull(ids, nameof(ids));
return PostRepository.DeleteAsync(ids);
}
}
模块入口 BlogsPack
模块入口基类
非AspNetCore模块基类 OsharpPack
前面多次提到,每个Pack模块都是继承自一个 模块基类OsharpPack,这个基类用于定义 模块初始化UsePack 过程中未涉及 AspNetCore 环境的模块。
///
/// OSharp模块基类
///
public abstract class OsharpPack
{
///
/// 获取 模块级别,级别越小越先启动
///
public virtual PackLevel Level => PackLevel.Business;
///
/// 获取 模块启动顺序,模块启动的顺序先按级别启动,同一级别内部再按此顺序启动,
/// 级别默认为0,表示无依赖,需要在同级别有依赖顺序的时候,再重写为>0的顺序值
///
public virtual int Order => 0;
///
/// 获取 是否已可用
///
public bool IsEnabled { get; protected set; }
///
/// 将模块服务添加到依赖注入服务容器中
///
/// 依赖注入服务容器
///
public virtual IServiceCollection AddServices(IServiceCollection services)
{
return services;
}
///
/// 应用模块服务
///
/// 服务提供者
public virtual void UsePack(IServiceProvider provider)
{
IsEnabled = true;
}
///
/// 获取当前模块的依赖模块类型
///
///
internal Type[] GetDependPackTypes(Type packType = null)
{
// ...
}
}
模块基类OsharpPack 定义了两个可重写属性:
PackLevel:模块级别,级别越小越先启动
模块级别按 模块 在框架中不同的功能层次,定义了如下几个级别:
///
/// 模块级别,级别越核心,优先启动
///
public enum PackLevel
{
///
/// 核心级别,表示系统的核心模块,
/// 这些模块不涉及第三方组件,在系统运行中是不可替换的,核心模块将始终加载
///
Core = 1,
///
/// 框架级别,表示涉及第三方组件的基础模块
///
Framework = 10,
///
/// 应用级别,表示涉及应用数据的基础模块
///
Application = 20,
///
/// 业务级别,表示涉及真实业务处理的模块
///
Business = 30
}
Order:级别内模块启动顺序,模块启动的顺序先按级别启动,同一级别内部再按此顺序启动,级别默认为 0,表示无依赖,需要在同级别有依赖顺序的时候,再重写为 >0 的顺序值
同时,模块基类 还定义了两个方法:
AddServices:用于将模块内定义的服务注入到 依赖注入服务容器 中。
UsePack:用于使用服务对当前模块进行初始化。
AspNetCore模块基类 AspOsharpPack
AspOsharpPack 基类继承了 OsharpPack,添加了一个对 IApplicationBuilder 支持的 UsePack 方法,用于实现与 AspNetCore 关联的模块初始化工作,例如 Mvc模块 初始化的时候需要应用中间件:app.UseMvcWithAreaRoute();
///
/// 基于AspNetCore环境的Pack模块基类
///
public abstract class AspOsharpPack : OsharpPack
{
///
/// 应用AspNetCore的服务业务
///
/// Asp应用程序构建器
public virtual void UsePack(IApplicationBuilder app)
{
base.UsePack(app.ApplicationServices);
}
}
博客模块的模块入口实现
回到我们的 Liuliu.Blogs 项目,我们来实现投票模块的模块入口类 BlogsPack:
博客模块属于业务模块,因此 PackLevel 设置为 Business
博客模块的启动顺序无需重写,保持 0 即可
将 博客业务服务 注册到 服务容器中
无甚初始化业务
实现代码如下:
///
/// 博客模块
///
public class BlogsPack : OsharpPack
{
///
/// 获取 模块级别,级别越小越先启动
///
public override PackLevel Level { get; } = PackLevel.Business;
///
/// 依赖注入服务容器
///
public override IServiceCollection AddServices(IServiceCollection services)
{
services.TryAddScoped
return services;
}
}
至此,博客模块的服务层实现完毕。