文档目录

本节内容:

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. 可爱的豆子——使用Beans思想让Python代码更易维护

    title: 可爱的豆子--使用Beans思想让Python代码更易维护 toc: false comments: true date: 2016-06-19 21:43:33 tags: [Pyth ...

  2. 关于面试题 Array.indexof() 方法的实现及思考

    这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...

  3. Xamarin+Prism开发详解一:PCL跨平台类库与Profile的关系

    在[Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用]中提到过以下错误,不知道大伙还记得不: 无法安装程序包"Microsoft.Identity.Client 1.0. ...

  4. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  5. animate.css(第三方动画使用方法)

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px Monaco; color: #a5b2b9 } animation 语法: animatio ...

  6. Oracle第一步

    Oracle 启动数据库 Startup [NOMOUNT|MOUNT|OPEN|FORCE] [restrict] [pfile=filename] 启动实例,加载数据库,启动数据库 oRACLE关 ...

  7. 从国内流程管理软件市场份额看中国BPM行业发展

    随着互联网+.中国制造2025.工业4.0等国家战略的支持与引导,企业在数字经济时代的信息化表现惊人,越来越多企业认识到,对于企业的发展来说,信息自动化远远还不够,企业的战略.业务和IT之间需保持高度 ...

  8. Android中的flexboxlayout布局

    提到FlexboxLayout大家估计有点模糊,它是谷歌最近开源的一个android排版库,它的前身Flexbox是2009年W3C提出了一种新的布局,可以简便.完整.响应式的实现页面布局,Flexb ...

  9. React Native 之 Text的使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  10. Atitit.研发团队的管理原则---立长不立贤与按资排辈原则

    Atitit.研发团队的管理原则---立长不立贤与按资排辈原则 1. 组织任命原则概述1 2. 历史的角度看,大部分组织使用的立长不立贤原则1 3. 论资排辈 立长不立贤原则1 3.1. 资格和辈分是 ...