程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> <<ABP框架>> 數據傳輸對象,abp框架

<<ABP框架>> 數據傳輸對象,abp框架

編輯:關於.NET

<<ABP框架>> 數據傳輸對象,abp框架


文檔目錄

 

本節內容:

  • DTO 必要性
    • 領域層的抽象
    • 數據隱藏
    • 序列化和延遲加載問題
  • DTO 約定和驗證
    • 示例
  • DTO和實體間自動映射
    • 使用特性和擴展方法進行映射
  • 輔助接口和類

 

Data Transfer Objects(DTO)用來在應用層和展現層之間傳輸數據。

展現層使用一個DTO調用一個應用服務方法,然後應用服務使用服務對象執行一些特定業務邏輯,並返回一個DTO給展現層。因此,展現層是完全獨立於領域層的。在一個理想的分層應用裡,展現層不直接使用領域對象(倉儲、實體...)。

 

DTO 必要性

首先為每個應用服務方法創建一個DTO看起來是件乏味且費時的工作,但如果你正確使用它,它能解救你的應用。為什麼呢?

 

領域層的抽象

dto提供一個有效的方法從展現層抽象領域對象,因此,你的層正確分離開,即使你想完全地改變展現層,也可以繼續使用已存在的應用和領域層。相反,你可以重寫你的領域層、完全改變數據庫結構、實體和ORM框架,只要你的應用服務契約(方法簽名和DTO)保持不變,展現層也不用做任何修改。

 

數據隱藏

考慮一下:你有一個User實體,它有Id、Name、EmailAddress和Password屬性,如果UserAppService的GetAllusers()方法返回一個List<User>,任何人都可以看到所有用戶的密碼,即使你沒有在屏幕上顯示它,也是不安全的。不只是數據安全,還有關於數據的隱藏,應用服務應該只向展現層返回必要的數據,不多也不少。

 

序列化和延遲加載問題

當你返回一個數據(一個對象)給展現層時,它可能會在某處被序列化,例如:在一個返回Json的MVC方法裡,你的對象會被序列化成JSON,然後發送給客戶端,在這種情況下,如果返回一個實體給展示層可能會有問題,為什麼呢?

在一個真實的應用裡,你的實體間可能存在相互引用,User實體可能關聯到Roles,所以如果你想序列化User,那麼它的Roles也要被序列化,而Role類可能包含一個List<Permission>,Permission類可能又關聯到PermissionGroup類等等。你能想到序列化這些對象,可能你就意外的序列化了你整個數據庫,而如果你的對象存在循環引用,它就不能被序列化了。

怎麼解決呢?把屬性標記為NonSerialized(不序列化)?不,你不知道它何時應當被序列化又何時不應當被序列化,可能在這個應用服務裡要序列化,而在另一個服務裡不要序列化,所以返回一個安全地可序列化的,特殊設計的DTO是一個好的選擇。

幾乎所有ORM框架都支持延遲加載,它是一個在需要時從數據庫加載實體的特性。假設User類有一個指向Role類的引用,當你從數據庫獲取一個User時,Role屬性沒有被填充,當你第一次讀取Role屬性時,它再從數據庫中加載。所以你返回這麼一個實體給展現層,它將去數據庫獲取額外的實體。如果一個序列化工具讀取這個實體,它遞歸讀取所有屬性,可能又會序列化你整個數據庫(如果實體間存在適當的關系)。

我們可以說出在展現層使用實體的更多問題,最好的做法是在應用層裡不引用包含領域(業務)層的程序集。

 

DTO 約定和驗證

ABP強支持DTO,它提供了一些約定類和接口,並建議了一些命名和使用約定,當你如本節描述的這樣去寫代碼,ABP會自動完成一些任務。

 

示例

讓我們看一個完整的示例,假設我們想開發一個通過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建議命名輸入/輸出參數為:MethodNameInput和MethodNameOutput,並為每個應用服務方法定義單獨的輸入和輸入DTO。即使你的方法只接受/返回一個參數,也最好是創建一個DTO類,因為你的代碼將來可能需要擴展,你可以稍後添加更多屬性,而不必修改你方法的簽名也不用打斷你已存在的客戶端應用。

當然,如果你的方法沒有返回值,也就是void,如果你在以後添加一個返回值,它也不會打斷已存在的應用。如果你的方法沒有參數,你不需要定義一個輸入DTO,但如果將來可能會添加參數,最好先添加一個輸入DTO類,這取決於你。

讓我們看一下這個例子的輸入和輸出DTO類:

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

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

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

在方法開始運行前,ABP會自動驗證輸入,這類似於Asp.net Mvc的驗證,但請注意:應用服務不是一個控制器,它就是一個單純的C#類,ABP攔截它並自動檢查輸入。有很多的驗證,請查閱DTO 驗證文檔。

EntityDto是一個實體通用只定義Id屬性的簡單類,如果你有實體主鍵不是int,有一個泛型版本可以用。你可以不用EntityDto,但最好定義一個Id屬性。

PersonDto如你所見,不包含Password屬性,因為展現層不需要它,並且發送所有用戶的密碼給展現層也是危險的,想象一下:一個Javascript客戶端請求它,任何人可以很容易地拿到所有密碼。

更進一步前,讓我們實現IPersonAppService:

public class PersonAppService : IPersonAppService
{
    private readonly IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }

    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
    {
        //Get entities
        var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

        //Convert to DTOs
        var peopleDtoList = peopleEntityList
            .Select(person => new PersonDto
                                {
                                    Id = person.Id,
                                    Name = person.Name,
                                    EmailAddress = person.EmailAddress
                                }).ToList();

        return new SearchPeopleOutput { People = peopleDtoList };
    }
}

我們從數據庫獲取實體,把它們轉換成DTO再返回給輸出,注意:我們沒有驗證輸入,ABP驗證了它,它甚至驗證了輸入參數是否為空,為空時拋出異常,這就省得我們在每個方法裡寫驗證代碼。

但是你可能不喜歡寫把一個Person實體轉換成PersonDto對象的代碼,它是確實是一個乏味的工作,Person實體可能包含很多屬性。

 

DTO和實體間自動映射

幸運地是:有工具使這件事變得容易,AutoMapper是其中之一,它發布在nuget上,你可以很容易地把它加入到你的項目裡。讓我們使用AutoMap再寫一下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,並按命名約定給DTO的屬性賦值。命名約定可以很復雜和配置,同樣,你也可以定義自己的配置及更多內容。更多信息查詢AutoMapper的文檔。

你可在你的模塊裡的PostInitialzie裡定義映射。

 

使用特性和擴展方法進行映射

ABP提供了多個特性和擴展方法用來定義映射,為使用它,先在你的項目裡添加Abp.AutoMapper的nuget包,然後使用AutoMap特性進行雙向映射,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

上面的代碼從一個MyClass1對象創建一個新的MyClass2對象,同樣,你也可以映射一個已存在的對象,如下所示:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2); //從obj1設置obj2的屬性

 

輔助接口和方法

ABP提供一些輔助接口,在被實現時,標准化通用DTO屬性名。

ILimitedResultRequest定義了MaxResultCount屬性,所以你可以在你的輸入DTO類裡實現它,用來標准化限制結果集。

IPagedResultRequst擴展了ILimitedResultRequest,添加了SkipCount。所以我們可以為SearchPeopleInput實現它,幫助分頁:

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

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

做為一個分頁的結果,你可以返回一個實現了IHasTotalCount的輸出DTO。命名標准化幫助我們創建可重用的代碼和約定。在Abp.Application.Services.Dto命名空間下查看其它接口和類。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved