1. Explain the application service layer
Application services are used to expose domain (business) logic to the presentation layer. The presentation layer calls the application service by passing in DTO (data transfer object) parameters, and the application service executes the corresponding business logic through the domain object and returns the DTO to the presentation layer. Therefore, the presentation layer and domain layer will be completely isolated.
The following points need to be noted when creating application services:
In ABP, an application service needs to implement the IApplicationService interface. The best practice is to create a corresponding inherited from IApplicationService for each application service. interface. (By inheriting this interface, ABP will automatically help with dependency injection)
ABP provides a default implementation of ApplicationService for IApplicationService. This base class provides convenient logging and localization functions. When implementing application services, just inherit from ApplicationService and implement the defined interface.
In ABP, an application service method is a unit of work by default. ABP automatically performs database connection and transaction management for UOW mode, and automatically saves data modifications.
2. Define the ITaskAppService interface
1. Let’s first take a look at the defined interface
public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); int CreateTask(CreateTaskInput input); Task<TaskDto> GetTaskByIdAsync(int taskId); TaskDto GetTaskById(int taskId); void DeleteTask(int taskId); IList<TaskDto> GetAllTasks(); }
Observe the parameters and return values of the method. You may find that Task is not used directly. Entity object. Why is this? Because the presentation layer and application service layer transmit data through Data Transfer Object (DTO).
2. Why do we need to transmit data through dto?
In summary, using DTO for data transmission has the following benefits.
Data hiding
Serialization and lazy loading issues
ABP provides a convention class for DTO to support verification
Parameter or return value changes, through Dto Convenient extension
For more details, please refer to:
ABP Framework-Data Transfer Object
3, Dto specification (flexible application)
ABP recommends naming input/output The parameters are: MethodNameInput and MethodNameOutput
and define separate input and output DTOs for each application service method (if a dto is defined for the input and output of each method, there will be a huge dto class that needs to be defined) Maintenance. Generally shared by defining a public dto)
Even if your method only accepts/returns one parameter, it is best to create a DTO class
Generally in the application of the corresponding entity Create a new Dtos folder under the service folder to manage the Dto class.
3. Define the DTO needed for the application service interface
1. Let’s first look at the definition of TaskDto
namespace LearningMpaAbp.Tasks.Dtos{ /// <summary> /// A DTO class that can be used in various application service methods when needed to send/receive Task objects. /// </summary> 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 ); } } }
The TaskDto directly inherits from EntityDto. EntityDto is a general The entity is a simple class that only defines the Id property. The purpose of directly defining a TaskDto is to be shared among multiple application service methods.
2, let’s take a look at the definition of GetTasksOutput
It directly shares TaskDto.
public class GetTasksOutput { public List<TaskDto> Tasks { get; set; } }
3, Let’s take a look at CreateTaskInput and UpdateTaskInput
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); } }
/// <summary> /// This DTO class is used to send needed data to <see cref="ITaskAppService.UpdateTask"/> method. /// /// Implements <see cref="ICustomValidate"/> for additional custom validation. /// </summary> 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); } }
Among them, UpdateTaskInput implements the ICustomValidate interface to implement custom verification. To understand DTO verification, please refer to ABP Framework - Verifying Data Transfer Objects
##4. Finally, let’s take a look at the definition of GetTasksInput
, which includes two attributes for filtering.
public class GetTasksInput { public TaskState? State { get; set; } public int? AssignedPersonId { get; set; } }
After defining DTO, do you have a question in your head? I am using DTO to transmit data in the presentation layer and application service layer, but in the end these DTOs need to be converted into entities to directly deal with the database. If each DTO has to be manually converted into the corresponding entity, the workload cannot be underestimated.
As smart as you are, you will definitely want to know if there is any way to reduce this workload.
四、使用AutoMapper自动映射DTO与实体
1,简要介绍AutoMapper
开始之前,如果对AutoMapper不是很了解,建议看下这篇文章AutoMapper小结。
AutoMapper的使用步骤,简单总结下:
创建映射规则(Mapper.CreateMap
类型映射转换(Mapper.Map
在Abp中有两种方式创建映射规则:
特性数据注解方式:
AutoMapFrom、AutoMapTo 特性创建单向映射
AutoMap 特性创建双向映射
代码创建映射规则:
Mapper.CreateMap
2,为Task实体相关的Dto定义映射规则
2.1,为CreateTasksInput、UpdateTaskInput定义映射规则
其中CreateTasksInput、UpdateTaskInput中的属性名与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{ /// <summary> /// 实现该接口以进行映射规则创建 /// </summary> internal interface IDtoMapping { void CreateMapping(IMapperConfigurationExpression mapperConfig); } }
2,为Task实体相关Dto创建映射类
namespace LearningMpaAbp.Tasks{ public class TaskDtoMapping : IDtoMapping { public void CreateMapping(IMapperConfigurationExpression mapperConfig) { //定义单向映射 mapperConfig.CreateMap<CreateTaskInput, Task>(); mapperConfig.CreateMap<UpdateTaskInput, Task>(); mapperConfig.CreateMap<TaskDto, UpdateTaskInput>(); //自定义映射 var taskDtoMapper = mapperConfig.CreateMap<Task, TaskDto>(); 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<IDtoMapping>() .WithService.Self() .WithService.DefaultInterfaces() .LifestyleTransient() ); //解析依赖,并进行映射规则创建 Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper => { var mappers = IocManager.IocContainer.ResolveAll<IDtoMapping>(); foreach (var dtomap in mappers) dtomap.CreateMapping(mapper); }); } } }
通过这种方式,我们只需要实现IDtoMappting进行映射规则定义。创建映射规则的动作就交给模块吧。
五、万事俱备,实现ITaskAppService
认真读完以上内容,那么到这一步,就很简单了,业务只是简单的增删该查,实现起来就很简单了。可以自己尝试自行实现,再参考代码:
namespace LearningMpaAbp.Tasks { /// <summary> /// Implements <see cref="ITaskAppService"/> to perform task related application functionality. /// /// Inherits from <see cref="ApplicationService"/>. /// <see cref="ApplicationService"/> contains some basic functionality common for application services (such as logging and localization). /// </summary> public class TaskAppService : LearningMpaAbpAppServiceBase, ITaskAppService { //These members set in constructor using constructor injection. private readonly IRepository<Task> _taskRepository; private readonly IRepository<Person> _personRepository; /// <summary> ///In constructor, we can get needed classes/interfaces. ///They are sent here by dependency injection system automatically. /// </summary> public TaskAppService(IRepository<Task> taskRepository, IRepository<Person> 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<Task> to List<TaskDto>. return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(query.ToList()) }; } public async Task<TaskDto> GetTaskByIdAsync(int taskId) { //Called specific GetAllWithPeople method of task repository. var task = await _taskRepository.GetAsync(taskId); //Used AutoMapper to automatically convert List<Task> to List<TaskDto>. return task.MapTo<TaskDto>(); } public TaskDto GetTaskById(int taskId) { var task = _taskRepository.Get(taskId); return task.MapTo<TaskDto>(); } 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); } } } }
到此,此章节就告一段落。为了加深印象,请自行回答如下问题:
什么是应用服务层?
如何定义应用服务接口?
What is DTO and how to define DTO?
How does DTO automatically map with entities?
How to create unified mapping rules?
The above is the content of the ABP introductory series (5) - creating application services. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!