Application Services用於將域邏輯公開給表示層。使用DTO(數據傳輸對象)作爲參數從表示層調用應用服務。它還使用域對象來執行某些特定的業務邏輯,並將DTO返回給表示層。因此,表示層與域層完全隔離。
在理想的分層應用程序中,表示層永遠不會直接使用域對象。
IApplicationService接口
在ASP.NET Boilerplate中,應用程序服務應實現 IApplicationService接口。 爲每個Application Service 創建一個接口是很好的。
首先,讓我們爲應用程序服務定義一個接口:
public interface IPersonAppService : IApplicationService
{
void CreatePerson(CreatePersonInput input);
}
IPersonAppService只有一個方法。它由表示層用於創建新人。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 來執行數據庫操作。它使用構造函數注入 模式,從而使用依賴注入。
PersonAppService實現IApplicationService(因爲IPersonAppService擴展了IApplicationService)。它由ASP.NET Boilerplate自動註冊到Dependency Injection系統,可以被其他類注入和使用。命名約定在這裏很重要。有關詳細信息,請參閱依賴項注入文檔。
該CreatePerson方法獲取CreatePersonInput。它是一個輸入DTO,由ASP.NET Boilerplate自動驗證。有關 詳細信息,請參閱 DTO和 驗證文檔。
ApplicationService類
應用程序服務應實現上面聲明的IApplicationService接口。(可選)它可以從ApplicationService基類派生 。因此,IApplicationService本身就是實現的。
ApplicationService類具有一些基本功能,可以輕鬆地進行日誌記錄, 本地化等等…建議您爲擴展ApplicationService類的應用程序服務創建一個特殊的基類。這樣,您可以爲所有應用程序服務添加一些常用功能。示例應用程序服務類如下所示:
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。這樣您就不會爲所有服務類重複它。有關此主題的更多信息,請參閱日誌記錄和 本地化文檔。
CrudAppService和AsyncCrudAppService類
如果您需要爲特定實體創建具有Create,Update,Delete,Get,GetAll方法的應用程序服務,則可以輕鬆地從CrudAppService類繼承。您還可以使用AsyncCrudAppService類來創建異步方法。CrudAppService基類是通用的,它將相關的Entity和 DTO類型作爲通用參數。這也是可擴展的,允許您在需要自定義時覆蓋功能。
簡單的CRUD應用服務示例
假設我們在下面定義了一個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應用程序服務
獲得一份清單
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);
}
}
首先,我們將GetAllTasksInput添加爲AsyncCrudAppService類的第4個通用參數(第3個是實體的PK類型)。然後我們重寫CreateFilteredQuery方法以應用自定義過濾器。此方法是AsyncCrudAppService類的擴展點。注意,WhereIf是ABP的擴展方法,以簡化條件過濾。我們在這裏做的只是過濾IQueryable。
注意:如果您創建了應用程序服務接口,則還需要爲該接口添加相同的通用參數!
創建和更新
請注意,我們使用相同的DTO(TaskDto)來獲取,創建 和更新可能對現實應用程序不利的任務,因此我們可能希望自定義創建和更新DTO。
讓我們從創建CreateTaskInput類開始:
[AutoMapTo(typeof(Task))]
public class CreateTaskInput
{
[Required]
[StringLength(Task.MaxTitleLength)]
public string Title { get; set; }
[StringLength(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以包含Update操作的所有屬性(您可能需要不同的東西)。實施 IEntity(對於不同的PK比int或IEntity <的PrimaryKey>)被 要求在這裏,因爲我們需要知道哪個實體正在更新。最後,我們添加了一個附加屬性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方法自定義輸入DTO,AsyncCrudAppService可以獲得更多通用參數。基類的所有方法都是虛擬的,因此您可以覆蓋它們以自定義行爲。
CRUD權限
您需要授權您的CRUD方法嗎?如果是,則可以設置預定義的權限屬性:GetPermissionName,GetAllPermissionName,CreatePermissionName,UpdatePermissionName和DeletePermissionName。如果設置了基本CRUD類,則會自動檢查權限。
在這裏,您可以在構造函數中設置它:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>
{
public TaskAppService(IRepository<Task> repository)
: base(repository)
{
CreatePermissionName = "MyTaskCreationPermission";
}
}
或者,您可以覆蓋相應的權限檢查器方法以手動檢查權限:CheckGetPermission(),CheckGetAllPermission(),CheckCreatePermission(),CheckUpdatePermission(),CheckDeletePermission()。默認情況下,它們都使用相關的權限名稱調用CheckPermission(…)方法。
簡單地說,這會調用IPermissionChecker.Authorize(…)方法。