ABP框架 - 数据传输对象
本节内容:
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框架 - 数据传输对象的更多相关文章
- 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 ...
- ABP框架 - 值对象
文档目录 本节内容: 简介 值对象基类 最佳实践 简介 “一个表示领域的一个描述性方面的没有概念上的身份对象,称为值对象.“(Eric Evans). 与一个有身份(Id)实体相反,一个值对象没有身份 ...
- ABP官方文档翻译 4.2 数据传输对象
数据传输对象 DTOs的必要性 领域层的抽象 数据隐藏 序列化和懒加载问题 DTO转换和验证 示例 DTOs和实体间的自动映射 辅助接口和类 数据传输对象用来在应用层和展示层之间传输数据. 展示层调用 ...
- 应用程序框架实战三十四:数据传输对象(DTO)介绍及各类型实体比较
本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行比较. 领域实体为何不能一统江湖? 当你阅读 ...
- ABP官方文档翻译 4.3 校验数据传输对象
校验数据传输对象 校验简介 使用数据标注 自定义校验 禁用校验 标准化 校验简介 应用的输入首先应该被校验.输入可以是用户的也可以是其他应用的.在一个web应用中,校验通常实现两次:客户端和服务端.客 ...
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十二 || 三种跨域方式比较,DTOs(数据传输对象)初探
更新反馈 1.博友@落幕残情童鞋说到了,Nginx反向代理实现跨域,因为我目前还没有使用到,给忽略了,这次记录下,为下次补充.此坑已填 2.提示:跨域的姊妹篇——<三十三║ ⅖ 种方法实现完美跨 ...
- ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)
Data Transfer Objects are used to transfer data between Application Layer and Presentation Layer. 数据 ...
- ABP框架 - 多层结构
文档目录 本节内容: 简介 ABP结构 多层 其它层(通用) 领域(Core)层 应用层 基础层 Web & 表示层 其它 总结 简介 一个应用的代码库的分层是一个广为接受的技术,用来减少复杂 ...
随机推荐
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- 运用php做投票题,例题
要求大概是这样的,有一个题目,题目下面是复选框,要求点完复选框提交后会变成进度条,各选项的进度条百分比,和投票数量 首先还是要在数据库建两张表,如下: 要完成这个题目,需要建两个页面 <!DOC ...
- 学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的
我们在<服务器在管道中的"龙头"地位>中对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了介绍,为了让读者朋友们对管道中的服务器具有更 ...
- MySQL+Amoeba实现数据库主从复制和读写分离
MySQL读写分离是在主从复制的基础上进一步通过在master上执行写操作,在slave上执行读操作来实现的.通过主从复制,master上的数据改动能够同步到slave上,从而保持了数据的一致性.实现 ...
- 两个变量交换的四种方法(Java)
对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) class TestEV //创建一个类 { public static ...
- 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 ...
- Jexus Web Server 完全傻瓜化图文配置教程(基于Ubuntu 12.04.3 64位)[内含Hyper-v 2012虚拟机镜像下载地址]
1. 前言 近日有感许多新朋友想尝试使用Jexus,不过绝大多数都困惑徘徊在Linux如何安装啊,如何编译Mono啊,如何配置Jexus啊...等等基础问题,于是昨日向宇内流云兄提议,不如搞几个配置好 ...
- 让Mono 4在Raspberry Pi上飞
最近公司有项目想要在树莓派上做,代替原来的工控机(我们是把工控主机当作小的主机用,一台小的工控主机最少也要600左右,而树莓派只要200多).于是,公司买了一个Raspberry Pi B+和一个Ra ...
- 【腾讯优测干货分享】如何降低App的待机内存(二)——规范测试流程及常见问题
本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/806TiugiSJvFI7fH6eVA5w 作者:腾讯TMQ专项测 ...
- 《AngularJS深度剖析与最佳实践》简介
由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...