應用服務Application Services

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

首先,我們將GetAllTask​​sInput添加爲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(…)方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章