一解释下应用服务层
应用服务用于将领域 (业务) 逻辑暴露给展现层展现层通过传入 DTO(数据传输对象)参数来调用应用服务, 而应用服务通过领域对象来执行相应的业务逻辑并且将 DTO 返回给展现层因此, 展现层和领域层将被完全隔离开来
以下几点, 在创建应用服务时需要注意:
在 ABP 中, 一个应用服务需要实现 IApplicationService 接口, 最好的实践是针对每个应用服务都创建相应继承自 IApplicationService 的接口(通过继承该接口, ABP 会自动帮助依赖注入)
ABP 为 IApplicationService 提供了默认的实现 ApplicationService, 该基类提供了方便的日志记录和本地化功能实现应用服务的时候继承自 ApplicationService 并实现定义的接口即可
ABP 中, 一个应用服务方法默认是一个工作单元(Unit of Work)ABP 针对 UOW 模式自动进行数据库的连接及事务管理, 且会自动保存数据修改
二定义 ITaskAppService 接口
1, 先来看看定义的接口
- public interface ITaskAppService : IApplicationService
- {
- GetTasksOutput GetTasks(GetTasksInput input);
- void UpdateTask(UpdateTaskInput input);
- int CreateTask(CreateTaskInput input);
- Task GetTaskByIdAsync(int taskId);
- TaskDto GetTaskById(int taskId);
- void DeleteTask(int taskId);
- IList GetAllTasks();
- }
观察方法的参数及返回值, 大家可能会发现并未直接使用 Task 实体对象这是为什么呢? 因为展现层与应用服务层是通过 Data Transfer Object(DTO)进行数据传输
2, 为什么需要通过 dto 进行数据传输?
总结来说, 使用 DTO 进行数据传输具有以下好处
数据隐藏
序列化和延迟加载问题
ABP 对 DTO 提供了约定类以支持验证
参数或返回值改变, 通过 Dto 方便扩展
了解更多详情请参考:
ABP 框架 - 数据传输对象
3,Dto 规范 (灵活应用)
ABP 建议命名输入 / 输出参数为: MethodNameInput 和 MethodNameOutput
并为每个应用服务方法定义单独的输入和输出 DTO(如果为每个方法的输入输出都定义一个 dto, 那将有一个庞大的 dto 类需要定义维护一般通过定义一个公用的 dto 进行共用)
即使你的方法只接受 / 返回一个参数, 也最好是创建一个 DTO 类
一般会在对应实体的应用服务文件夹下新建 Dtos 文件夹来管理 Dto 类
三定义应用服务接口需要用到的 DTO
1, 先来看看 TaskDto 的定义
- namespace LearningMpaAbp.Tasks.Dtos{ ///
- /// A DTO class that can be used in various application service methods when needed to send/receive Task objects.
- ///
- public class TaskDto : EntityDto
- {
- public long? AssignedPersonId { get; set; }
- public string AssignedPersonName { get; set; }
- public string Title { get; set; }
- public string Description { get; set; }
- public DateTime CreationTime { get; set; }
- public TaskState State { get; set; } //This method is just used by the Console Application to list tasks
- public override string ToString()
- {
- return string.Format(
- "[Task Id={0}, Description={1}, CreationTime={2}, AssignedPersonName={3}, State={4}]",
- Id,
- Description,
- CreationTime,
- AssignedPersonId,
- (TaskState)State
- );
- }
- }
- }
该 TaskDto 直接继承自 EntityDto,EntityDto 是一个通用的实体只定义 Id 属性的简单类直接定义一个 TaskDto 的目的是为了在多个应用服务方法中共用
2, 下面来看看 GetTasksOutput 的定义
就是直接共用了 TaskDto
- public class GetTasksOutput
- { public List Tasks { get; set; }
- }
3, 再来看看 CreateTaskInputUpdateTaskInput
- public class CreateTaskInput
- { public int? AssignedPersonId { get; set; }
- [Required] public string Description { get; set; }
- [Required] public string Title { get; set; }
- public TaskState State { get; set; }
- public override string ToString()
- { return string.Format("[CreateTaskInput > AssignedPersonId = {0}, Description = {1}]", AssignedPersonId, Description);
- }
- }
- ///
- /// This DTO class is used to send needed data to method.
- ///
- /// Implements for additional custom validation.
- ///
- public class UpdateTaskInput : ICustomValidate
- {
- [Range(1, Int32.MaxValue)] //Data annotation attributes work as expected.
- public int Id { get; set; }
- public int? AssignedPersonId { get; set; }
- public TaskState? State { get; set; }
- [Required]
- public string Title { get; set; }
- [Required]
- public string Description { get; set; }
- //Custom validation method. It's called by ABP after data annotation validations.
- public void AddValidationErrors(CustomValidationContext context)
- {
- if (AssignedPersonId == null && State == null)
- {
- context.Results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!",
- new[] { "AssignedPersonId", "State" }));
- }
- }
- public override string ToString()
- {
- return string.Format("[UpdateTaskInput > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", Id, AssignedPersonId, State);
- }
- }
其中 UpdateTaskInput 实现了 ICustomValidate 接口, 来实现自定义验证了解 DTO 验证可参考 ABP 框架 - 验证数据传输对象
##4, 最后来看一下 GetTasksInput 的定义
其中包括两个属性用来进行过滤
- public class GetTasksInput
- { public TaskState? State { get; set; } public int? AssignedPersonId { get; set; }
- }
定义完 DTO, 是不是脑袋有个疑问, 我在用 DTO 在展现层与应用服务层进行数据传输, 但最终这些 DTO 都需要转换为实体才能与数据库直接打交道啊如果每个 dto 都要自己手动去转换成对应实体, 这个工作量也是不可小觑啊
聪明如你, 你肯定会想肯定有什么方法来减少这个工作量
四使用 AutoMapper 自动映射 DTO 与实体
1, 简要介绍 AutoMapper
开始之前, 如果对 AutoMapper 不是很了解, 建议看下这篇文章 AutoMapper 小结
AutoMapper 的使用步骤, 简单总结下:
创建映射规则(Mapper.CreateMap();)
类型映射转换(Mapper.Map(sourceModel))
在 Abp 中有两种方式创建映射规则:
特性数据注解方式:
AutoMapFromAutoMapTo 特性创建单向映射
AutoMap 特性创建双向映射
代码创建映射规则:
Mapper.CreateMap();
2, 为 Task 实体相关的 Dto 定义映射规则
2.1, 为 CreateTasksInputUpdateTaskInput 定义映射规则
其中 CreateTasksInputUpdateTaskInput 中的属性名与 Task 实体的属性命名一致, 且只需要从 Dto 映射到实体, 不需要反向映射所以通过 AutoMapTo 创建单向映射即可
- [AutoMapTo(typeof(Task))] // 定义单向映射
- public class CreateTaskInput
- {
- ...
- }
- [AutoMapTo(typeof(Task))] // 定义单向映射
- public class UpdateTaskInput
- {
- ...
- }
2.2, 为 TaskDto 定义映射规则
TaskDto 与 Task 实体的属性中, 有一个属性名不匹配 TaskDto 中的 AssignedPersonName 属性对应的是 Task 实体中的 AssignedPerson.FullName 属性针对这一属性映射, AutoMapper 没有这么智能需要我们告诉它怎么做;
- var taskDtoMapper = mapperConfig.CreateMap();
- taskDtoMapper.ForMember(dto => dto.AssignedPersonName, map => map.MapFrom(m => m.AssignedPerson.FullName));
为 TaskDto 与 Task 创建完自定义映射规则后, 我们需要思考, 这段代码该放在什么地方呢?
四创建统一入口注册 AutoMapper 映射规则
如果在映射规则既有通过特性方式又有通过代码方式创建, 这时就会容易混乱不便维护
为了解决这个问题, 统一采用代码创建映射规则的方式并通过 IOC 容器注册所有的映射规则类, 再循环调用注册方法
1, 定义抽象接口 IDtoMapping
应用服务层根目录创建 IDtoMapping 接口, 定义 CreateMapping 方法由映射规则类实现
- namespace LearningMpaAbp{ ///
- /// 实现该接口以进行映射规则创建
- ///
- internal interface IDtoMapping
- { void CreateMapping(IMapperConfigurationExpression mapperConfig);
- }
- }
2, 为 Task 实体相关 Dto 创建映射类
- namespace LearningMpaAbp.Tasks{ public class TaskDtoMapping : IDtoMapping
- { public void CreateMapping(IMapperConfigurationExpression mapperConfig)
- { // 定义单向映射
- mapperConfig.CreateMap();
- mapperConfig.CreateMap();
- mapperConfig.CreateMap(); // 自定义映射
- var taskDtoMapper = mapperConfig.CreateMap();
- taskDtoMapper.ForMember(dto => dto.AssignedPersonName, map => map.MapFrom(m => m.AssignedPerson.FullName));
- }
- }
- }
3, 注册 IDtoMapping 依赖
在应用服务的模块中对 IDtoMapping 进行依赖注册, 并解析以进行映射规则创建
- namespace LearningMpaAbp{
- [DependsOn(typeof(LearningMpaAbpCoreModule), typeof(AbpAutoMapperModule))]
- public class LearningMpaAbpApplicationModule : AbpModule
- { public override void PreInitialize() {
- Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper =>
- { //Add your custom AutoMapper mappings here...
- });
- } public override void Initialize() {
- IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); // 注册 IDtoMapping
- IocManager.IocContainer.Register(
- Classes.FromAssembly(Assembly.GetExecutingAssembly())
- .IncludeNonPublicTypes()
- .BasedOn()
- .WithService.Self()
- .WithService.DefaultInterfaces()
- .LifestyleTransient()
- ); // 解析依赖, 并进行映射规则创建
- Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper =>
- { var mappers = IocManager.IocContainer.ResolveAll();
- foreach (var dtomap in mappers)
- dtomap.CreateMapping(mapper);
- });
- }
- }
- }
通过这种方式, 我们只需要实现 IDtoMappting 进行映射规则定义创建映射规则的动作就交给模块吧
五万事俱备, 实现 ITaskAppService
认真读完以上内容, 那么到这一步, 就很简单了, 业务只是简单的增删该查, 实现起来就很简单了可以自己尝试自行实现, 再参考代码:
- namespace LearningMpaAbp.Tasks
- { ///
- /// Implements to perform task related application functionality.
- ///
- /// Inherits from .
- /// contains some basic functionality common for application services (such as logging and localization).
- ///
- public class TaskAppService : LearningMpaAbpAppServiceBase, ITaskAppService
- { //These members set in constructor using constructor injection.
- private readonly IRepository _taskRepository;
- private readonly IRepository _personRepository; ///
- ///In constructor, we can get needed classes/interfaces.
- ///They are sent here by dependency injection system automatically.
- ///
- public TaskAppService(IRepository taskRepository, IRepository personRepository)
- { _taskRepository = taskRepository; _personRepository = personRepository;
- }
- public GetTasksOutput GetTasks(GetTasksInput input)
- {
- var query = _taskRepository.GetAll(); if (input.AssignedPersonId.HasValue)
- {
- query = query.Where(t => t.AssignedPersonId == input.AssignedPersonId.Value);
- } if (input.State.HasValue)
- {
- query = query.Where(t => t.State == input.State.Value);
- } //Used AutoMapper to automatically convert List to List.
- return new GetTasksOutput
- {
- Tasks = Mapper.Map>(query.ToList())
- };
- }
- public async Task GetTaskByIdAsync(int taskId)
- { //Called specific GetAllWithPeople method of task repository.
- var task = await _taskRepository.GetAsync(taskId); //Used AutoMapper to automatically convert List to List.
- return task.MapTo();
- }
- public TaskDto GetTaskById(int taskId)
- {
- var task = _taskRepository.Get(taskId);
- return task.MapTo();
- }
- public void UpdateTask(UpdateTaskInput input)
- { //We can use Logger, it's defined in ApplicationService base class.
- Logger.Info("Updating a task for input:" + input); //Retrieving a task entity with given id using standard Get method of repositories.
- var task = _taskRepository.Get(input.Id); //Updating changed properties of the retrieved task entity.
- if (input.State.HasValue)
- { task.State = input.State.Value;
- } if (input.AssignedPersonId.HasValue)
- { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
- } //We even do not call Update method of the repository.
- //Because an application service method is a 'unit of work' scope as default.
- //ABP automatically saves all changes when a 'unit of work' scope ends (without any exception).
- }
- public int CreateTask(CreateTaskInput input)
- { //We can use Logger, it's defined in ApplicationService class.
- Logger.Info("Creating a task for input:" + input); //Creating a new Task entity with given input's properties
- var task = new Task
- {
- Description = input.Description,
- Title = input.Title,
- State = input.State,
- CreationTime = Clock.Now
- }; if (input.AssignedPersonId.HasValue)
- { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
- } //Saving entity with standard Insert method of repositories.
- return _taskRepository.InsertAndGetId(task);
- }
- public void DeleteTask(int taskId)
- {
- var task = _taskRepository.Get(taskId); if (task != null)
- { _taskRepository.Delete(task);
- }
- }
- }
- }
到此, 此章节就告一段落为了加深印象, 请自行回答如下问题:
什么是应用服务层?
如何定义应用服务接口?
什么 DTO, 如何定义 DTO?
DTO 如何与实体进行自动映射?
如何对映射规则统一创建?
来源: https://www.php1.cn/detail/php-9336bb9bc7.html