团队开发框架实战—Dto

1 为何需要DTO

为每个应用服务方法创建一个DTO起初可能被看作是一项乏味而又耗时的事情。但如果正确地使用它,那么DTOs可能会拯救你应用。为啥呢?

1.1 领域层抽象

DTO为展现层抽象领域对象提供了一种有效方式。这样,层与层之间就正确分离了。即使你想完全分离展现层,仍然可以使用已存在的应用层和领域层。相反,只要领域服务的契约(方法签名和DTOs)保持不变,即使重写领域层,完全改变数据库模式,实体和ORM框架,也不需要在展现层做任何改变。

1.2 数据隐藏

试想你有一个User实体,包含Id,Name,EmailAddress和Password字段。如果UserAppService的GetAllUsers()方法返回一个List,即使你没有在屏幕上显示它,那么任何人也都能看到所有user的密码。它不是涉及安全的,而是与数据隐藏相关的。应用服务都应该返回给展现层需要的,不要更多,也不很少,要的是恰到好处。

1.3 序列化和懒加载问题

在一个真实应用中,实体之间是相互引用的。User实体可能有一个Role的引用。因此,如果你想序列化User,那么Role也会序列化。而且,如果Role有一个List且Permission类有一个PermissionGroup类的引用等等。你能想象所有的对象都会被序列化的那种场景吗?你可能会意外地序列化整个数据库。那么解决方案是什么呢?把属性标记为NonSerilized吗?不,你可能不知道它何时应该序列化,何时不应该。它可能在一个应用方法中需要,可能在另一个就不需要了。因此,在这种情景中,设计一个可安全序列化的,特别设计的DTOs是一种好的选择。

几乎所有的ORM框架都支持懒加载。它的特征是当需要时才从数据库中加载实体。假如说User类有一个Role类的引用。当从数据库中获得一个User时,此时Role属性还没有填充,当第一次读该Role属性时,它才从数据库中加载。因此,不要将这样的一个实体直接返回给展现层,它可能会轻易造成从数据库检索额外的实体。如果序列化工具读到了该实体,它会递归地读取所有属性,最终整个数据库可能会被检索(如果实体间有合适的关系)。

在展现层使用实体还会有更多的问题。最好压根不要在将包含领域(业务)层的程序集引用到展现层上。

2 DTO惯例和验证

BatchDeleteInput

using System.Collections.Generic;

namespace Rdf.Application.Services.Dto
{
    public class BatchDeleteInput : IInputDto
    {
        public List<string> IdList { get; set; }
    }
}

JsonMessage

namespace Rdf.Application.Services.Dto
{
    public class JsonMessage : IOutputDto
    {
        public int ErrCode { get; set; }
        public string ErrMsg { get; set; }
        public object Result { get; set; }
    }
}

PageInput

namespace Rdf.Application.Services.Dto
{
    public class PageInput : IInputDto
    {
        public int PageIndex { get; set; }
        public int PageSize { get; set; }
        public string OrderBy { get; set; }
        public string Keyword { get; set; }
    }
}

PageOutput

using System.Collections;

namespace Rdf.Application.Services.Dto
{
    public class PageOutput : IOutputDto
    {
        public int Total { get; set; }
        public IEnumerable Records { get; set; }
    }
}

IDto

namespace Rdf.Application.Services.Dto
{
    /// <summary>
    /// This interface must be implemented by all DTO classes to identify them by convention.
    /// </summary>
    public interface IDto
    {
    }
}

IValidate

namespace Rdf.Application.Services.Dto
{
    /// <summary>
    /// This interface is implemented by classes those are needed to validate before use.
    /// </summary>
    public interface IValidate
    {
    }
}

IInputDto

namespace Rdf.Application.Services.Dto
{
    /// <summary>
    /// This interface is used to define DTOs those are used as input parameters.
    /// </summary>
    public interface IInputDto : IDto, IValidate
    {
    }
}

IOutputDto

namespace Rdf.Application.Services.Dto
{
    /// <summary>
    /// This interface is used to define DTOs those are used as output parameters.
    /// </summary>
    public interface IOutputDto : IDto
    {
    }
}

3 DTO和实体的自动映射

幸好,我们有工具可以让这个变得很简单,AutoMapper就是之一。

Configuration.cs

using AutoMapper;

namespace Rdf.Web.AutoMapper
{
    public class Configuration 
    {
        public static void Configure()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.AddProfile<Profiles.ActProfile>();
            });
        }
    }
}

ActProfile.cs

using AutoMapper;
using Rdf.Application.Act.Dtos;
using Rdf.Domain.Entities.Act;

namespace Rdf.Web.AutoMapper.Profiles
{
    public class ActProfile : Profile
    {
        protected override void Configure()
        {
            CreateMap<CreateRoleInput, Role>();
        }
    }
}

Startup.cs

// InitMapping
AutoMapper.Configuration.Configure();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容