ABP理论学习之数据传输对象(DTO)
本篇目录
DTO用于应用层和 展现层间的数据传输。
展现层调用具有DTO参数的应用服务方法,然后应用服务使用领域对象来执行一些特定的业务逻辑,最后返回给展现层一个DTO。因此,展现层完全独立于领域层。在一个理想的分层应用中,展现层不直接和领域对象打交道(仓储,实体...)。
为何需要DTO###
为每个应用服务方法创建一个DTO起初可能被看作是一项乏味而又耗时的事情。但如果正确地使用它,那么DTOs可能会拯救你应用。为啥呢?
领域层抽象
DTO为展现层抽象领域对象提供了一种有效方式。这样,层与层之间就正确分离了。即使你想完全分离展现层,仍然可以使用已存在的应用层和领域层。相反,只要领域服务的契约(方法签名和DTOs)保持不变,即使重写领域层,完全改变数据库模式,实体和ORM框架,也不需要在展现层做任何改变。
数据隐藏
试想你有一个User实体,包含Id,Name,EmailAddress和Password字段。如果UserAppService的GetAllUsers()方法返回一个List,即使你没有在屏幕上显示它,那么任何人也都能看到所有user的密码。它不是涉及安全的,而是与数据隐藏相关的。应用服务都应该返回给展现层需要的,不要更多,也不很少,要的是恰到好处。
序列化和懒加载问题
当返回给展现层一个对象时,它很可能在某个地方序列化。比如,一个MVC方法返回JSON,一个对象会被序列化成JSON,然后发送到客户端。在那种情况,将一个实体返回到展现层是有问题的。这是怎么回事呢?
在一个真实应用中,实体之间是相互引用的。User实体可能有一个Role的引用。因此,如果你想序列化User,那么Role也会序列化。而且,如果Role有一个List且Permission类有一个PermissionGroup类的引用等等。你能想象所有的对象都会被序列化的那种场景吗?你可能会意外地序列化整个数据库。那么解决方案是什么呢?把属性标记为NonSerilized吗?不,你可能不知道它何时应该序列化,何时不应该。它可能在一个应用方法中需要,可能在另一个就不需要了。因此,在这种情景中,设计一个可安全序列化的,特别设计的DTOs是一种好的选择。
几乎所有的ORM框架都支持懒加载。它的特征是当需要时才从数据库中加载实体。假如说User类有一个Role类的引用。当从数据库中获得一个User时,此时Role属性还没有填充,当第一次读该Role属性时,它才从数据库中加载。因此,不要将这样的一个实体直接返回给展现层,它可能会轻易造成从数据库检索额外的实体。如果序列化工具读到了该实体,它会递归地读取所有属性,最终整个数据库可能会被检索(如果实体间有合适的关系)。
在展现层使用实体还会有更多的问题。最好压根不要在将包含领域(业务)层的程序集引用到展现层上。
DTO惯例和验证###
ABP高度支持DTOs,它提供了一些符合惯例的类和接口,并且对于DTO的命名和用法提出了一些建议。当按照下面描述的那样编写代码时,ABP会轻易地自动处理一些事情。
举个例子
让我们看一个完整的例子。假如我们想要开发一个应用服务方法,作用是使用一个名字来搜索人,并返回一个人的集合。这种情况下,我们可能会有一个如下的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/output参数命名为MethodNameInput和 MethodNameOutput,并为每个应用服务方法定义一个单独的input和output DTO。即使你的方法只需要或返回一个参数,最好也创建一个DTO类。这样,你的代码回更具有扩展性。以后你可以添加更多的属性而不用改变方法的签名,而且也不用使已存在的客户端应用发生重大变化。
当然,如果你的方法没有返回值,那么方法可以返回void。如果以后添加了一个返回值,也不会打破已存在的应用。如果你的方法不需要任何参数,那么你也不必定义一个输入DTO。但是如果未来很可能添加参数,那么也许最好还是编写一个输入DTO。这取决于你。
让我们看一下为这个例子定义的输入和输出的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; }
}
验证:按照惯例,输入DTO实现了 IInputDto接口,输出DTO实现了 IOutputDto接口。当实现了IInputDto时,ABP会在方法执行前自动验证输入。这和ASP.NET MVC的验证很相似,但是注意应用服务不是控制器,它是纯粹的C#类。ABP使用拦截来自动检查输入。关于更多的验证,请看下篇DTO验证。
EntityDto是一个声明了Id属性的简单类。因为这对于所有的实体都是公用的。如果你的实体的主键不是int的,那么还有一个泛型版本。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 };
}
}
我们从数据库中获得实体,再将它们转成DTOs,然后返回到输出。注意我们没有验证输入,因为ABP会自动验证。ABP甚至会检查输入参数是否为null,如果为null,就会抛出异常。
但是很可能你不喜欢从一个Person实体到一个PersonDto对象的转换代码。这是相当无聊的事情,而且,Person实体可能会有更多的属性。
DTO和实体的自动映射###
幸好,我们有工具可以让这个变得很简单。AutoMapper就是之一(要学习AutoMapper,请看我的AutoMapper系列教程。它已经发布到Nuget上了,你可以轻松地将它添加到项目中。让我们再次写一下SearchPeople方法,但是这次是用AutoMapper:
public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));
return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(peopleEntityList) };
}
这样就ok了。你可以给实体和DTO添加更多的属性而不需要转换代码做任何改变。唯一要做的事情就是在使用前定义一个映射:
Mapper.CreateMap<Person, PersonDto>();
AutoMapper创建了映射代码。这样,动态映射就不会成为性能问题了。它既快速又容易。AutoMapper为Person实体创建了PersonDto,并使用命名规范赋予DTO属性。命名规范可能是复杂的且可配置的。此外,你还可以定义自定义映射以及更多。
使用特性和扩展方法进行映射
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>(); //从obj1的副本创建一个新的MyClass2对象
上面的代码从MyClass1的对象创建了MyClass2一个新的对象。此外,你可以像下面那样,映射到一个已存在的对象:
var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2);
帮助接口###
ASP.NET 提供了一些实现标准化公共DTO属性名称的帮助接口。
ILimitedResultRequest定义了 MaxResultCount属性。这样你就可以在你的输入DTO中实现它来标准化有限的结果集。
IPagedResultRequest通过添加了 SkipCount扩展了 ILimitedResultRequest。这样,我们可以在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的输出DTO。命名标准化帮助我们创建可重复使用的代码和惯例。你也可以在 Abp.Application.Services.Dto命名空间下看到其他的接口和类。
ABP理论学习之数据传输对象(DTO)的更多相关文章
- 应用程序框架实战三十四:数据传输对象(DTO)介绍及各类型实体比较
本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行比较. 领域实体为何不能一统江湖? 当你阅读 ...
- 数据传输对象(DTO)介绍及各类型实体比较
数据传输对象(DTO)介绍及各类型实体比较 本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行 ...
- ABP框架 - 验证数据传输对象
文档目录 本节内容: 简介 使用数据注解 自定义验证 禁用验证 正常化 简介 一个应用的输入应当先要验证,这个输入可能来自用户或另一个应用,在一个web应用里,验证通常实现两次:在客户端和在服务端,客 ...
- ABP(现代ASP.NET样板开发框架)系列之16、ABP应用层——数据传输对象(DTOs)
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Project ...
- ABP应用层——数据传输对象(DTOs)
ABP应用层——数据传输对象(DTOs) 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Pro ...
- 为什么需要DTO(数据传输对象)
DTO即数据传输对象.之前不明白有些框架中为什么要专门定义DTO来绑定表现层中的数据,为什么不能直接用实体模型呢,有了DTO同时还要维护DTO与Model之间的映射关系,多麻烦. 然后看了这篇文章中的 ...
- 我们为什么需要DTO(数据传输对象)
原文:http://www.cnblogs.com/Gyoung/archive/2013/03/23/2977233.html DTO即数据传输对象(Data Transfer Object).之前 ...
- (扫盲)DTO数据传输对象
DTO即数据传输对象.但从定义上看就是简单的用来传递数据的.主要用途是在框架中定义DTO来绑定表现层中的数据.学过MVC.EF实体模型的都应该知道,我们可以定义一个Model实体来实现前后台数据的交互 ...
- Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...
随机推荐
- c++ boost asio库初学习
前些日子研究了一个c++的一个socket库,留下范例代码给以后自己参考. 同步server: // asio_server.cpp : コンソール アプリケーションのエントリ ポイントを定義します. ...
- 被Unity5坑惨了
各种不明所以的crash,导致crash率从0.5%瞬间暴涨到10%. Unity5还是非常不稳定,慎入慎入...
- 【C++设计模式】单件类与DCLP(Double Check Lock Pattern)的风险
[单件类] 保证只能有一个实例化对象,并提供全局的访问入口. [设计注意事项] 1.阻止所有实例化的方法: private 修饰构造函数,赋值构造函数,赋值拷贝函数. 2.定义单实例化对象的方法: a ...
- 压力测试相关之ab命令
1. 短时压力测试工具 ab 命令(apache的工具) 关键指标: Requests per second: 98.52 [#/sec] (mean) ###平均每秒的请求数 Tim ...
- [转]oracle 分析函数over
oracle 分析函数over 分析函数(OVER) 目录: =============================================== 1.Oracle分析函数简介 2. O ...
- SOAPUI使用教程-REST Service Mocking
REST服务模拟 REST服务模拟功能允许您创建一个REST服务模拟器-MockService . 好处 RESTMockServices可用于例如: Web服务的原型 ——几秒内从请求生成一个完整的 ...
- 2016-2017 ACM-ICPC Southwestern European Regional Programming Contest (SWERC 2016)
A. Within Arm's Reach 留坑. B. Bribing Eve 枚举经过$1$号点的所有直线,统计直线右侧的点数,旋转卡壳即可. 时间复杂度$O(n\log n)$. #includ ...
- 【转】Oracle索引的类型
数据库的应用类型分为 OLTP(OnLine Transaction Processing ,联机事务处理):OLTP是传统关系型数据库的主要应用,其主要面向基本的.日常的事务处理,例如银行交易. O ...
- Selenium_IEDriver操作sendkeys输入速度太慢
通过调用64位IEDriverServer来操控sendkeys方式时,输入速度非常慢.网上说是64位有bug. 解决办法: 使用32位IEDriverServer.
- Ubuntu install JDK适合像我的小白
1.#下载JDK,记住保存的目录 2. sudo mkdir /usr/java 3. sudo tar zxvf jdk-7u75-linux-x64.tar.gz -C /usr/java 4. ...