【问题标题】:Where to bind DTO into domain model在哪里将 DTO 绑定到域模型中
【发布时间】:2012-09-25 14:19:30
【问题描述】:

我开始使用 AutoMapper,但出现了一些疑问。 将 dto 映射到域模型的正确方法在哪里? 我正在这样做:

DTO:

public class PersonInsert
{
    [Required]
    public string Name { get; set; }
    public string LastName { get; set; }
}

行动:

[HttpPost]
public ActionResult Insert(PersonInsert personInsert)
{
    if (ModelState.IsValid)
    {
        new PersonService().Insert(personInsert);

        return RedirectToAction("Insert");
    }

    return View("Insert");
}

服务:

public class PersonService
{
    public int Insert(PersonInsert personInsert)
    {
        var person = Mapper.Map<PersonInsert, Person>(personInsert);

        return new PersonRepository().Insert(person);
    }
}

存储库:

 public class PersonRepository
    {
        internal int Insert(Person person)
        {
            _db.Person.Add(person);
            _db.SaveChanges();

            return person.Id;
        }
     }

那么,这是正确的吗?我的服务应该知道域吗?还是我应该只在存储库中进行绑定?在 DTO 中使用 [Required] 是否正确?

【问题讨论】:

    标签: asp.net-mvc architecture automapper dto domain-model


    【解决方案1】:

    我几乎不会从 DTO 创建实体 - 我将在下面解释原因。我会使用请求对象来允许工厂方法构建实体:

    请求:

    public class InsertPersonRequest
    { 
        [Required] 
        public string Name { get; set; } 
        public string LastName { get; set; } 
    } 
    

    行动:

    [HttpPost] 
    public ActionResult Insert(InsertPersonViewModel viewModel) 
    { 
        if (ModelState.IsValid) 
        { 
            InsertPersonRequest request = InsertPersonViewModelMapper.CreateRequestFrom(viewModel);
            new PersonService().Insert(request ); 
            return RedirectToAction("Insert"); 
        } 
    
        return View("Insert"); 
    } 
    

    服务:

    public class PersonService 
    { 
        public int Insert(InsertPersonRequest request) 
        { 
            var person = Person.Create(request.name, request.LastName);          
            return new PersonRepository().Insert(person); 
        } 
    } 
    

    存储库保持不变。

    这样创建 Person 的所有逻辑都位于 person 的 Factory 方法中,因此业务逻辑被封装在领域派生字段、默认字段等中。

    您所做的问题是必须在 UI 中创建 DTO,然后将所有字段映射到实体 - 这是业务逻辑渗入服务层的可靠方式, UI,或任何不应该出现的地方。

    请再读一遍 - 这是我一次又一次看到的一个非常严重的错误。

    不过,我会在服务层中使用 AutoMapper 来返回 DTO:

    服务:

    public class PersonService 
    { 
        public PersonDto GetById(intid) 
        { 
            var person = new PersonRepository().GetById(id);
            var personDto = Mapper.Map<Person, PersonDto>(person); 
            return personDto
        } 
    } 
    

    【讨论】:

    • 非常感谢您的回答,但我有一个问题:PersonDTO 和 Person 应该具有相同的属性不应该吗?
    • @murilokunze 我会说是的,除非有很好的理由。
    【解决方案2】:

    这对吗?

    我个人认为让您的服务进行映射没有任何问题

    在DTO中使用[Required]是否正确

    不,DTO 不应该有任何业务逻辑。它们应该纯粹用于跨应用程序的不同层/层传输数据。

    DataAnnotations 通常在 ViewModels 上用于客户端/服务器端验证,因此,我将在您的模型中添加另一个分隔符并为您的 Insert 操作引入 ViewModel,例如

    public class PersonViewModel    
    {
        [Required]
        public string Name { get; set; }
        public string LastName { get; set; }
    }
    
    public class PersonDto
    {
        public string Name { get; set; }
        public string LastName { get; set; }
    }
    

    行动:

    [HttpPost]
    public ActionResult Insert(PersonViewModel personViewModel)
    {
         if (ModelState.IsValid)
         {
             var personDto = Mapper.Map<PersonViewModel, PersonDto>(personViewModel);
             new PersonService().Insert(personDto);
             ...
          }
          ...
         }
    }
    

    服务:

    public class PersonService
    {
        public int Insert(PersonDto personDto)
        {
            var person = Mapper.Map<PersonDto, Person>(personDto);
    
            return new PersonRepository().Insert(person);
        }
    }
    

    在这种情况下,它可能看起来有点矫枉过正(考虑到唯一的区别是[Required] 属性)。但是,在典型的 MVC 应用程序中,您需要确保 ViewModels 和业务模型之间的清晰分离。

    【讨论】:

    • 那么[必填]我该怎么办? asp.net-mvc 使用 ModelState.IsValid 来验证用户输入。我应该避免这种情况并在 serviceLayer 中创建逻辑验证吗?
    • 您会将 [Required] 注释放在哪里?在 Person 模型上?
    【解决方案3】:

    我会说您的PersonService 可以被视为您架构的域层(或直接位于域之上的应用层)的一部分,而控制器和 DTO 位于其之上的一层。这意味着您不应在 PersonService 签名中引用 DTO,而应在此处使用域 Person 类。所以映射代码应该进入控制器。这可确保您的域逻辑不受 Web 服务合同更改的影响,这实际上可能只是使用您的 PersonService 的一种方式。 我还将为您的存储库引入一个接口,该接口被注入您的PersonService,因为PersonService 再次不需要了解具体的数据访问实现。

    至于[Required] 属性,我认为在DTO 上使用它没有问题,因为它只是说明了您的Web 服务方法的数据协定。任何调用您的 Web 服务的人都应遵守此数据合同。当然,这个要求通常也会反映在你的域代码的某个地方,可能是通过抛出异常等。

    【讨论】:

    • 谢谢,我没有创建注入代码来简化示例。
    【解决方案4】:

    在 ASP.NET MVC 中,DTO 的典型用途是作为视图模型的一部分。 Viewmodel 是一个类,它将一个到多个 DTO 组合成一个类,为视图呈现和将值发送回服务器而量身定制。

    您所做的是正确的,没有问题,但是数据注释应该驻留在视图模型上,而不是 DTO。除非您将 DTO 称为视图模型,否则就可以了。

    请阅读以下关于 ASP.NET MVC 世界中的模型(域模型)与 ViewModel 的帖子:

    希望对你有帮助

    【讨论】:

      【解决方案5】:

      我认为在 DTO 上添加注释很好,例如 [Required]、MaxLength、Range 等。

      您的 DTO 可以来自任何(可能不受信任的)来源(不仅是您的网站,还可以来自另一个端点、WCF 服务等)。所有请求都将集中到您的服务/业务层,因此您需要在执行业务逻辑之前验证输入(简单的警卫检查)。在 DTO 上添加注释只是描述了执行手头任务所需的输入。传递带有注释的对象不会执行验证。

      但是,我认为您应该在服务/业务层验证 DTO 信息是否正确(注释是检查这一点的好方法)。

      只是我对这种情况的看法:)

      【讨论】:

        猜你喜欢
        • 2018-08-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-06
        • 1970-01-01
        相关资源
        最近更新 更多