應用服務
應用服務將領域邏輯暴露給展示層。在展示層使用DTO(數據傳輸對象)作為參數調用應用服務,應用服務使用領域對象執行一些特定的業務邏輯,並返回DTO到展示層。因此,展示層與領域層是完全獨立的。在一個理想的分層應用中,展示層從不直接使用領域對象。
在ABP中,應用服務應該實現IApplicationService接口。建議為每一個應用服務創建一個接口。所以,我們首先定義一個應用服務的接口,如下所示:
public interface IPersonAppService : IApplicationService { void CreatePerson(CreatePersonInput input); }
IPsersonAppService只有一個方法。它被展示層用來創建一個新的person。CreatePersonInput是一個DTO對象,如下所示:
public class CreatePersonInput { [Required] public string Name { get; set; } public string EmailAddress { get; set; } }
然后,我們可以實現IPersonAppService:
public class PersonAppService : IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public void CreatePerson(CreatePersonInput input) { var person = _personRepository.FirstOrDefault(p => p.EmailAddress == input.EmailAddress); if (person != null) { throw new UserFriendlyException("There is already a person with given email address"); } person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); } }
這有一些重要的點:
- PersonAppService使用IRepository<Person>執行數據庫操作。它使用構造函數注入模式。這里我們使用依賴注入。
- PersonAppService實現IApplicationService(因為IPersonAppService擴展了IApplicationService),它被ABP自動注冊到依賴注入系統,可以被其他類注入並使用。命名約定在這里是非常重要的。參見依賴注入文檔了解更多。
- CreatePerson方法使用CreatePersonInput對象。它是一個input DTO,自動被ABP校驗。參見DTO和驗證文檔了解詳情。
應用服務應該實現IApplicationService接口。也可以選擇派生子ApplicationService基類。因此,IApplicationService自然也就被實現了。ApplicationService類有一些基本的功能,可以很容易的實現日志、本地化等。建議為應用服務創建一個特別的擴展了ApplicationSerivice的基類。這樣,就可以為應用服務添加一些通用的功能。應用服務類實例如下:
public class TaskAppService : ApplicationService, ITaskAppService { public TaskAppService() { LocalizationSourceName = "SimpleTaskSystem"; } public void CreateTask(CreateTaskInput input) { //Write some logs (Logger is defined in ApplicationService class) Logger.Info("Creating a new task with description: " + input.Description); //Get a localized text (L is a shortcut for LocalizationHelper.GetString(...), defined in ApplicationService class) var text = L("SampleLocalizableTextKey"); //TODO: Add new task to database... } }
你可以在一個基類的構造函數里定義LocalizationSourceName。這樣,你就不用再所有的服務類里重復定義它。關於這個話題可以參見logging和localization文檔了解更多信息。
CrudService和AsyncCrudAppService類
如果你創建的應用服務對於一個特定的實體包含Create,Update,Delete,Get,GetAll方法,你可以繼承CrudAppService(或AsyncCrudAppService如果你創建異步方法)類。CrudAppService基類是泛型的,它接收相關的實體和DTO類型作為泛型參數並且是可擴展的,當你需要自定義它的時候可以重寫功能。
假定我們有一個Task實體,定義如下:
public class Task : Entity, IHasCreationTime { public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Person AssignedPerson { get; set; } public Guid? AssignedPersonId { get; set; } public Task() { CreationTime = Clock.Now; State = TaskState.Open; } }
我們為這個實體創建一個DTO對象:
[AutoMap(typeof(Task))] public class TaskDto : EntityDto, IHasCreationTime { public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Guid? AssignedPersonId { get; set; } public string AssignedPersonName { get; set; } }
AutoMap特性創建實體和DTO之間的自動映射配置。現在,我們可以創建一個應用服務了,如下所示:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto> { public TaskAppService(IRepository<Task> repository) : base(repository) { } }
我們注入了倉儲並把它傳遞給基類(如果我們想創建同步方法而不是異步方法的時候,可以繼承CrudAppService)。就這樣!TaskAppService現在有簡單的CRUD方法了,如果你想為應用服務定義一個接口,你可以按如下所示創建你的接口:
public interface ITaskAppService : IAsyncCrudAppService<TaskDto> { }
注意,IAsyncCrudAppService不接收實體(Task)作為泛型參數。因為,實體和實現相關,不應該包含在公共接口中。現在,我們可以為TaskAppService類實現ITaskAppService接口了:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>, ITaskAppService { public TaskAppService(IRepository<Task> repository) : base(repository) { } }
Crud應用服務默認使用PagedAndSortedResultRequestDto做為GetAll方法的參數,它提供了可選的排序和分頁參數。但是你可能想為GetAll方法添加其他的參數。例如,你想添加一些自定義過濾器。在這種情況下,你可以為GetAll方法創建一個DTO。例如:
public class GetAllTasksInput : PagedAndSortedResultRequestDto { public TaskState? State { get; set; } }
我們繼承了PagedAndSortedResultRequestInput(不是必須的,但是想使用分頁和排序參數)並添加了一個可選的State屬性來通過它過濾任務。現在,為了應用自定義過濾器,我們應該改變TaskAppService類:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput> { public TaskAppService(IRepository<Task> repository) : base(repository) { } protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input) { return base.CreateFilteredQuery(input) .WhereIf(input.State.HasValue, t => t.State == input.State.Value); } }
首先,我們添加了GetAllTaskInput作為AsyncCrudAppService類的第四個泛型參數(第三個是實體的PK類型)。然后我們重寫了CreateFilteredQuery方法來應用自定義過濾器。這個方法是AsyncCrudAppService類的一個自定義擴展點(WhereIf是ABP的一個擴展方法用來簡化條件過濾。實際上我們簡化了過濾IQueryable接口)。
注意:如果你創建了應用服務接口,你也應該為這個接口添加同樣的泛型參數。
注意,我們為getting,creating和updating任務使用同樣的DTO(TaskDto),這對真實的應用來說可能並不合適。所以,我們想自定義create和update DTOs。讓我們先從創建一個CreateTaskInput類開始:
[AutoMapTo(typeof(Task))] public class CreateTaskInput { [Required] [MaxLength(Task.MaxTitleLength)] public string Title { get; set; } [MaxLength(Task.MaxDescriptionLength)] public string Description { get; set; } public Guid? AssignedPersonId { get; set; } }
然后創建一個UpdateTaskInput DTO:
[AutoMapTo(typeof(Task))] public class UpdateTaskInput : CreateTaskInput, IEntityDto { public int Id { get; set; } public TaskState State { get; set; } }
我想繼承CreateTaskInput來包含更新操作所需要的所有屬性(但是你或許想要不一樣的)。這里,實現IEntity(或IEntity<PrimaryKey>來實現除int之外類型的PK)是需要的,因為我們需要知道哪個實體將要被更新。最后,我添加了一個額外的屬性,State,這個屬性不在CreateTaskInput類里。
現在,我們可以使用這些DTO類作為AsyncCrudAppService類的泛型參數,如下所示:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput, CreateTaskInput, UpdateTaskInput> { public TaskAppService(IRepository<Task> repository) : base(repository) { } protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input) { return base.CreateFilteredQuery(input) .WhereIf(input.State.HasValue, t => t.State == input.State.Value); } }
不需要更改其他代碼。
如果你想為Get和Delete方法定義input DTOs,AsyncCrudAppService可以接收更多泛型參數。同樣,基類的所有方法都是虛方法,所以,你可以重寫他們來自定義他們的行為。
你可能需要授權你的CRUD方法。有預定義的權限屬性可以設置:GetPermissionName,GetAllPermissionName,CreatePermissionName,UpdatePermissionName和DeletePermissionName。如果你設置了他們,基礎CRUD類自動檢測這些權限。你可以在構造函數中設置它,如下所示:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto> { public TaskAppService(IRepository<Task> repository) : base(repository) { CreatePermissionName = "MyTaskCreationPermission"; } }
作為選擇,你可以重寫恰當的權限檢查方法來手動檢查權限:CheckGetPermission(),CheckGetAllPermission(),CheckUpdatePermission(),CheckDeletePermission()。默認,他們都使用相關的權限名稱調用CheckPermission(...)方法,它只是簡單的調用了IPermissionChecker.Authorize(...)方法。
在ABP中,應用服務方法默認為一個工作單元。因此,任何應用服務方法都是事務的並在方法結束的時候自動保存數據庫的更改。
參見工作單元文檔了解更多。
所有的應用服務實例都是瞬態的。意味着,他們每次使用時實例化。參見依賴注入文檔了解更多信息。
返回主目錄