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

数据传输对象用于在应用层和表示层之间传输数据。

Presentation Layer calls to an Application Service method with a Data Transfer Object (DTO), then application service uses domain objects to perform some specific business logic and returns a DTO back to the presentation layer. Thus, Presentation layer is completely isolated from Domain layer. In an ideally layered application, presentation layer does not directly work with domain objects (RepositoriesEntities...).

表示层调用一个数据传输对象(DTO)应用服务的方法,然后应用服务使用域对象执行一些具体的业务逻辑,并返回一个DTO返回给表现层。因此,表示层与域层完全隔离。在理想的分层应用程序中,表示层不直接与域对象(存储库、实体……)一起工作。

The need for DTOs

To create a DTO for each Application Service method can be seen as a tedious and time-consuming work at first. But they can save your application if you correctly use it. Why?

创建一个DTO各应用服务的方法可以看出,首先作为一个繁琐和费时的工作。但如果你正确使用,他们可以保存你的应用程序。为什么?

Abstraction of domain layer(领域层抽象)

DTOs provide an efficient way of abstracting domain objects from presentation layer. Thus, your layers are correctly seperated. Even if you want to change presentation layer completely, you can continue with existing application and domain layers. As opposite, you can re-write your domain layer, completely change database schema, entities and O/RM framework without any change in presentation layer as long as contracts (method signatures and DTOs) of your application services remain unchanged.

DTO提供从表示层的抽象域对象的一种有效方法。因此,您的层是正确分离的。即使您想完全改变表示层,也可以继续使用现有的应用程序和域层。相反,你可以重写你的领域层,彻底改变数据库模式、实体和O / RM框架不在表示层的任何变化只要合同(方法签名和DTOs)你的应用程序的服务保持不变。

Data hiding(数据隐藏)

Think that you have a User entity with properties Id, Name, EmailAddress and Password fields. If GetAllUsers() method of UserAppService returns a List<User>, anyone can see passwords of all users, even you do not show it in the screen. It's not just about security, it's about data hiding. Application service should return to presentation layer what it needs. Not more, not less.

假设你有一个用户实体,包含属性ID,名字,地址和密码字段。如果userappservice getallusers()方法返回一个列表<用户名>,任何人都可以看到所有用户的密码,即使你不在屏幕上显示出来。它不仅仅是关于安全性的,它是关于数据隐藏的。应用程序服务应该返回到表示层它需要什么。不是更多,不是更少。

Serialization & lazy load problems(序列化和延迟加载问题)

When you return a data (an object) to presentation layer, it's probably serialized in somewhere. For example, in an MVC method that returns JSON, your object can be serialized to JSON and sent to the client. Returning an Entity to the presentation layer can be problematic in that case. How?

In a real application, your entities will have references to each other. User entity can have a reference to its Roles. So, if you want to serialize User, its Roles are also serialized. And even Role class may have a List<Permission> and Permission class can have a reference to a PermissionGroup class and so on... Can you think that all of these objects are serialized? You can easily serialize your whole database accidently. Also, if your objects has circcular references, it can not be serialized.

What's the solution? Marking properties as NonSerialized? No, you can not know when it should be serialized and when it shouldn't be. It may be needed in one application service method, may not be needed in other. So, returning a safely serializable, specially designed DTOs are a good choice in this situation.

Almost all O/RM frameworks support lazy-loading. It's a feature to load entities from database when it's needed. Say User class has a reference to Role class. When you get a User from database, Role property is not filled. When you first read the Role property, it's loaded from database. So, if you return such an Entity to presentation layer, it will cause to retrieve additonal entities from database. If a serialization tool reads the entity, it reads all properties recursively and again your complete database can be retrieved (if there are suitable relations between entities).

We can say some more problems about using Entities in presentation layer. It's best to do not reference the assembly containing domain (business) layer to the presentation layer at all.

当您将数据(对象)返回到表示层时,它可能在某处序列化。例如,在返回JSON的MVC方法中,您的对象可以序列化为JSON并发送给客户机。将实体返回到表示层在这种情况下可能是有问题的。怎么用?

在实际应用程序中,您的实体将相互引用。用户实体可以对其角色进行引用。所以,如果你想要连载的用户,其角色也连载。甚至角色类可能有一个列表<许可>和允许类可以有一个参考一permissiongroup类等…你认为所有这些对象都是序列化的吗?你可以很容易地将你的整个数据库的意外。另外,如果你的对象有自身的引用,它不能被序列化。

解决的办法是什么?标记性非序列化?不,你不知道什么时候应该序列化,什么时候不应该。它可能需要在一个应用程序服务方法,可能不需要在其他。所以,回到一个安全的可串行化的,在这种情况下,专门设计的点是一个很好的选择。

几乎所有的O/RM框架都支持延迟加载。在需要时从数据库加载实体是一个特性。表示用户类对角色类有引用。当从数据库中获取用户时,角色属性不被填充。当您第一次读取角色属性时,它是从数据库加载的。所以,如果你还这样一个实体的表示层,它会从数据库获取额外的实体。如果序列化工具读取该实体,则它将递归地读取所有属性,并且可以检索完整的数据库(如果实体之间有适当的关系)。

在表示层中使用实体可以说更多的问题。最好不要将包含域(业务)层的程序集引用到表示层。

DTO conventions & validation(约定和验证)

ASP.NET Boilerplate strongly supports DTOs. It provides some conventional classes & interfaces and advices some naming and usage conventions about DTOs. When you write your codes as described here, ASP.NET Boilerplate automates some tasks easily.

ASP.NET样板强烈支持DTOs。它提供了一些常规的类和接口,建议一些关于DTOs的命名和使用公约。当你写你的代码,这里所描述的,ASP.NET样板自动执行一些任务很容易。

Example

Let's see a complete example. Say that we want to develop an application service method that is used to search people with a name and returns list of people. In that case, we may have a Person entity as shown below:

public class Person : Entity
{
public virtual string Name { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
}

And we can define an interface for our application service:

public interface IPersonAppService : IApplicationService
{
SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ASP.NET Boilerplate suggest naming input/output parameters as MethodNameInput and MethodNameOutput and defining a seperated input and output DTO for every application service method. Even if your method only take/return one parameter, it's better to create a DTO class. Thus, your code will be more extensible. You can add more properties later without changing signature of your method and without breaking existing client applications.

Surely, your method can return void if there is no return value. It will not break existing applications if you add a return value later. If your method does not get any argument, you do not have to define an input DTO. But it maybe better to write an input DTO class if it's probable to add parameter in the future. This is up to you.

ASP.NET样板建议命名输入/输出methodnameinput和methodnameoutput定义分离的输入和输出的DTO为每个应用服务方法参数。即使你的方法只需要/返回一个参数,它是更好地创建一个DTO类。因此,您的代码将具有更大的可扩展性。您可以在不改变方法签名的情况下添加更多的属性,而不会破坏现有的客户端应用程序。

当然,如果没有返回值,则您的方法可以返回空值。如果稍后添加返回值,它不会破坏现有的应用程序。如果你的方法没有得到任何的说法,你不需要定义一个输入DTO。但也许最好写输入DTO类如果在将来添加参数是可能的。这取决于你。

Let's see input and output DTO classes defined for this example:

public class SearchPeopleInput
{
[StringLength(40, MinimumLength = 1)]
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; }
}

ASP.NET Boilerplate automatically validates input before execution of the method. It's similar to ASP.NET MVC's validation, but notice that application service is not a Controller, it's a plain C# class. ASP.NET Boilerplate makes interception and check input automatically. There are much more about validation. See DTO validation document.

EntityDto is a simple class that declares Id property since they are common for entities. Has a generic version if your entity's primary key is not int. You don't have to use it, but can be better to define an Id property.

PersonDto does not include Password property as you see, since it's not needed for presentation layer. Even it can be dangerous to send all people's password to presentation layer. Think that a Javascript client requested it, anyone can easily grab all passwords.

ASP.NET样板自动验证输入之前执行的方法。这是类似于ASP.NET的MVC的验证,但注意到应用服务不是一个控制器,它是一个普通的C #类。ASP.NET样板进行拦截和检查自动输入。还有更多关于验证的内容。See DTO validation document.

entitydto是一个简单的类声明ID属性因为他们是常见的实体。如果你的实体的主键不是int型的,那么它就有一个通用的版本,你不必使用它,但是可以更好地定义id属性。

persondto不包括密码特性如你所看到的,因为它不是为表示层需要。甚至把所有人的密码发送到表示层也是很危险的。认为一个JavaScript客户端请求它,任何人都可以轻松地获取所有密码。

Let's implement IPersonAppService before go further.

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 };
}
}

We get entities from database, convert them to DTOs and return output. Notice that we did not validated input. ASP.NET Boilerplate validates it. It even checks if input parameter is null and throws exception if so. This saves us to write guard clauses in every method.

But, probably you did not like converting code from a Person entity to a PersonDto object. It's really a tedious work. Person entity could have much more properties.

我们得到的实体数据库,将其转换为DTOs和返回输出。注意,我们没有验证输入。ASP.NET样板验证它。它甚至检查输入参数是否为null,并抛出异常。这就节省了我们在每个方法中写保护子句的能力。

但是,你可能不喜欢将代码从一person到一个persondto实体对象。这真是一件单调乏味的工作。实体可以拥有更多的属性。

Auto mapping between DTOs and entities

Fortunately there are tools that makes this very easy. AutoMapper is one of them. See AutoMapper Integration document to know how to use it.

Helper interfaces and classes

ASP.NET provides some helper interfaces that can be implemented to standardize common DTO property names.

ILimitedResultRequest defines MaxResultCount property. So, you can implement it in your input DTOs to standardize limiting result set.

IPagedResultRequest extends ILimitedResultRequest by adding SkipCount. So, we can implement this interface in SearchPeopleInput for paging:

ASP.NET提供了一些辅助接口,可以实现规范普通DTO属性名称。

ilimitedresultrequest maxresultcount属性定义。所以,你可以实现它在你输入DTOs规范限制结果集。

ipagedresultrequest延伸ilimitedresultrequest加入skipcount。所以,我们可以实现这个接口在SearchPeopleInput分页:

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

As a result to a paged request, you can return an output DTO that implements IHasTotalCount. Naming standardization helps us to create re-usable codes and conventions. See other interfaces and classes underAbp.Application.Services.Dto namespace.

作为一个结果,一个页面的请求,你可以返回一个输出,实现了ihastotalcount DTO。命名标准化有助于我们创建可重用的代码和约定。看到其他的接口和类underabp.application.services.dto命名空间。

ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)的更多相关文章

  1. ABP框架系列之三十八:(NHibernate-Integration-NHibernate-集成)

    ASP.NET Boilerplate can work with any O/RM framework. It has built-in integration with NHibernate. T ...

  2. ABP框架系列之四十八:(Specifications-规范)

    Introduction Specification pattern is a particular software design pattern, whereby business rules c ...

  3. ABP框架系列之五十二:(Validating-Data-Transfer-Objects-验证数据传输对象)

    Introduction to validation Inputs of an application should be validated first. This input can be sen ...

  4. ABP框架系列之三十九:(NLayer-Architecture-多层架构)

    Introduction Layering of an application's codebase is a widely accepted technique to help reduce com ...

  5. ABP框架系列之三十四:(Multi-Tenancy-多租户)

    What Is Multi Tenancy? "Software Multitenancy refers to a software architecture in which a sing ...

  6. ABP框架系列之十:(Application-Services-应用服务)

    Application Services are used to expose domain logic to the presentation layer. An Application Servi ...

  7. ABP框架系列之五十四:(XSRF-CSRF-Protection-跨站请求伪造保护)

    Introduction "Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a maliciou ...

  8. ABP框架系列之四十九:(Startup-Configuration-启动配置)

    ASP.NET Boilerplate provides an infrastructure and a model to configure it and modules on startup. A ...

  9. ABP框架系列之十五:(Caching-缓存)

    Introduction ASP.NET Boilerplate provides an abstraction for caching. It internally uses this cache ...

随机推荐

  1. 学习 MeteoInfo二次开发教程(一)

    来自气象家园:http://bbs.06climate.com/forum.php?mod=viewthread&tid=6631 按照教程,没有太大问题,有些是对c#操作不熟悉导致. 1.添 ...

  2. 边缘触发(Edge Trigger)和条件触发(Level Trigger)

    int select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct timeval *timeout);     sele ...

  3. 关于连接oracle工具plsql的一些使用

    上面图片是打开客户端PL\SQL devepoper的连接内容 进入页面后就可以进行相关的sql语句编写了 将几个结果放入一个表中 select 30+30 as 结果 from dual union ...

  4. android 开发 View _6_Canvas详解

    牛逼大神的博客地址:http://www.gcssloop.com/customview/Canvas_BasicGraphics 安卓自定义View进阶-Canvas之绘制图形 在上一篇自定义Vie ...

  5. 实战ELK(2) ElasticSearch 常用命令

    1.Cluster Health 集群状态 curl 'localhost:9200/_cat/health?v' yellow代表分片副本确实,因为我们现在只有一台机器. curl 'localho ...

  6. 修改java在进程中的映像名

    java小程序用java -jar xxx.jar  启动的进程映像名都是java.exe. 如果启动多个小程序就不好区分,导致监控程序无法定位到具体需要守护的小程序上. 解决办法: 在java安装目 ...

  7. sybase-sql语法-replace用法

    1.去空格 update hyl_temp02 --去空格 set acc_nbr=replace(acc_nbr,' ',''); commit; 2.去回车 update hyl_temp02 - ...

  8. RAD Studio 10.3 来了

    官方原版下载链接:HTTP FTP 官方更新说明:http://docwiki.embarcadero.com/RADStudio/Rio/en/What's_New [官方更新说明简译]1.Delp ...

  9. 并发中的volatile

    目录 1. 概述 2. volatile的特性 3. volatile写-读的内存语义 4. volatile内存语义的实现 5. JSR-133为什么要增强volatile的内存语义 6. 总结 1 ...

  10. Sql Server数据库之identity(自增)

    一.identity的基本用法 1.identity的含义: identity表示该字段的值会自动更新,通常情况下,不允许直接修改identity修饰的字段,否则编译会报错 2.基本语法 列名  数据 ...