文档目录

本节内容:

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(, MinimumLength = )]
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(, MinimumLength = )]
public string SearchedName { get; set; } public int MaxResultCount { get; set; }
public int SkipCount { get; set; }
}

对于一个分页的请求,可以返回一个实现了IHasTotalCount的输出DTO。命名标准化帮助我们创建可重用的代码和约定。其它接口和类可在Abp.Application.Services.Dto命名空间下查看。

kid1412附:英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Data-Transfer-Objects

ABP框架 - 数据传输对象的更多相关文章

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

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

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

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

  3. ABP框架 - 值对象

    文档目录 本节内容: 简介 值对象基类 最佳实践 简介 “一个表示领域的一个描述性方面的没有概念上的身份对象,称为值对象.“(Eric Evans). 与一个有身份(Id)实体相反,一个值对象没有身份 ...

  4. ABP官方文档翻译 4.2 数据传输对象

    数据传输对象 DTOs的必要性 领域层的抽象 数据隐藏 序列化和懒加载问题 DTO转换和验证 示例 DTOs和实体间的自动映射 辅助接口和类 数据传输对象用来在应用层和展示层之间传输数据. 展示层调用 ...

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

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

  6. ABP官方文档翻译 4.3 校验数据传输对象

    校验数据传输对象 校验简介 使用数据标注 自定义校验 禁用校验 标准化 校验简介 应用的输入首先应该被校验.输入可以是用户的也可以是其他应用的.在一个web应用中,校验通常实现两次:客户端和服务端.客 ...

  7. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十二 || 三种跨域方式比较,DTOs(数据传输对象)初探

    更新反馈 1.博友@落幕残情童鞋说到了,Nginx反向代理实现跨域,因为我目前还没有使用到,给忽略了,这次记录下,为下次补充.此坑已填 2.提示:跨域的姊妹篇——<三十三║ ⅖ 种方法实现完美跨 ...

  8. ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)

    Data Transfer Objects are used to transfer data between Application Layer and Presentation Layer. 数据 ...

  9. ABP框架 - 多层结构

    文档目录 本节内容: 简介 ABP结构 多层 其它层(通用) 领域(Core)层 应用层 基础层 Web & 表示层 其它 总结 简介 一个应用的代码库的分层是一个广为接受的技术,用来减少复杂 ...

随机推荐

  1. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  2. 运用php做投票题,例题

    要求大概是这样的,有一个题目,题目下面是复选框,要求点完复选框提交后会变成进度条,各选项的进度条百分比,和投票数量 首先还是要在数据库建两张表,如下: 要完成这个题目,需要建两个页面 <!DOC ...

  3. 学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的

    我们在<服务器在管道中的"龙头"地位>中对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了介绍,为了让读者朋友们对管道中的服务器具有更 ...

  4. MySQL+Amoeba实现数据库主从复制和读写分离

    MySQL读写分离是在主从复制的基础上进一步通过在master上执行写操作,在slave上执行读操作来实现的.通过主从复制,master上的数据改动能够同步到slave上,从而保持了数据的一致性.实现 ...

  5. 两个变量交换的四种方法(Java)

    对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) class TestEV //创建一个类 { public static ...

  6. Configure a bridged network interface for KVM using RHEL 5.4 or later?

    environment Red Hat Enterprise Linux 5.4 or later Red Hat Enterprise Linux 6.0 or later KVM virtual ...

  7. Jexus Web Server 完全傻瓜化图文配置教程(基于Ubuntu 12.04.3 64位)[内含Hyper-v 2012虚拟机镜像下载地址]

    1. 前言 近日有感许多新朋友想尝试使用Jexus,不过绝大多数都困惑徘徊在Linux如何安装啊,如何编译Mono啊,如何配置Jexus啊...等等基础问题,于是昨日向宇内流云兄提议,不如搞几个配置好 ...

  8. 让Mono 4在Raspberry Pi上飞

    最近公司有项目想要在树莓派上做,代替原来的工控机(我们是把工控主机当作小的主机用,一台小的工控主机最少也要600左右,而树莓派只要200多).于是,公司买了一个Raspberry Pi B+和一个Ra ...

  9. 【腾讯优测干货分享】如何降低App的待机内存(二)——规范测试流程及常见问题

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/806TiugiSJvFI7fH6eVA5w 作者:腾讯TMQ专项测 ...

  10. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...