在哪里将 DTO 转换为 ViewModel?

Posted

技术标签:

【中文标题】在哪里将 DTO 转换为 ViewModel?【英文标题】:Where to convert a DTO to ViewModel? 【发布时间】:2012-08-06 03:05:03 【问题描述】:

我继承了一个 MVC2 项目,使用了一个非常标准且保存完好的 DDD 模式。我也对整个 DTO/ViewModel 辩论进行了大量阅读。

目前我们的 DTO 经常像 ViewModels 一样使用。老实说,这对我们正在做的事情没有影响,但我想在升级网站时使用适当的 ViewModel。

这是我的问题:

我们的“域”项目模型当前持有实体并将 DTO 返回给我的控制器。现在我需要将该 DTO 映射到 ViewModel。我应该在哪里做这个?

在控制器中? 在域项目中? 其他地方?

我将我的 ViewModel 与“Web”项目中的视图放在一起,因此在域项目中转换 DTO -> ViewModel 感觉不对。在控制器里做也感觉不对。

其他人做了什么?

编辑:

This question/answer 建议在控制器中处理它。这肯定很容易过度思考。

【问题讨论】:

这里的 DTO 是什么意思?这是你的领域模型吗? @CuongLe 对我来说:DTO == “数据传输对象”,大多数人在 .NET/Java 和 DDD 领域讨论 DTO。我还在学习所有这些,所以我不知道还能说什么! 我还是很困惑,我暗示你提到的 DTO 是 DOMAIN ENTITY?因为 DTO 本身就是在分布层工作的“数据传输对象”。 DTO 和 View Model 是同一个级别的,如果你在 service 中工作,top model 称为 DTO,如果你在 application 中工作,top model 称为 View Model 【参考方案1】:

DTO 通常是特定于技术的。例如,在 .NET 世界中,您的 DTO 可能会使用 DataContractDataMember 序列化属性进行修饰。此外,DTO 与返回它们的服务一起形成了域的适配器hexagonal architecture。它们调整您的域以适应特定的传输技术,例如 HTTP,因此它们位于您的域之外。换句话说,域不应该了解 DTO - DTO 应该在单独的项目中定义。包含该服务的项目应该具有将域对象映射到 DTO 的映射代码。

ASP.NET MVC 项目在本质上是相似的 - 它适应您的服务/DTO(或直接的域对象)到表示技术,特别是 html。因此,DTO 不应该知道 ViewModel。相反,MVC 控制器应该调用 DTO 和 ViewModel 之间的映射。这可以通过多种方式完成,但我发现效果最好的是 ViewModel 中的构造函数接受 DTO。此外,在控制器操作保证创建 DTO 以发送回服务的情况下,ViewModel 可以包含基于 ViewModel 创建 DTO 的方法。这包含 ViewModel 中最接近实际数据的所有映射代码 - information expert pattern 的一个实例。实现这一点的另一种方法是使用类似AutoMapper 的东西,它使用基于约定的映射来避免样板代码。除非需要,否则我会认为超出此范围。

在许多情况下,您的 ViewModel 最终看起来就像 DTO,但具有用于绑定和验证的 ASP.NET MVC 特定属性。尽管这似乎违反了DRY,但这些确实是单独的职责。

【讨论】:

恕我直言,如果上面有 [DataMember],那么它不是 DTO。 为什么不呢?似乎这些属性支持序列化,从而支持数据传输,因此支持 DTO。 Fowler 定义的数据传输对象只是将数据从一层移动到另一层。 “移动数据”并不意味着支持移动数据的所有可能机制,因此,我将排除“DTO”定义允许的序列化。也许我可以称其为“支持序列化的 DTO”。 这取决于属性的原因。如果它们在那里是因为它们是自动生成的,那么我的回答是“不要”。 Visual Studio 为我们做了一些事情,因为微软想要更多的“易用性”,而不是因为它们总体上是个好主意。 我一遍又一遍地读到 DTO 的部分要点是它们很容易序列化,所以我认为序列化属性没问题。这里有很多很好的答案,但这似乎是我一直在学习的所有内容的一个很好的总结。我会接受的。【参考方案2】:

首先,始终为您的视图使用显式 ViewModel,不要将 DTO 一直传递到视图。这需要更多的前期工作,但它可以让您更好地控制确切视图中需要哪些数据(它还可以防止像 EF 这样的框架旁加载您可能使用或可能不使用的大量额外数据)

其次,本文概述了 Orchestrator 模式 http://www.simple-talk.com/dotnet/asp.net/never-mind-the-controller,-here-is-the-orchestrator/,它可能只是其他模式的另一个名称,但我喜欢这种格式。

本质上,您为每个控制器创建一个 Orchestrator。 Orchestrator 接收数据(通常是 ViewModel,以及所需的任何其他基本数据类型,尤其是来自 HttpContext 的数据),并返回 ViewModel(如果 View 需要,否则返回一些其他类型)。

这种格式使您能够轻松地对实际逻辑进行单元测试,而无需尝试模拟控制器所需的 HttpContext 内容。

【讨论】:

安德鲁太酷了。这是我第一次听说 Orchestrator 模式,我真的很想弄清楚 大多数 人是如何构建他们的应用程序的。不过我会看看这篇文章,也许它会打开一些新的想法。 在我的应用程序中,我使用域和应用程序服务。 Orchestrator 和应用服务有什么区别???【参考方案3】:

听起来像是您想在专门构建的映射类/模块中做的事情。

我会亲自让我的控制器依赖于映射服务,然后在返回带有新映射的视图模型的视图之前将实际转换委托给该服务。

public class DemoController : Controller

    private readonly IMappingService _mappingService;

    public DemoController(IMappingService mappingService)
    
        _mappingService = mappingService;
    

    public ActionResult Stuff()
    
        var vm = _mappingService.Map(yourDto);

        return View(vm);
    

【讨论】:

【参考方案4】:

一个不错的方法是使用第二个构造函数重载 ViewModel 上的构造函数,该构造函数将 dto 作为参数。这意味着您可以在视图模型本身中处理映射。这可以让您的控制器保持整洁,而无需设置映射服务。

【讨论】:

有趣。好像是我的风格。 问题: 您最初在 ViewModel 构造函数中有什么需要重载它?目前,我的 ViewModel 看起来几乎像带有验证属性的 DTO。只是好奇。 这在简单的场景中可以正常工作。但是,当您的 ViewModel 需要的不仅仅是一个简单的模型实体(例如同名)时,您会怎么做?您是否只是在构造函数中放入越来越多的逻辑并传入更多对象?发生这种情况时,我更喜欢创建一个新层(适配器),它从所需的一组依赖项中创建 ViewModel。拥有这个额外的层还可以降低耦合(ViewModel 现在根本不耦合到模型)。

以上是关于在哪里将 DTO 转换为 ViewModel?的主要内容,如果未能解决你的问题,请参考以下文章

将 DTO 转换为实体,反之亦然

我应该把我的 DTO 放在哪里?

我应该在哪里进行转换:域对象<-> DTO?

WCF/服务层/存储库层:从服务层返回 DTO?并从返回的 DTO 在 Controller 中创建 ViewModel

应用程序的哪一层应该包含 DTO 实现

“nestjs/swagger”中的哪个函数将 DTO 转换为 Swagger 模型定义?