Search code examples
asp.net-mvcentity-frameworkasp.net-mvc-4repository-patternasp.net-mvc-viewmodel

How to convert DTO to View Model and then back again?


I'm using MVC 4 with the repository pattern and unit testing also. I have a typical controller that has simple CRUD functionality. I've separated my View Models from my DTOs and I would like to know the best way to convert between the 2:

Models:

I have Admin.Models.Product which is my view model and AdminAssembly.Models.Product which is my DTO.

Controller:

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

How do I convert between the 2?

Do I reference my DTO assembly in my view model and do something like: (won't this break my unit testing?)

//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));

Do I need some sort of object conversion black box?

Converter.ToViewProduct(product);

Some sort of interface?

or something else?

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

Solution

  • The long-hand response

    [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();
    }
    

    While this looks verbose and tedious (and it is) it has to happen eventually, somewhere.

    You can hide this mapping either in another class or extension method, or you can use a third party like AutoMapper, as Charlino points out.

    As a side note, having two classes with the same name in two different namespaces will eventually get confusing (if not for you, then for the next person who has to maintain your code.) Implement friendlier and more descriptive names wherever possible. For example, put all your view models in a folder called ViewModels, not Models. And append all your view models with ViewModel, or VM. It's also a good convention, imo, to name your view models based on the view that they are for, not so much the domain model that they will be mapped to, as not all view models will map directly to a domain model. Sometimes you'll want parts of more than one domain model, for a single view, and that will blow up your naming convention.

    So in this particular case I would suggest changing Admin.Models to Admin.ViewModels and then rename the view model version of Product to CreateViewModel. Your code will be much more readable and will not be littered with namespaces throughout your methods.

    All of that would result in a method that would look more like this:

    [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();
    }