1 為何需要DTO
為每個應用服務方法創(chuàng)建一個DTO起初可能被看作是一項乏味而又耗時的事情。但如果正確地使用它斧账,那么DTOs可能會拯救你應用出牧。為啥呢?
1.1 領域層抽象
DTO為展現層抽象領域對象提供了一種有效方式搏恤。這樣,層與層之間就正確分離了湃交。即使你想完全分離展現層熟空,仍然可以使用已存在的應用層和領域層。相反搞莺,只要領域服務的契約(方法簽名和DTOs)保持不變息罗,即使重寫領域層,完全改變數據庫模式才沧,實體和ORM框架迈喉,也不需要在展現層做任何改變绍刮。
1.2 數據隱藏
試想你有一個User實體,包含Id挨摸,Name孩革,EmailAddress和Password字段。如果UserAppService的GetAllUsers()方法返回一個List得运,即使你沒有在屏幕上顯示它膝蜈,那么任何人也都能看到所有user的密碼。它不是涉及安全的熔掺,而是與數據隱藏相關的饱搏。應用服務都應該返回給展現層需要的,不要更多置逻,也不很少推沸,要的是恰到好處。
1.3 序列化和懶加載問題
在一個真實應用中诽偷,實體之間是相互引用的坤学。User實體可能有一個Role的引用。因此报慕,如果你想序列化User,那么Role也會序列化压怠。而且眠冈,如果Role有一個List且Permission類有一個PermissionGroup類的引用等等。你能想象所有的對象都會被序列化的那種場景嗎菌瘫?你可能會意外地序列化整個數據庫蜗顽。那么解決方案是什么呢?把屬性標記為NonSerilized嗎?不雨让,你可能不知道它何時應該序列化雇盖,何時不應該。它可能在一個應用方法中需要栖忠,可能在另一個就不需要了崔挖。因此,在這種情景中庵寞,設計一個可安全序列化的狸相,特別設計的DTOs是一種好的選擇。
幾乎所有的ORM框架都支持懶加載捐川。它的特征是當需要時才從數據庫中加載實體脓鹃。假如說User類有一個Role類的引用。當從數據庫中獲得一個User時古沥,此時Role屬性還沒有填充瘸右,當第一次讀該Role屬性時娇跟,它才從數據庫中加載。因此太颤,不要將這樣的一個實體直接返回給展現層逞频,它可能會輕易造成從數據庫檢索額外的實體。如果序列化工具讀到了該實體栋齿,它會遞歸地讀取所有屬性苗胀,最終整個數據庫可能會被檢索(如果實體間有合適的關系)。
在展現層使用實體還會有更多的問題瓦堵。最好壓根不要在將包含領域(業(yè)務)層的程序集引用到展現層上基协。
2 DTO慣例和驗證
BatchDeleteInput
using System.Collections.Generic;
namespace Rdf.Application.Services.Dto
{
public class BatchDeleteInput : IInputDto
{
public List<string> IdList { get; set; }
}
}
JsonMessage
namespace Rdf.Application.Services.Dto
{
public class JsonMessage : IOutputDto
{
public int ErrCode { get; set; }
public string ErrMsg { get; set; }
public object Result { get; set; }
}
}
PageInput
namespace Rdf.Application.Services.Dto
{
public class PageInput : IInputDto
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public string OrderBy { get; set; }
public string Keyword { get; set; }
}
}
PageOutput
using System.Collections;
namespace Rdf.Application.Services.Dto
{
public class PageOutput : IOutputDto
{
public int Total { get; set; }
public IEnumerable Records { get; set; }
}
}
IDto
namespace Rdf.Application.Services.Dto
{
/// <summary>
/// This interface must be implemented by all DTO classes to identify them by convention.
/// </summary>
public interface IDto
{
}
}
IValidate
namespace Rdf.Application.Services.Dto
{
/// <summary>
/// This interface is implemented by classes those are needed to validate before use.
/// </summary>
public interface IValidate
{
}
}
IInputDto
namespace Rdf.Application.Services.Dto
{
/// <summary>
/// This interface is used to define DTOs those are used as input parameters.
/// </summary>
public interface IInputDto : IDto, IValidate
{
}
}
IOutputDto
namespace Rdf.Application.Services.Dto
{
/// <summary>
/// This interface is used to define DTOs those are used as output parameters.
/// </summary>
public interface IOutputDto : IDto
{
}
}
3 DTO和實體的自動映射
幸好,我們有工具可以讓這個變得很簡單菇用,AutoMapper就是之一澜驮。
Configuration.cs
using AutoMapper;
namespace Rdf.Web.AutoMapper
{
public class Configuration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<Profiles.ActProfile>();
});
}
}
}
ActProfile.cs
using AutoMapper;
using Rdf.Application.Act.Dtos;
using Rdf.Domain.Entities.Act;
namespace Rdf.Web.AutoMapper.Profiles
{
public class ActProfile : Profile
{
protected override void Configure()
{
CreateMap<CreateRoleInput, Role>();
}
}
}
Startup.cs
// InitMapping
AutoMapper.Configuration.Configure();