1

I want to know a good way of converting the Model to ViewModel and ViewModel to Model without AutoMapper or something similar, because I want to understand what is behind and learn how to do it myself. Of course, by Model I mean the classes generated by EF.

I made something like this so far, but have some issues when nested classes are involved:

    // to VM
    public static Author ToViewModel(EntityAuthor author)
    {
        if (author == null)
            return null;

        Author result = new Author();
        result.Id = author.ATH_ID;
        result.FirstName = author.ATH_FirstName;
        result.LastName = author.ATH_LastName;
        return result;
    }
    public static BlogPost ToViewModel(EntityBlogPost post)
    {
        if (post == null)
            return null;

        Experiment result = new Experiment();
        result.Id = post.BP_ID;
        result.Title = post.BP_Title;
        result.Url = post.BP_Url;
        result.Description = post.BP_Description;
        result.Author = ToViewModel(post.Author);
        return result;
    }


    // from VM 
    public static EntityAuthor ToModel(Author author)
    {
        if (author == null)
            return null;

        EntityAuthor result = new EntityAuthor();
        result.ATH_ID= author.Id;
        result.ATH_FirstName = author.FirstName;
        result.ATH_LastName = author.LastName;
        return result;
    }
    public static EntityBlogPost ToModel(BlogPost post)
    {
        if (post == null)
            return null;

        EntityBlogPost result = new EntityBlogPost();
        result.BP_ID = post.Id;
        result.BP_Title = post.Title;
        result.BP_Url = post.Url;
        result.BP_Description = post.Description;
        result.Author = ToModel(post.Author);
        return result;
    }

Note: The EntityBlogPost holds the Foreign key to the EntityAuthor. One issue that I face now is when I want to edit a BlogPost, its corresponding entity requires the author's foreign key: "BP_ATH_ID" to be set, but this is '0' since the author of the edited post is 'null', because I don't want to http-post the author. Still, the author needs to be in the view-model because I want to display it (during http-get). Here is my controller to understand better (the view is not of importance):

    // GET: I make use of Author for this
    public ActionResult Edit(int id)
    {
        return View(VMConverter.ToViewModel(new BlogPostService().GetByID(id)));
    }

    //
    // POST: I don't make use of Author for this
    [HttpPost]
    public ActionResult Edit(BlogPost input)
    {
        if (ModelState.IsValid)
        {                
            new BlogPostService().Update(VMConverter.ToModel(input));
            return RedirectToAction("List");
        }
        return View(input);
    }

At the moment I have some Services behind my controller which work only over the Model (as you can see in my code). The intent was to reuse this "service layer" for other applications as well.

    public void Update(EntityBlogPost post)
    {
        // let's keep it simple for now
        this.dbContext.Entry(post).State = EntityState.Modified;
        this.dbContext.SaveChanges();
    }

Ok, so back to my question. What would be a nice way to handle this transition Model->ViewModel and back?

LukLed
  • 31,452
  • 17
  • 82
  • 107
Learner
  • 3,297
  • 4
  • 37
  • 62
  • Possible duplicate of [How do I manually populate ViewModel (Not using AutoMapper!)](http://stackoverflow.com/q/10906208/102937) – Robert Harvey Jun 14 '12 at 22:41
  • @RobertHarvey: Actually this is different. I want to know how to solve the issue with nested classes, foreign key references and stuff... plus some other good ideas that can help me understand how to use Model classes and Viewmodel classes. – Learner Jun 14 '12 at 22:49

2 Answers2

5

In my opinion the approach is problematic in both directions.

  • Model to ViewModel (GET requests)

    If you are using a method like this...

    public static Author ToViewModel(EntityAuthor author)
    

    ...the question is: Where do you get the EntityAuthor author from? Of course you load it from the database using Find or Single or something. This materializes the whole EntityAuthor entity with all properties. Do you need them all in the view? Maybe yes, in this simple example. But imagine a big Order entity with a lot of references to other entities - customer, delivery address, order items, contact person, invoice address, etc., etc. - and you want to display a view with only some properties: due date, customer name, contact person email address.

    To apply the ToViewModel method you have to load the EntityOrder with a whole bunch of properties you don't need for the view and you even have to apply Include for the related entities. This again will load all properties of those entities but you need only a selection of them in the view.

    The usual way to load only the properties you need for the view is a projection, for example:

    var dto = context.Orders.Where(o => o.Id == someOrderId)
        .Select(o => new MyViewDto
        {
            DueDate = o.DueDate,
            CustomerName = o.Customer.Name,
            ContactPersonEmailAddress = o.ContactPerson.EmailAddress
        })
        .Single();
    

    As you can see I have introduced a new helper class MyViewDto. Now you could create specific ToViewModel methods:

    public static OrderViewModel ToMyViewModel(MyViewDto dto)
    

    The mapping between dto and viewModel is a good candidate for AutoMapper. (You cannot use AutoMapper for the projection step above.)

    An alternative is to project directly into the ViewModel, i.e. replace MyViewDto above by OrderViewModel. You have to expose IQueryable<Order> to the view layer though where the ViewModels live in. Some people don't like it, personally I am using this approach.

    The downside is that you need a lot of different methods of type ToMyViewModel, basically for every view another method.

  • ViewModel to Model (POST requests)

    This is the bigger problem as you already have noticed in your example: Many views don't show full entities or show entity data that are supposed to be "view only" and don't get posted back to the server.

    If you use the method (with AutoMapper or not)...

    public static EntityAuthor ToModel(Author author)
    

    ...you obviously don't create a full EntityAuthor object in most cases because the view represented by the view model Author author doesn't show all properties and at least doesn't post them all back. Using an Update method like this:

    this.dbContext.Entry(post).State = EntityState.Modified;
    

    ...would destroy the entity partially in the database (or throw an exception in the best case because some required FKs or properties are not set correctly). The achieve a correct Update you actually have to merge values which are stored in the database and are left unchanged with changed values posted back from the view.

    You could use specific Update methods tailored to the view:

    public void UpdateForMyView1(EntityBlogPost post)
    {
        this.dbContext.EntityBlogPosts.Attach(post);
        this.dbContext.Entry(post).Property(p => p.Title).IsModified = true;
        this.dbContext.Entry(post).Property(p => p.Description).IsModified = true;
        this.dbContext.SaveChanges();
    }
    

    This would be a method for a view which only allows to edit Title and Description of an EntityBlogPost. By marking specific properties as Modified EF will only update those columns in the database.

    The alternative is to introduce DTOs again and mapping methods between view model and those DTOs:

    public static MyUpdateAuthorDto ToMyUpdateAuthorDto(Author author)
    

    This is only property copying or AutoMapper. The Update could be done by:

    public void UpdateForMyView1(MyUpdateAuthorDto dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(dto.AuthorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    This updates only the properties which match in EntityAuthor and in dto and marks them as Modified if they did change. This would solve your problem of the missing foreign key because it is not part of the dto and won't be updated. The original value in the database remains unchanged.

    Note that SetValues takes an object as parameter. So, you could use some kind of resusable Update method:

    public void UpdateScalarAuthorProperties(int authorId, object dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(authorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    This approach only works for updates of scalar and complex properties. If your view is allowed to change related entities or relationships between entities the procedure is not that easy. For this case I don't know another way than writing specific methods for every kind of Update.

Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Your answer is absolutely FANTASTIC!!! Thanks a lot! It helped me realize some mistakes I would have made. The DTO idea is really good, but since I would like to have the service layer reuseable (maybe creating a wcf service out of it), wouldn't it be a hassle with having so many DTOs? I cannot imagine how service versioning would look like with so many DTO's which most likely would change very often. – Learner Jun 15 '12 at 15:28
  • 2
    @Cristi: Yes, the approach leads to many DTOs. But what is the alternative, given that you want to keep a layered architecture, especially that the service layer doesn't know anything about views and view models? Somehow you must tell the service what you have changed in the view in order to create correct Updates. The DTO explosion would disappear if you allow to use the EF context in the view layer, for example in controller actions, as most MS examples present it. I could imagine to introduce an "EntityViewService" between reusable service and views, but that's not less to maintain. – Slauma Jun 15 '12 at 15:50
  • I understand you very well and really appreciate all your help provided. I would have voted more if I could. Thanks a lot! – Learner Jun 15 '12 at 16:35
1

A nice way to handle these transition would be to use AutoMapper. That is what automapper was created for, really.

If you want to learn how it works, please use assemlby decompiler (ILSpy is one of them) and use it on AutoMapper.dll.

The magic word here is Reflection.

Start with:

foreach (PropertyInfo prop in typeof(EntityAuthor).GetProperties())
{
   ...
}

Reflection mechanisms allows you to list all properties of source and destination class, compare their names and when you match these names, you can set property of destination object using SetValue method, based on values from source object.

LukLed
  • 31,452
  • 17
  • 82
  • 107
  • Thanks for the info, is useful. But still, I wouldn't know how to use AutoMapper for my specific example of nested classes. – Learner Jun 15 '12 at 10:32
  • 1
    Please read AutoMappers documentation. AutoMapper is very smart, he will handle it automatically. Please read this question about using it with prefixes: http://stackoverflow.com/questions/9321487/automapper-with-prefix – LukLed Jun 15 '12 at 10:40