Heim  >  Artikel  >  Backend-Entwicklung  >  ABP-Reihe „Erste Schritte“ (5) – Erstellen von Anwendungsdiensten

ABP-Reihe „Erste Schritte“ (5) – Erstellen von Anwendungsdiensten

黄舟
黄舟Original
2017-01-16 17:31:092633Durchsuche

1. Erklären Sie die Anwendungsdienstschicht

Anwendungsdienste werden verwendet, um der Präsentationsschicht Domänenlogik (Geschäftslogik) zugänglich zu machen. Die Präsentationsschicht ruft den Anwendungsdienst auf, indem sie DTO-Parameter (Data Transfer Object) übergibt. Der Anwendungsdienst führt die entsprechende Geschäftslogik über das Domänenobjekt aus und gibt das DTO an die Präsentationsschicht zurück. Daher werden die Präsentationsschicht und die Domänenschicht vollständig isoliert.
Die folgenden Punkte müssen beim Erstellen von Anwendungsdiensten beachtet werden:

In ABP muss ein Anwendungsdienst die IApplicationService-Schnittstelle implementieren. Die beste Vorgehensweise besteht darin, für jeden Anwendungsdienst eine entsprechende IApplicationService-Schnittstelle zu erstellen . (Durch die Vererbung dieser Schnittstelle hilft ABP automatisch bei der Abhängigkeitsinjektion.)

ABP stellt die Standardimplementierung ApplicationService für IApplicationService bereit. Diese Basisklasse bietet praktische Protokollierungs- und Lokalisierungsfunktionen. Wenn Sie Anwendungsdienste implementieren, erben Sie einfach von ApplicationService und implementieren Sie die definierte Schnittstelle.

In ABP ist eine Anwendungsdienstmethode standardmäßig eine Arbeitseinheit. ABP führt automatisch die Datenbankverbindungs- und Transaktionsverwaltung für den UOW-Modus durch und speichert automatisch Datenänderungen.

2. Definieren Sie die ITaskAppService-Schnittstelle

1. Schauen wir uns zunächst die definierte Schnittstelle an

    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();
    }

Beobachten Sie die Parameter und Rückgabewerte der Methode. Möglicherweise stellen Sie fest, dass es kein direktes Entitätsobjekt „Aufgabe verwenden“ gibt. Warum ist das so? Weil die Präsentationsschicht und die Anwendungsdienstschicht Daten über das Data Transfer Object (DTO) übertragen.

2. Warum müssen wir Daten über dto übertragen?

Zusammenfassend hat die Verwendung von DTO für die Datenübertragung die folgenden Vorteile.

Ausblenden von Daten

Serialisierungs- und Lazy-Loading-Probleme

ABP stellt Vertragsklassen für DTO zur Unterstützung der Verifizierung bereit

Änderungen von Parametern oder Rückgabewerten können über Dto einfach durchgeführt werden erweitern

Weitere Einzelheiten finden Sie unter:
ABP Framework – Data Transfer Object

3, Dto-Spezifikation (flexible Anwendung)

ABP empfiehlt die Benennung von Eingabe/Ausgabe Die Parameter sind: MethodNameInput und MethodNameOutput

und definieren separate Eingabe- und Ausgabe-DTOs für jede Anwendungsdienstmethode (wenn ein DTO für die Eingabe und Ausgabe jeder Methode definiert ist, ist eine große DTO-Klasse erforderlich

Auch wenn Ihre Methode nur einen Parameter akzeptiert/zurückgibt, ist es am besten, eine DTO-Klasse zu erstellen.

Im Allgemeinen wird sie verwendet die Anwendung der entsprechenden Entität Erstellen Sie einen neuen Dtos-Ordner unter dem Dienstordner, um die Dto-Klasse zu verwalten.

3. Definieren Sie das für die Anwendungsdienstschnittstelle benötigte DTO

1 Schauen wir uns zunächst die Definition von TaskDto an

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
                );
        }
    }
}

Das TaskDto erbt direkt von EntityDto und EntityDto ist eine generische Entität, eine einfache Klasse, die nur die Id-Eigenschaft definiert. Der Zweck der direkten Definition eines TaskDto besteht darin, es von mehreren Anwendungsdienstmethoden gemeinsam zu nutzen.

2. Schauen wir uns die Definition von GetTasksOutput an.

teilt TaskDto direkt.

public class GetTasksOutput
    {        public List<TaskDto> Tasks { get; set; }
    }

3. Werfen wir einen Blick auf CreateTaskInput und 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&#39;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);
        }
    }

, wobei UpdateTaskInput die ICustomValidate-Schnittstelle implementiert, um eine benutzerdefinierte Überprüfung zu implementieren. Um die DTO-Überprüfung zu verstehen, lesen Sie bitte ABP Framework – Verification Data Transfer Object

##4. Schauen wir uns abschließend die Definition von GetTasksInput
an, die zwei Attribute zum Filtern enthält.

 public class GetTasksInput
    {        public TaskState? State { get; set; }        public int? AssignedPersonId { get; set; }
    }

Haben Sie nach der Definition von DTO eine Frage im Kopf? Ich verwende DTO, um Daten in der Präsentationsschicht und der Anwendungsdienstschicht zu übertragen, aber am Ende müssen diese DTOs in Entitäten umgewandelt werden um direkt mit der Datenbank umzugehen. Wenn jedes DTO manuell in die entsprechende Entität umgewandelt werden muss, ist der Arbeitsaufwand nicht zu unterschätzen.
So schlau Sie auch sind, Sie werden sich bestimmt fragen, ob es eine Möglichkeit gibt, diesen Arbeitsaufwand zu reduzieren.

四、使用AutoMapper自动映射DTO与实体

1,简要介绍AutoMapper

开始之前,如果对AutoMapper不是很了解,建议看下这篇文章AutoMapper小结。

AutoMapper的使用步骤,简单总结下:

创建映射规则(Mapper.CreateMapbc1f4f6eceb5c3fe3046d7ce55a259f7();)

类型映射转换(Mapper.Mapaac097dc6d13bd23cbb7f49e52c14533(sourceModel))

在Abp中有两种方式创建映射规则:

特性数据注解方式:

AutoMapFrom、AutoMapTo 特性创建单向映射

AutoMap 特性创建双向映射

代码创建映射规则:

Mapper.CreateMapbc1f4f6eceb5c3fe3046d7ce55a259f7();

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.CreateMapfad4ed88b82bbc62dc7998055dd75070();
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&#39;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 &#39;unit of work&#39; scope as default.
            //ABP automatically saves all changes when a &#39;unit of work&#39; scope ends (without any exception).
        }

        public int CreateTask(CreateTaskInput input)
        {            //We can use Logger, it&#39;s defined in ApplicationService class.
            Logger.Info("Creating a task for input: " + input);            //Creating a new Task entity with given input&#39;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);
            }
        }
    }
}

到此,此章节就告一段落。为了加深印象,请自行回答如下问题:

什么是应用服务层?

如何定义应用服务接口?

Was ist DTO und wie definiert man DTO?

Wie ordne ich DTO automatisch Entitäten zu?

Wie erstelle ich einheitliche Zuordnungsregeln?

Das Obige ist der Inhalt der ABP-Einführungsreihe (5) – Erstellen von Anwendungsdiensten. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn