【问题标题】:How to convert DTO to View Model and then back again? [duplicate]如何将 DTO 转换为 View Model 然后再转换回来? [复制]
【发布时间】:2013-03-08 17:27:57
【问题描述】:

我也在使用 MVC 4 与存储库模式和单元测试。我有一个具有简单 CRUD 功能的典型控制器。我已将我的视图模型与我的 DTO 分开,我想知道在两者之间进行转换的最佳方式:

型号:

Admin.Models.Product 是我的视图模型,AdminAssembly.Models.Product 是我的 DTO。

控制器:

    //repo that handles product operations
    AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> db;
    //default constructor
    public ProductController() { db = new AdminAssembly.Repositories.EntityRepo<AdminAssembly.Models.Product>(new AdminAssembly.Models.EntitiesContext()); }
    //unit testing constructor
    public ProductController(AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> context) { db = context; }

    //
    // POST: /Product/Create

    [HttpPost]
    public ActionResult Create(Admin.Models.Product product) {

        if (ModelState.IsValid) {
            //COMPILE-ERROR: how to convert to DTO?
            db.Add(product);
        }
        return View();

    }

    //
    // GET: /Product/Edit/5

    public ActionResult Edit(int id) {
        //COMPILE-ERROR: how to convert to view model?
        Admin.Models.Product product = db.GetAll().Where(p => p.ID == id);

        return View(product);
    }

如何在两者之间进行转换?

我是否在我的视图模型中引用我的 DTO 程序集并执行以下操作:(这不会破坏我的单元测试吗?)

//convert to AdminAssembly.Models.Product
db.Add(product.ToDTO());

//convert back to Admin.Models.Product via constructor
Admin.Models.Product product = Admin.Models.new Product(db.GetAll().Where(p => p.ID == id));

我需要某种对象转换黑盒吗?

Converter.ToViewProduct(product);

某种界面?

还是别的什么?

更新 1:

    public static class Product {
        public static Admin.Models.Product ToView(AdminAssembly.Models.Product dto) {
            Admin.Models.Product viewProduct = new Admin.Models.Product();
            //straight copy
            viewProduct.Property1 = dto.Property1;
            viewProduct.Property2 = dto.Property2;

            return viewProduct;
        }
        public static AdminAssembly.Models.Product ToDTO(Admin.Models.Product viewModel) {
            AdminAssembly.Models.Product dtoProduct = new AdminAssembly.Models.Product();
            //straight copy
            dtoProduct.Property1 = viewModel.Property1;
            dtoProduct.Property2 = viewModel.Property2;

            //perhaps a bit of wizza-majig
            dtoProduct.Property1 = viewModel.Property1 + viewModel.Property2;

            return dtoProduct;
        }
    }

【问题讨论】:

    标签: asp.net-mvc entity-framework asp.net-mvc-4 repository-pattern asp.net-mvc-viewmodel


    【解决方案1】:

    长期响应

    [HttpPost]
    public ActionResult Create(Admin.Models.Product product) 
    {
      if (ModelState.IsValid)
      {
        //COMPILE-ERROR: how to convert to DTO?
        var dtoProduct = new AdminAssembly.Models.Product();
        dtoProduct.Property1 = product.Property1;
        dtoProduct.Property2 = product.Property2;
        //...and so on
        db.Add(dtoProduct);
      }
      return View();
    }
    

    虽然这看起来冗长乏味(而且确实如此),但它最终必须在某个地方发生。

    您可以在另一个类或扩展方法中隐藏此映射,或者您可以使用像 AutoMapper 这样的第三方,正如 Charlino 指出的那样。

    附带说明一下,在两个不同的命名空间中拥有两个具有相同名称的类最终会让人感到困惑(如果不是您,那么对于必须维护您的代码的下一个人来说。)尽可能使用更友好和更具描述性的名称.例如,将所有视图模型放在一个名为 ViewModels 的文件夹中,而不是 Models 中。并使用 ViewModel 或 VM 附加所有视图模型。这也是一个很好的约定,imo,根据它们所针对的视图来命名您的视图模型,而不是它们将映射到的域模型,因为并非所有视图模型都会直接映射到域模型。有时,您可能需要多个域模型的多个部分用于一个视图,这会破坏您的命名约定。

    所以在这种特殊情况下,我建议将Admin.Models 更改为Admin.ViewModels,然后将Product 的视图模型版本重命名为CreateViewModel。您的代码将更具可读性,并且不会在您的方法中到处都是名称空间。

    所有这些都会导致方法看起来更像这样:

    [HttpPost]
    public ActionResult Create(CreateViewModel viewModel) 
    {
      if (ModelState.IsValid)
      {
        var product = new Product();
        product.Property1 = viewModel.Property1;
        product.Property2 = viewModel.Property2;
        //...and so on
        db.Add(product);
      }
      return View();
    }
    

    【讨论】:

    • 谢谢,在某些时候我知道我必须这样做,我只是想确保它是可测试的并且正确。在您看来,您认为 Update 1 符合要求并且还可以进行单元测试?
    • 另外,如果我在所有 DTO 前加上“e”或“dto”前缀,那是否符合您在命名约定中提出的建议? (例如产品 => dtoProduct)非常感谢:)
    • 为什么要重命名 ViewModel 而不是 DTO,因为我必须很少引用 DTO?是因为我仍然需要一个约定来处理每个 DTO 的单独创建、编辑和详细信息视图模型吗?
    • 不必更改 dto 的名称。如果我看到一个名为ProductCustomer 的类,我会假设它是一个(可能)映射到数据源的域对象。如果我看到一个名为 *ViewModel 的类,我会知道它只是传递给视图的数据。
    • 知道了,我详细阅读了您的答案,这非常有意义。非常感谢您的 42 美分;)
    【解决方案2】:

    查看一个名为 AutoMapper 的库。

    来自他们的维基:

    什么是 AutoMapper?
    AutoMapper 是一个简单的小库,旨在解决一个看似复杂的问题——摆脱将一个对象映射到另一个对象的代码。这种类型的代码写起来相当枯燥乏味,为什么不发明一个工具来帮我们做呢?

    【讨论】:

    • 入门指南和 wiki 链接返回 404。如果我采用这种方法,可能会产生什么样的性能开销?这可以处理自定义翻译吗? DTOProduct.SpecID = ViewProduct.Date.ToString() + ViewProduct.ID.ToString()?如果我愿意,它是否还允许我的视图模型仅采用 DTO 属性的子集?
    • 我很确定性能开销可以忽略不计。是的,它应该允许自定义转换,否则您可以始终在 ViewProduct 上使用自己的 get 属性。是的,您可以允许 ViewModel 仅采用 DTO 产品的子集。
    • 嗯,很有趣,刚刚了解了如何访问 wiki 页面。它背后似乎有很多有用的功能。我目前的立场是,我不想学习一些东西,只是为了避免学习其他有意义的东西。谢谢:)
    【解决方案3】:

    如果你不想使用 AutoMapper,你可以使用扩展,正如@Forty-Two 所建议的那样。如果要映射的东西不是很多,我会采用这种方法,因为那样的话,AutoMapper == YAGNI

    public static class Extensions
    {
        public static ViewModel ToViewModel(this Model )
        {
            var vm = new ViewModel()
            {
                //map
            };
            return vm;
        }
        public static Model ToModel(this ViewModel viewModel)
        {
            var model = new Model()
            {
                //map
            };
            return model;
        }
    }
    

    类似于您在 UPDATE 中的代码,但使用了扩展。

    【讨论】:

      猜你喜欢
      • 2011-08-24
      • 2020-04-02
      • 2014-03-18
      • 2012-01-08
      • 2013-11-15
      • 2017-01-02
      • 2011-02-15
      • 2015-01-05
      • 1970-01-01
      相关资源
      最近更新 更多