返回总目录


本篇目录

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特性是双向映射方式, AutoMapFromAutoMapTo是单向映射方式。最后,使用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)的更多相关文章

  1. 应用程序框架实战三十四:数据传输对象(DTO)介绍及各类型实体比较

    本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行比较. 领域实体为何不能一统江湖? 当你阅读 ...

  2. 数据传输对象(DTO)介绍及各类型实体比较

    数据传输对象(DTO)介绍及各类型实体比较 本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行 ...

  3. ABP框架 - 验证数据传输对象

    文档目录 本节内容: 简介 使用数据注解 自定义验证 禁用验证 正常化 简介 一个应用的输入应当先要验证,这个输入可能来自用户或另一个应用,在一个web应用里,验证通常实现两次:在客户端和在服务端,客 ...

  4. ABP(现代ASP.NET样板开发框架)系列之16、ABP应用层——数据传输对象(DTOs)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Project ...

  5. ABP应用层——数据传输对象(DTOs)

    ABP应用层——数据传输对象(DTOs) 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Pro ...

  6. 为什么需要DTO(数据传输对象)

    DTO即数据传输对象.之前不明白有些框架中为什么要专门定义DTO来绑定表现层中的数据,为什么不能直接用实体模型呢,有了DTO同时还要维护DTO与Model之间的映射关系,多麻烦. 然后看了这篇文章中的 ...

  7. 我们为什么需要DTO(数据传输对象)

    原文:http://www.cnblogs.com/Gyoung/archive/2013/03/23/2977233.html DTO即数据传输对象(Data Transfer Object).之前 ...

  8. (扫盲)DTO数据传输对象

    DTO即数据传输对象.但从定义上看就是简单的用来传递数据的.主要用途是在框架中定义DTO来绑定表现层中的数据.学过MVC.EF实体模型的都应该知道,我们可以定义一个Model实体来实现前后台数据的交互 ...

  9. Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

    在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...

随机推荐

  1. SQL Server 2012 开发新功能 序列对象(Sequence)(转)

    转载链接:http://www.cnblogs.com/zhangyoushugz/archive/2012/11/09/2762720.html 众所周知,在之前的sqlserver版本中,一般采用 ...

  2. 2017年第1贴:EXT.JS使用MVC模式时,注意如何协调MODEL, STORE,VIEW,CONTROLLER的关系

    也调了快一天,死活找不到窍门. MODEL, STORE,VIEW的调置测试了很久,试了N种方法,不得其果. 最后,试着在APPLICATION里加入CONTROLLER, 在CONTROLLER里加 ...

  3. 解决Trauncate table没权限

    错误信息Cannot find the object "TableName" because it does not exist or you do not have permis ...

  4. Centos 6.5 X64 环境下编译 hadoop 2.6.0 --已验证

    Centos 6.5 x64 hadoop 2.6.0 jdk 1.7 protobuf-2.5.0 maven-3.0.5 set environment export JAVA_HOME=/hom ...

  5. linux内存和虚拟内存的使用

  6. 如何动态在spring mvc中增加bean

    阅读对象 搭框架人员,或者其他感兴趣的开发人员 背景 一般来说在业务代码中,加上 @Component, @Service,@Repository, @Controller等注解就可以实现将bean注 ...

  7. Swift学习之熟悉控件

    最近是比较清闲一些的,对于一个开发者来说,这也是一个很好的充电机会.以前做项目都是使用Objective-C去开发,但我们都知道,Swift语言从2014年的出现到现在,一步一步变的完善,渐渐变的受欢 ...

  8. Lua 排行榜更新

    排行榜: key:玩家名字,val:玩家的数值 local key1 = {"a1", "a2", "b1", "b2" ...

  9. 历年NOIP水题泛做

    快noip了就乱做一下历年的noip题目咯.. noip2014 飞扬的小鸟 其实这道题并不是很难,但是就有点难搞 听说男神错了一个小时.. 就是$f_{i,j}$表示在第$i$个位置高度为$j$的时 ...

  10. SSDB 主从配置

    环境 Master/Slave     系统 IP SSDB版本 Master     CentOS6.7         10.10.3.211         ssdb-1.8.0     Sla ...