1. 程式人生 > >ABP官方文件(二十三)【應用服務】

ABP官方文件(二十三)【應用服務】

4.1 ABP應用層 - 應用服務

應用服務用於將領域(業務)邏輯暴露給展現層。展現層通過傳入DTO(資料傳輸物件)引數來呼叫應用服務,而應用服務通過領域物件來執行相應的業務邏輯並且將DTO返回給展現層。因此,展現層和領域層將被完全隔離開來。在一個理想的層級專案中,展現層應該從不直接訪問領域物件。

4.1.1 IApplicationService介面

在ABP中,一個應用服務需要實現 IApplicationService 介面。最好的實踐是針對每個應用服務都建立相應的介面。所以,我們首先定義一個應用服務介面,如下所示:

public interface IPersonAppService : IApplicationService
{
    void
CreatePerson(CreatePersonInput input); }

IPersonAppService 只有一個方法,它將被展現層呼叫來建立一個新的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 型別的引數。這是一個作為輸入的DTO,它將被ABP自動驗證其資料有效性。可以檢視DTO和資料有效性驗證(Validation)文件獲取相關細節。

4.1.2 應用服務類

應用服務(Application Services)需要實現IApplicationService介面。當然,你可以選擇將你的應用服務(Application Services)繼承自 ApplicationService 基類,這樣你的應用服務也就自然而然的實現 IApplicationService 介面了。ApplicationService基類提供了方便的日誌記錄和本地化功能。在此建議你針對你的應用程式建立一個應用服務基類繼承自ApplicationService型別。這樣你就可以新增一些公共的功能來提供給你的所有應用服務使用。一個應用服務示例如下所示:

public class TaskAppService : ApplicationService, ITaskAppService
{
    public TaskAppService()
    {
        LocalizationSourceName = "SimpleTaskSystem";
    }

    public void CreateTask(CreateTaskInput input)
    {
        //記錄日誌,Logger定義在ApplicationService中
        Logger.Info("Creating a new task with description: " + input.Description);

        //獲取本地化文字(L是LocalizationHelper.GetString(...)的簡便版本, 定義在 ApplicationService型別)
        var text = L("SampleLocalizableTextKey");

        //TODO: Add new task to database...
    }
}

本例中我們在建構函式中定義了 LocalizationSourceName,但你可以在基類中定義它,這樣你就不需要在每個具體的應用服務中定義它。檢視日誌記錄(logging)和本地化(localization)文件可以獲取更多的相關資訊。

4.1.3 CrudAppService 和 AsyncCrudAppService

如果你想為某個特定的實體建立基於CRUD的應用服務,該服務具有這些方法:Create,Delete,Get,GetAll;那麼(為了方便快捷),我們可以繼承 CrudAppService(或者 AsyncCrudAppService 如果你想要建立async方法)類來簡單實現這些功能。CrudAppService 是一個可擴充套件的泛型基類,該類的泛型引數是對指定實體操作與之相關的 Entity和DTO 類,並且你可以根據你的需要自定義覆蓋基類的功能。

關於CRUD的簡單應用服務示例

假設我們有一個任務實體的定義,如下所示:

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)
    {

    }
}

我們注入了倉儲並且傳遞該引數給基類(如果我們想要建立 sync 方法來替代 async 方法,那麼我們可以繼承自 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

Getting List

CRUD 應用服務使用 PagedAndSortedResultRequestDto 作為方法 GetAll 的預設引數,該引數提供了可選的排序和分頁引數。但是你可能想新增其他引數到GetAll方法。例如,你可能想新增某些自定義的過濾。這樣的話,你可以為GetAll方法建立一個DTO。如下所示:

public class GetAllTasksInput : PagedAndSortedResultRequestDto
{
    public TaskState? State { get; set; }
}

我們繼承 PagedAndSortedResultRequestInput 類(當然這不是必須的,如果你想要使用基類的分頁和排序引數的話)並且新增一個可選的 State 屬性,通過State屬性來過濾Task。現在,為了能夠應用自定義過濾,我們應該改變 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 作為第4個泛型引數到AsyncCrudAppService類(第三個引數是實體的主鍵)。然後,重寫 CreateFilteredQuery 方法來應用自定義過濾。這個方法是作為 AsyncCrudAppService 類的一個擴充套件點(為了簡化條件過濾,我們使用了ABP的一個擴充套件方法WhereIf,但實際上我們所做的就是對IQueryable的過濾)。

注意:如果你已經建立應用服務的介面,那麼你也需要為該介面新增泛型引數。

Create和Update

注意:我們使用相同的DTO(TaskDto)去取得,建立以及更新任務;但是在實際的應用中,這樣使用可能不太好。所以,我們需要 自定義建立和更新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>介面) 是必須(Required)的;因為,我們需要知道那個實體需要更新。最後,我還添加了一個不在CreateTaskInput類中的屬性 State

現在,我們可以使用這些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類能設定更多的泛型引數。那麼,所有的基類方法都是virtual,這樣你就可以重寫或者自定義這些行為。

4.1.4 CRUD 許可權

你可能需要授權給CRUD方法。ABP中有一些預定義的許可權屬性可以設定:GetPermissionNam,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(...)方法。

4.1.5 工作單元

在ABP中應用服務中的方法預設是一個工作單元。因此,任意應用服務方法都是事務的並在方法最後自動儲存所有更改到資料庫中。

瞭解更多請查詢工作單元

4.1.6 應用服務的生命週期

所有應用服務(Application Services)例項的生命週期都是暫時的(Transient)。這意味著在每次使用都會建立新的應用服務(Application Services)例項。ABP堅決地使用依賴注入技術。當一個應用服務(Application Services)型別需要被注入時,該應用服務(Application Services)型別的新例項將會被依賴注入容器自動建立。檢視依賴注入(Dependency Injection)文件獲取更多資訊。