1. Expliquer la couche de service d'application
Les services d'application sont utilisés pour exposer la logique de domaine (métier) à la couche de présentation. La couche de présentation appelle le service d'application en transmettant les paramètres DTO (objet de transfert de données), et le service d'application exécute la logique métier correspondante via l'objet de domaine et renvoie le DTO à la couche de présentation. Par conséquent, la couche de présentation et la couche de domaine seront complètement isolées.
Les points suivants doivent être notés lors de la création de services d'application :
Dans ABP, un service d'application doit implémenter l'interface IApplicationService. La meilleure pratique consiste à créer une interface IApplicationService correspondante pour chaque service d'application. . (En héritant de cette interface, ABP aidera automatiquement à l'injection de dépendances)
ABP fournit une implémentation par défaut d'ApplicationService pour IApplicationService. Cette classe de base fournit des fonctions de journalisation et de localisation pratiques. Lors de l'implémentation des services d'application, héritez simplement d'ApplicationService et implémentez l'interface définie.
Dans ABP, une méthode de service applicatif est une unité de travail par défaut. ABP effectue automatiquement la connexion à la base de données et la gestion des transactions pour le mode UOW, et enregistre automatiquement les modifications des données.
2. Définir l'interface ITaskAppService
1. Examinons d'abord l'interface définie
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(); }
Observez les paramètres et la valeur de retour de la méthode. n’est pas un objet d’entité Utiliser la tâche direct. Pourquoi est-ce ? Parce que la couche de présentation et la couche de service d'application transmettent des données via un objet de transfert de données (DTO).
2. Pourquoi devons-nous transmettre des données via dto ?
En résumé, l'utilisation de DTO pour la transmission de données présente les avantages suivants.
Masquage des données
Problèmes de sérialisation et de chargement paresseux
ABP fournit des classes de contrat pour DTO pour prendre en charge la vérification
Modifications de paramètres ou de valeurs de retour, via Dto Facile à extend
Pour plus de détails, veuillez vous référer à :
ABP Framework - Data Transfer Object
3, spécification Dto (application flexible)
ABP recommande de nommer les entrées/sorties Les paramètres sont : MethodNameInput et MethodNameOutput
et définissent des DTO d'entrée et de sortie distincts pour chaque méthode de service d'application (si un dto est défini pour l'entrée et la sortie de chaque méthode, il y aura une énorme classe dto qui devra être défini Maintenance. Généralement partagé en définissant un dto public)
Même si votre méthode n'accepte/renvoie qu'un seul paramètre, il est préférable de créer une classe DTO
Généralement, elle sera utilisée dans l'application de l'entité correspondante Créez un nouveau dossier Dtos sous le dossier service pour gérer la classe Dto.
3. Définissez le DTO nécessaire pour l'interface du service d'application
1 Regardons d'abord la définition de 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 ); } } }
Le TaskDto hérite directement d'EntityDto, et EntityDto est une entité générique qui est une classe simple qui définit uniquement la propriété Id. Le but de la définition directe d’un TaskDto doit être partagé entre plusieurs méthodes de service d’application.
2. Jetons un coup d'œil à la définition de GetTasksOutput
partage directement TaskDto.
public class GetTasksOutput { public List<TaskDto> Tasks { get; set; } }
3. Jetons un coup d'œil à CreateTaskInput et 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); } }
parmi lesquels UpdateTaskInput implémente l'interface ICustomValidate pour implémenter une vérification personnalisée. Pour comprendre la vérification DTO, veuillez vous référer à ABP Framework - Verification Data Transfer Object
##4 Enfin, jetons un coup d'œil à la définition de GetTasksInput
, qui comprend deux attributs pour le filtrage.
public class GetTasksInput { public TaskState? State { get; set; } public int? AssignedPersonId { get; set; } }
Après avoir défini DTO, avez-vous une question en tête ? J'utilise DTO pour transmettre des données dans la couche de présentation et la couche de service d'application, mais au final ces DTO doivent être convertis en entités pour traiter directement la base de données. Si chaque DTO doit être converti manuellement en entité correspondante, la charge de travail ne peut pas être sous-estimée.
Aussi intelligent que vous soyez, vous vous demanderez certainement s'il existe un moyen de réduire cette charge de travail.
四、使用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); } } } }
到此,此章节就告一段落。为了加深印象,请自行回答如下问题:
什么是应用服务层?
如何定义应用服务接口?
Qu'est-ce que DTO et comment définir DTO ?
Comment mapper automatiquement un DTO avec des entités ?
Comment créer des règles de mappage unifiées ?
Ce qui précède est le contenu de la série d'introduction ABP (5) - création de services d'application. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !