ABP(現代ASP.NET樣板開發框架)系列之1六、ABP應用層——數據傳輸對象(DTOs)

點這裏進入ABP系列文章總目錄html

 

基於DDD的現代ASP.NET開發框架--ABP系列之1六、ABP應用層——數據傳輸對象(DTOs)
git

 

ABP是「ASP.NET Boilerplate Project (ASP.NET樣板項目)」的簡稱。github

ABP的官方網站http://www.aspnetboilerplate.com數據庫

ABP在Github上的開源項目https://github.com/aspnetboilerplate安全

 


 

數據傳輸對象(Data Transfer Objects)用於應用層和展示層的數據傳輸。架構

展示層傳入數據傳輸對象(DTO)調用一個應用服務方法,接着應用服務經過領域對象執行一些特定的業務邏輯而且返回DTO給展示層。這樣展示層和領域層被徹底分離開了。在具備良好分層的應用程序中,展示層不會直接使用領域對象(倉庫,實體)。併發

數據傳輸對象的做用

爲每一個應用服務方法建立DTO看起來是一項乏味耗時的工做。但若是你正確使用它們,這將會解救你的項目。爲啥呢?app

(1)抽象領域層 (Abstraction of domain layer)框架

在展示層中數據傳輸對象對領域對象進行了有效的抽象。這樣你的層(layers)將被恰當的隔離開來。甚至當你想要徹底替換展示層時,你還能夠繼續使用已經存在的應用層和領域層。反之,你能夠重寫領域層,修改數據庫結構,實體和ORM框架,但並不須要對展示層作任何修改,只要你的應用層沒有發生改變。dom

(2)數據隱藏 (Data hiding)

想象一下,你有一個User實體擁有屬性Id, Name, EmailAddress和Password。若是UserAppService的GetAllUsers()方法的返回值類型爲List。這樣任何人均可以查看全部人的密碼,即便你沒有將它打印在屏幕上。這不只僅是安全問題,這還跟數據隱藏有關。應用服務應只返回展示層所須要的,很少很多剛恰好。

(3)序列化 & 惰性加載 (Serialization & lazy load problems)

當你將數據(對象)返回給展示層時,數據有可能會被序列化。舉個例子,在一個返回Json的MVC的Action中,你的對象須要被序列化成JSON併發送給客戶端。直接返回實體給展示層將有可能會出現麻煩。

在真實的項目中,實體會引用其餘實體。User實體會引用Role實體。因此,當你序列化User時,Role也將被序列化。並且Role還擁有一個List而且Permission還引用了PermissionGroup等等….你能想象這些對象都將被序列化嗎?這有頗有可能使整個數據庫數據意外的被序列化。那麼該如何解決呢?將屬性標記爲不可序列化?不行,由於你不知道屬性什麼時候該被序列化什麼時候不應序列化。因此在這種狀況下,返回一個可安全序列化,特別定製的數據傳輸對象是不錯的選擇哦。

幾乎全部的ORM框架都支持惰性加載。只有當你須要加載實體時它纔會被加載。好比User類型引用Role類型。當你從數據庫獲取User時,Role屬性並無被填充。當你第一次讀取Role屬性時,纔會從數據庫中加載Role。因此,當你返回這樣一個實體給展示層時,很容易引發反作用(從數據庫中加載)。若是序列化工具讀取實體,它將會遞歸地讀取全部屬性,這樣你的整個數據庫都將會被讀取。

在展示層中使用實體還會有更多的問題。最佳的方案就是展示層不該該引用任何包含領域層的程序集。

 DTO 約定 & 驗證

ABP對數據傳輸對象提供了強大的支持。它提供了一些相關的(Conventional)類型 & 接口並對DTO命名和使用約定提供了建議。當你像這裏同樣使用DTO,ABP將會自動化一些任務使你更加輕鬆。

一個例子 (Example)

讓咱們來看一個完整的例子。咱們相要編寫一個應用服務方法根據name來搜索people並返回people列表。Person實體代碼以下:

public class Person : Entity
{
    public virtual string Name { get; set; }
    public virtual string EmailAddress { get; set; }
    public virtual string Password { get; set; }
}

首先,咱們定義一個應用服務接口:

public interface IPersonAppService : IApplicationService
{
    SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ABP建議命名input/ouput對象相似於MethodNameInput/MethodNameOutput,對於每一個應用服務方法都須要將Input和Output進行分開定義。甚至你的方法只接收或者返回一個值,也最好建立相應的DTO類型。這樣,你的代碼纔會更具備擴展性,你能夠添加更多的屬性而不須要更改方法的簽名,這並不會破壞現有的客戶端應用。

固然,方法返回值有多是void,以後你添加一個返回值並不會破壞現有的應用。若是你的方法不須要任何參數,那麼你不須要定義一個Input Dto。可是建立一個Input Dto多是個更好的方案,由於該方法在未來有可能會須要一個參數。固然是否建立這取決於你。 Input和Output DTO類型定義以下:

public class SearchPeopleInput : IInputDto
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }
}

public class SearchPeopleOutput : IOutputDto
{
    public List<PersonDto> People { get; set; }
}

public class PersonDto : EntityDto
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

驗證:做爲約定,Input DTO實現IInputDto 接口,Output DTO實現IOutputDto接口。當你聲明IInputDto參數時, 在方法執行前ABP將會自動對其進行有效性驗證。這相似於ASP.NET MVC驗證機制,可是請注意應用服務並非一個控制器(Controller)。ABP對其進行攔截並檢查輸入。查看DTO 驗證(DTO Validation)文檔獲取更多信息。 EntityDto是一個簡單具備與實體相同的Id屬性的簡單類型。若是你的實體Id不爲int型你可使用它泛型版本。EntityDto也實現了IDto接口。你能夠看到PersonDto並不包含Password屬性,由於展示層並不須要它。

跟進一步以前咱們先實現IPersonAppService:

public class PersonAppService : IPersonAppService
{
    private readonly IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }
    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
    {
        //獲取實體
        var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

        //轉換成DTO
        var peopleDtoList = peopleEntityList
            .Select(person => new PersonDto
                                {
                                    Id = person.Id,
                                    Name = person.Name,
                                    EmailAddress = person.EmailAddress
                                }).ToList();

        return new SearchPeopleOutput { People = peopleDtoList };
    }
}

咱們從數據庫獲取實體,將實體轉換成DTO並返回output。注意咱們沒有手動檢測Input的數據有效性。ABP會自動驗證它。ABP甚至會檢查Input是否爲null,若是爲null則會拋出異常。這避免了咱們在每一個方法中都手動檢查數據有效性。

可是你極可能不喜歡手動將Person實體轉換成PersonDto。這真的是個乏味的工做。Peson實體包含大量屬性時更是如此。

DTO和實體間的自動映射

還好這裏有些工具可讓映射(轉換)變得十分簡單。AutoMapper就是其中之一。你能夠經過nuget把它添加到你的項目中。讓咱們使用AutoMapper來重寫SearchPeople方法:

public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
    var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));
    return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(peopleEntityList) };
}

這就是所有代碼。你能夠在實體和DTO中添加更多的屬性,可是轉換代碼依然保持不變。在這以前你只須要作一件事:映射

Mapper.CreateMap<Person, PersonDto>();

AutoMapper建立了映射的代碼。這樣,動態映射就不會成爲性能問題。真是快速又方便。AutoMapper根據Person實體建立了PersonDto,並根據命名約定來給PersonDto的屬性賦值。命名約定是可配置的而且很靈活。你也能夠自定義映射和使用更多特性,查看AutoMapper的文檔獲取更多信息。

使用特性(attributes)和擴展方法來映射 (Mapping using attributes and extension methods)

ABP提供了幾種attributes和擴展方法來定義映射。使用它你須要經過nuget將Abp.AutoMapper添加到你的項目中。使用AutoMap特性(attribute)能夠有兩種方式進行映射,一種是使用AutoMapFrom和AutoMapTo。另外一種是使用MapTo擴展方法。定義映射的例子以下:

[AutoMap(typeof(MyClass2))] //定義映射(這樣有兩種方式進行映射)
public class MyClass1
{
    public string TestProp { get; set; }
}

public class MyClass2
{
    public string TestProp { get; set; }
}

接着你能夠經過MapTo擴展方法來進行映射:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = obj1.MapTo<MyClass2>(); //建立了新的MyClass2對象,並將obj1.TestProp的值賦值給新的MyClass2對象的TestProp屬性。
上面的代碼根據MyClass1建立了新的MyClass2對象。你也能夠映射已存在的對象,以下所示:
var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2); //根據obj1設置obj2的屬性

 輔助接口和類型

ABP還提供了一些輔助接口,定義了經常使用的標準化屬性。

ILimitedResultRequest定義了MaxResultCount屬性。因此你能夠在你的Input DTO上實現該接口來限制結果集數量。

IPagedResultRequest擴展了ILimitedResultRequest,它添加了SkipCount屬性。因此咱們在SearchPeopleInput實現該接口用來分頁:

public class SearchPeopleInput : IInputDto, IPagedResultRequest
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }

    public int MaxResultCount { get; set; }
    public int SkipCount { get; set; }
}

對於分頁請求,你能夠將實現IHasTotalCount的Output DTO做爲返回結果。標準化屬性幫助咱們建立可複用的代碼和規範。可在Abp.Application.Services.Dto命名空間下查看其餘的接口和類型。

 


 

但願更多國內的架構師能關注到ABP這個項目,也許這其中有能幫助到您的地方,也許有您的參與,這個項目能夠發展得更好。

歡迎加QQ羣:

ABP架構設計交流羣:134710707 ABP架構設計交流羣      ABP架構設計交流2羣: 579765441ABP架構設計交流羣2

 

點這裏進入ABP系列文章總目錄

相關文章
相關標籤/搜索