0
1. explain the application service layer
Application services are used to expose the domain (business) logic to the presentation layer. The presentation layer calls the application service by passing in the DTO parameter, 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 the domain layer will be completely isolated.
Note the following when creating an application service:
An application service needs to implement the IApplicationService interface in the ABP. The best practice is to create an interface inherited from the IApplicationService for each application service. (By inheriting this interface, the ABP will automatically help with dependency injection)
ABP provides the default implementation ApplicationService for IApplicationService. this base class provides convenient logging and localization functions. You can inherit from ApplicationService and implement the defined interface when implementing the application service.
By default, an application service method is a Unit of Work ). In the UOW mode, you can automatically manage database connections and transactions, and save and modify data automatically.
II. define the ITaskAppService interface
1. let's take a look at the defined interfaces.
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(); }
Observe the parameters and return values of the method. you may find that the Task object is not directly used. Why? Because the presentation layer and application service layer transmit Data through Data Transfer Object (DTO.
2. why data transmission through dto?
To sum up, using DTO for data transmission has the following benefits.
Data Hiding
Serialization and delayed loading
A contractual class is provided for DTO to support verification.
The parameter or return value is changed and can be extended through Dto.
For more information, see:
ABP framework-data transmission object
3. Dto specification (flexible application)
We recommend that you name the input/output parameters as MethodNameInput and MethodNameOutput.
Define separate input and output DTO 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 to be defined and maintained. Generally, a common dto is defined for sharing)
Even if your method only accepts/returns one parameter, it is best to create a DTO class.
Generally, the Dtos folder is created under the application service folder of the corresponding object to manage the Dto class.
3. define the DTO required for the application service interface
1. let's take a look at the definition of 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 ); } }}
This TaskDto directly inherits from EntityDto. EntityDto is a simple class for a common object to define only the Id attribute. The purpose of defining a TaskDto is to integrate multiple application service methods.
2. let's take a look at the definition of GetTasksOutput.
TaskDto is directly shared.
public class GetTasksOutput { public List
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); } }
/// /// 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 implements the ICustomValidate interface to implement custom verification. For details about DTO verification, refer to the ABP framework-verification of data transmission objects.
#4. let's take a look at the definition of GetTasksInput.
These two attributes are used for filtering.
public class GetTasksInput { public TaskState? State { get; set; } public int? AssignedPersonId { get; set; } }
After defining DTO, do you have a question? I am using DTO for data transmission at the presentation layer and application service layer, however, in the end, all these DTO functions must be converted into entities to directly deal with databases. If each dto needs to be manually converted to the corresponding entity, the workload cannot be underestimated.
If you are smart, you will surely think of some ways to reduce this workload.
4. use AutoMapper to automatically map DTO to objects
1. Brief introduction to AutoMapper
If you are not familiar with AutoMapper before you start, we recommend that you read the AutoMapper summary in this article.
The following describes how to use AutoMapper:
Create a er. CreateMap ();)
Mapper. Map (SourceModel ))
You can create a ing rule in the following two methods:
Feature data annotation method:
AutoMapFrom and AutoMapTo features to create one-way ing
AutoMap feature creates bidirectional ing
Code creation ing rules:
Mapper. CreateMap ();
2. define the Ding rules for Dto related tasks.
2.1. define the ing rules for CreateTasksInput and UpdateTaskInput.
The attribute names in CreateTasksInput and UpdateTaskInput are the same as those in the Task object, and only need to map from Dto to the object without reverse ING. Therefore, you can use AutoMapTo to create one-way ING.
[AutoMapTo (typeof (Task)] // defines one-way public ing public class CreateTaskInput {...} [AutoMapTo (typeof (Task)] // defines one-way public ing public class UpdateTaskInput {...}
2.2. define a ing rule for TaskDto
TaskDto and Task object attributes do not match. The AssignedPersonName attribute in TaskDto corresponds to the AssignedPerson. FullName attribute in the Task object. For this property ING, AutoMapper is not so intelligent that we need to tell it how to do it;
Var taskDtoMapper = mapperConfig. CreateMap ();
TaskDtoMapper. ForMember (dto => dto. AssignedPersonName, map => map. MapFrom (m => m. AssignedPerson. FullName ));
After creating custom ing rules for TaskDto and Task, we need to think about where to put this code?
4. create a unified entry registration AutoMapper ing rule
If the ing rule is created both in feature mode and in code mode, it will be confusing and inconvenient to maintain.
To solve this problem, the code creates a ing rule. Register all the ing rule classes through the IOC container and call the registration method again.
1. define the abstract interface IDtoMapping
Create the IDtoMapping interface in the root directory of the application service layer. the CreateMapping method is defined by the ing rule class.
Namespace LearningMpaAbp {////// Implement this interface to create a ing rule ///Internal interface IDtoMapping {void CreateMapping (IMapperConfigurationExpression mapperConfig );}}
2. create a Ding class for the Dto related to the Task object
Namespace LearningMpaAbp. Tasks {public class TaskDtoMapping: IDtoMapping {public void CreateMapping (IMapperConfigurationExpression mapperConfig) {// defines one-way Maping mapperConfig. CreateMap
(); MapperConfig. CreateMap
(); MapperConfig. CreateMap
(); // Custom var ing var taskDtoMapper = mapperConfig. CreateMap
(); TaskDtoMapper. ForMember (dto => dto. AssignedPersonName, map => map. MapFrom (m => m. AssignedPerson. FullName ));}}}
3. Register the IDtoMapping dependency
In the application service module, register IDtoMapping dependencies and parse them to create ing rules.
Namespace LearningMpaAbp {[DependsOn (typeof (types), 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 (); // register IDtoMapping IocManager. iocContainer. register (Classes. fromAssembly (Assembly. getExecutingAssembly ()). includeNonPublicTypes (). basedOn
(). WithService. self (). withService. defaultInterfaces (). lifestyleTransient (); // Parse dependencies and create a Configuration for ing rules. modules. abpAutoMapper (). configurators. add (mapper => {var mappers = IocManager. iocContainer. resolveAll
(); Foreach (var dtomap in mappers) dtomap. CreateMapping (mapper );});}}}
In this way, we only need to implement IDtoMappting to define the ing rules. The action for creating a ing rule is handed over to the module.
5. everything is ready for use to implement ITaskAppService
After carefully reading the above content, this step is very simple. The business is simply adding, deleting, and querying. it is very easy to implement. You can implement it by yourself, and then refer to the code:
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); } } }}
At this point, this chapter has come to an end. To enhance your impression, please answer the following questions:
What is the application service layer?
How do I define an application service interface?
What DTO and how to define DTO?
How does DTO automatically map objects?
How to create a uniform ing rule?
The above is the ABC getting started series (5)-The content for creating application services. For more information, see PHP Chinese network (www.php1.cn )!