ABP官方文档(二十四)【数据传输对象】

来源:互联网 发布:淘宝营销技巧话语 编辑:程序博客网 时间:2024/06/05 00:59

4.2 ABP应用层 - 数据传输对象

数据传输对象(Data Transfer Objects)用于应用层和展现层的数据传输。

展现层传入数据传输对象(DTO)调用一个应用服务方法,接着应用服务通过领域对象执行一些特定的业务逻辑并且返回DTO给展现层。这样展现层和领域层被完全分离开了。在具有良好分层的应用程序中,展现层不会直接使用领域对象(仓库,实体)。

4.2.1 数据传输对象的作用

为每个应用服务方法创建DTO看起来是一项乏味耗时的工作。但如果你正确使用它们,这将会解救你的项目。为啥呢?

(1)抽象领域层 (Abstraction of domain layer)

在展现层中数据传输对象对领域对象进行了有效的抽象。这样你的层(layers)将被恰当的隔离开来。甚至当你想要完全替换展现层时,你还可以继续使用已经存在的应用层和领域层。反之,你可以重写领域层,修改数据库结构,实体和ORM框架,但并不需要对展现层做任何修改,只要你的应用层没有发生改变。

(2)数据隐藏 (Data hiding)

想象一下,你有一个User实体拥有属性Id, Name, EmailAddress和Password。如果UserAppService的GetAllUsers()方法的返回值类型为List\

4.2.2 DTO 约定 & 验证

ABP对数据传输对象提供了强大的支持。它提供了一些相关的(Conventional)类型 & 接口并对DTO命名和使用约定提供了建议。当你像这里一样使用DTO,ABP将会自动化一些任务使你更加轻松。

一个例子 (Example)

让我们来看一个完整的例子。我们相要编写一个应用服务方法根据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建议命名input/ouput对象类似于MethodNameInput/MethodNameOutput,对于每个应用服务方法都需要将Input和Output进行分开定义。甚至你的方法只接收或者返回一个值,也最好创建相应的DTO类型。这样,你的代码才会更具有扩展性,你可以添加更多的属性而不需要更改方法的签名,这并不会破坏现有的客户端应用。

当然,方法返回值有可能是void,之后你添加一个返回值并不会破坏现有的应用。如果你的方法不需要任何参数,那么你不需要定义一个Input Dto。但是创建一个Input Dto可能是个更好的方案,因为该方法在将来有可能会需要一个参数。当然是否创建这取决于你。
Input和Output DTO类型定义如下:

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

验证:作为约定,Input DTO实现IInputDto 接口,Output DTO实现IOutputDto接口。当你声明IInputDto参数时, 在方法执行前ABP将会自动对其进行有效性验证。这类似于ASP.NET MVC验证机制,但是请注意应用服务并不是一个控制器(Controller)。ABP对其进行拦截并检查输入。查看DTO 验证(DTO Validation)文档获取更多信息。
EntityDto是一个简单具有与实体相同的Id属性的简单类型。如果你的实体Id不为int型你可以使用它泛型版本。EntityDto也实现了IDto接口。你可以看到PersonDto并不包含Password属性,因为展现层并不需要它。

跟进一步之前我们先实现IPersonAppService

public class PersonAppService : IPersonAppService{    private readonly IPersonRepository _personRepository;    public PersonAppService(IPersonRepository personRepository)    {        _personRepository = personRepository;    }    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)    {        //获取实体        var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));        //转换为DTO对象        var peopleDtoList = peopleEntityList            .Select(person => new PersonDto                                {                                    Id = person.Id,                                    Name = person.Name,                                    EmailAddress = person.EmailAddress                                }).ToList();        return new SearchPeopleOutput { People = peopleDtoList };    }}

我们从数据库获取实体,将实体转换成DTO并返回output。注意我们没有手动检测Input的数据有效性。ABP会自动验证它。ABP甚至会检查Input是否为null,如果为null则会抛出异常。这避免了我们在每个方法中都手动检查数据有效性。

但是你很可能不喜欢手动将Person实体转换成PersonDto。这真的是个乏味的工作。Peson实体包含大量属性时更是如此。

4.2.3 DTO和实体间的自动映射

还好这里有些工具可以让映射(转换)变得十分简单。AutoMapper就是其中之一。你可以通过nuget把它添加到你的项目中。让我们使用AutoMapper来重写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,并根据命名约定来给PersonDto的属性赋值。命名约定是可配置的并且很灵活。你也可以自定义映射和使用更多特性,查看AutoMapper的文档获取更多信息。

使用特性(attributes)和扩展方法来映射 (Mapping using attributes and extension methods)

ABP提供了几种attributes和扩展方法来定义映射。使用它你需要通过nuget将Abp.AutoMapper添加到你的项目中。使用AutoMap特性(attribute)可以有两种方式进行映射,一种是使用AutoMapFromAutoMapTo。另一种是使用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的值赋值给新的MyClass2对象的TestProp属性。上面的代码根据MyClass1创建了新的MyClass2对象。你也可以映射已存在的对象,如下所示:var obj1 = new MyClass1 { TestProp = "Test value" };var obj2 = new MyClass2();obj1.MapTo(obj2); //根据obj1设置obj2的属性

4.2.4 辅助接口和类型

ABP还提供了一些辅助接口,定义了常用的标准化属性。

ILimitedResultRequest 定义了 MaxResultCount 属性。所以你可以在你的Input DTO上实现该接口来限制结果集数量。

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

对于分页请求,你可以将实现IHasTotalCountOutput DTO作为返回结果。标准化属性帮助我们创建可复用的代码和规范。可在Abp.Application.Services.Dto命名空间下查看其他的接口和类型。

阅读全文
0 0