数据传输对象

  数据传输对象用来在应用层和展示层之间传输数据。

  展示层调用应用服务方法并传递一个数据传输对象(DTO),然后应用服务使用领域对象执行一些特定的业务逻辑并返回给展示层一个DTO。因此,展示层完全与领域层隔离。在一个理想的分层应用中,展示层不直接使用领域对象(仓储、实体...)。

DTOs的必要性

  最初,为每一个应用服务方法创建DTO看起来乏味且耗时,但是如果你正确使用它,他们会拯救你的应用。为什么这么说呢?

领域层的抽象

  DTOs提供了一种从展示层抽象领域对象的有效方式。因此,你的分层是正确分离的。即使你想完全改变展示层,你可以继续使用已有的应用和领域层。相反,你可以重写领域层,完全改变数据模式、实体和ORM框架而展示层不用做任何改变,只要你应用服务的约定(方法签名和DTOs)保持不变。

数据隐藏

  假如你有一个user实体,它有属性Id,Name,EmailAddress和Password。如果UserAppService的GetAllUsers()方法返回一个List<User>,任何人都可以看到所有用户的密码,即使你不在屏幕上显示。这不是关于安全,而是数据隐藏。应用服务应该给展示层返回它需要的。不是更多而是更少。

序列化和懒加载问题

  当你返回一个数据(对象)到展示层时,它可能会在某处被序列化。例如,在一个返回JSON的MVC方法里,你的对象被序列化为JSON并发送到客户端。在这种情况下,返回实体到展示层是有问题的。如何?

  在一个真实应用里,你的实体将会互相引用。User实体引用Roles。所以,如果你想序列化User,它的Role是也被序列化。甚至Role类或许有一个List<Permission> ,Permission类可能引用PermissionGroup类等等...你能想象所有这些对象都被序列化吗?你可能意外的序列化了整个数据库。如果你的对象存在循环引用,它就不能被序列化。

  解决方案是什么呢?标记属性为不可序列化?不,你不知道什么时候它被序列化什么时候不被序列化。它可能在一个应用服务方法里需要序列化,在其他却不需要。所以,在这种情况下返回一个安全的可序列化的、特定设计的DTOs是一个好的选择。

  几乎所有的ORM框架支持懒加载。当需要的时候从数据库加载实体。比如说User类有一个Role类的引用。当你从数据库里获取一个User时,Role属性没有填充。当你第一次读Role属性时,它才从数据库加载。所以,如果你返回给展示层一个实体,它将导致从数据库中提取额外的实体。如果一个序列化工具读取这个实体,它递归读取所有属性,你的整个数据库再一次被提取了(如果实体之间有恰当的关系的话)。

  我们可以说更多关于在展示层使用实体的问题。最好不要引用包含领域(业务)层到展示层的程序集。

DTO转换和验证

  ABP强支持DTOs。它提供了关于DTOs的一些约定类和接口并建议了一些命名和使用约定。当你编写如这里描述的代码时,ABP简单的自动处理一些任务。

示例

  让我们看一个完整的示例。比如说我们想开发一个应用服务方法,用来查询people,提供一个name参数并返回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的形式命名input/output参数,并为每个应用服务方法定义一个分离的input和outputDTO。即使你的方法只接收或返回衣蛾参数,最好也创建一个DTO类。因此,你的代码将更易扩展。你可以以后添加更多的属性而不用更改你的方法的签名并且不会破坏已有的客户端应用。

  当然,如果没有返回值的话,你的方法可以返回void。如果你以后添加一个返回值,它不会破坏已有的应用。如果你的方法不接受任何参数,你就不需要定义一个input DTO。但是,如果将来有可能添加参数,最好还是写一个input DTO类。由你来决定。

  让我们来看看这个示例定义的input和output 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在方法执行前自动校验input。和ASP.NET MVC的验证类似,但是注意应用服务不是一个控制器,它是一个普通的C#类。ABP自动拦截并检查input。这有更多关于验证的,参见DTO validation文档。

  EntityDto是一个简单的类,它声明了Id属性,因为它对所有的实体都通用。如果你的实体主键不是int,有一个泛型版本。你不用必须使用它,但是建议定义一个Id属性。

  PersonDto如你所见不包含Password属性,因为对展示层来说是不需要的。甚至发送所有people的密码到展示层是危险的。想想一个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并返回output。注意,我们不校验input,ABP校验它。它甚至检查input参数是否为null,如果是便抛出异常。这使得我们不用再每个方法里编写检查语句。

  但是,可能你不喜欢从一个Person实体到一个PersonDto对象的转换代码。它确实是一个乏味的工作。Person实体有更多的属性。

DTOs和实体间的自动映射

  幸运的是有工具可以使得这个非常简单。AutoMapper就是其中之一。参见AutoMapper集成文档了解如何使用它。

辅助接口和类

  ABP提供了一些帮助接口,可以用来实现以标准化通用DTO的属性名称。

  ILimitedResultRequest定义了MaxResultCount属性。所以,你可以在你的input DTOs里实现它来标准化限制结果集。

  IPageResultRequest通过添加SkipCount扩展了ILimitedResultRequest。所以,我们可以在SearchPeopleInput分页里实现这个接口:

public class SearchPeopleInput : IPagedResultRequest
{
[StringLength(, MinimumLength = )]
public string SearchedName { get; set; } public int MaxResultCount { get; set; }
public int SkipCount { get; set; }
}

  作为一个分页请求的结果,你可以返回一个实现IHasTotalCount接口的output DTO。命名标准化帮助我们创建可复用的代码和约定。参见Abp.Application.Services.Dto命名空间下的其他接口和类。

返回主目录

ABP官方文档翻译 4.2 数据传输对象的更多相关文章

  1. ABP官方文档翻译 3.2 值对象

    值对象 介绍 值对象基类 最佳实践 介绍 "展现领域描述性层面且没有概念性身份的对象称之为值对象."(Eric Evans). 和实体相反,实体有身份标示(Id),值对象没有身份标 ...

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

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

  3. ABP官方文档翻译 4.1 应用服务

    应用服务 IApplicationService接口 ApplicationService类 CrudService和AsyncCrudAppService类 简单的CRUD应用服务示例 自定义CRU ...

  4. ABP官方文档翻译 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  5. ABP官方文档翻译 1.2 N层架构

    N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构, ...

  6. ABP官方文档翻译 2.7 对象到对象的映射

    对象到对象的映射 介绍 IObjectMapper接口 AutoMapper集成 安装 创建映射 自动映射属性 自定义映射 MapTo扩展方法 单元测试 预定义映射 LocalizeableStrin ...

  7. ABP官方文档翻译 10.1 ABP Nuget包

    ABP Nuget包 Packages Abp Abp.AspNetCore Abp.Web.Common Abp.Web Abp.Web.Mvc Abp.Web.Api Abp.Web.Api.OD ...

  8. 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  9. ABP官方文档翻译 2.5 设置管理

    设置管理 介绍 关于 ISettingStore 定义设置 设置范围 重写设置定义 获取设置值 服务端 客户端 更改设置 关于缓存 介绍 每个应用都需要存储设置,并且在应用的某些地方需要使用这些设置. ...

随机推荐

  1. Codeforces Round #434 (Div. 2, based on Technocup 2018 Elimination Round 1)&&Codeforces 861A k-rounding【暴力】

    A. k-rounding time limit per test:1 second memory limit per test:256 megabytes input:standard input ...

  2. BZOJ:4827: [Hnoi2017]礼物

    [问题描述] 我的室友最近喜欢上了一个可爱的小女生.马上就要到她的生日了,他决定买一对情侣手 环,一个留给自己,一个送给她.每个手环上各有 n 个装饰物,并且每个装饰物都有一定的亮度. 但是在她生日的 ...

  3. Codeforces Round #345(Div. 2)-651A.水题 651B.。。。 651C.去重操作 真是让人头大

    A. Joysticks time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  4. 淘淘商城学习笔记 之 上传图片到远程服务器,图片的回显出现的bug

    最近在学习淘淘商城中用到的技术,感觉受益良多,遇到一个比较奇怪的bug调了好久,遂心乐之分享于诸君 bug情况是这样的:在商城的后台上传图片之后图片回显不出来,右键查看链接,发现链接被加了localh ...

  5. Thinkphp5+PHPExcel实现批量上传表格数据功能

    http://www.jb51.net/article/129262.htm 1.首先要下载PHPExcel放到vendor文件夹下,我的路径是:项目/vendor/PHPExcel/,把下载的PHP ...

  6. 织梦dede在首页调用留言本

    织梦dedecms在首页调用留言本 . {dede:loop table=dede_guestbook sort=dtime row=10 titlelen=36 typeid=40 if=ische ...

  7. python文件操作总结

    python中对文件.文件夹(文件操作函数)的操作需要涉及到os模块和shutil模块. 得到当前工作目录,即当前Python脚本工作的目录路径: os.getcwd() 返回指定目录下的所有文件和目 ...

  8. C# 处理Word自动生成报告 四、程序处理

    C# 处理Word自动生成报告 一.概述 C# 处理Word自动生成报告 二.数据源例子 C# 处理Word自动生成报告 三.设计模板 C# 处理Word自动生成报告 四.程序处理 现在说一下程序处理 ...

  9. 我的Android手册

    目录解释说明 assets文件说明 app_id:机智云 app id app_secret:机智云 app secret product_key:机智云 product key wifi_type_ ...

  10. 谈Ajax的Get和Post的区别

    Get方式:   用get方式可传送简单数据,但大小一般限制在1KB下,数据追加到url中发送(http的header传送),也就是说,浏览器将各个表单字段元素及其数据按照URL参数的格式附加在请求行 ...