ASP.NET MVC – Using Controller.UpdateModel when using a ViewModel

When updating a model in MVC it is common to use the Controller.UpdateModel method.  I recently ran into an issue where I was using a custom ViewModel which meant that UpdateModel could not map the updated data back to my model object. The solution to this was a simple one, but not as obvious as it should be due to intellisense not picking up the method’s overloads when the generic type is inferred. 

 

When not using a ViewModel I may have a controller method like this: 

public ActionResult Edit(int id)
{
    Student student = context.Student.FirstOrDefault(s => s.ID.Equals(id));

    return View(student);
}

This is quite simply grabbing a student from my database using the Entity Framework and rendering a strongly typed view.  The view will have fields like so: 


<div>
    <%= Html.LabelFor(model => model.Forename) %>
</div>
<div>
    <%= Html.TextBoxFor(model => model.Forename)%>
    <%= Html.ValidationMessageFor(model => model.Forename)%>
</div>

<div>
    <%= Html.LabelFor(model => model.Surname)%>
</div>
<div>
    <%= Html.TextBoxFor(model => model.Surname)%>
    <%= Html.ValidationMessageFor(model => model.Surname)%>
</div>

I would then have a post method so save the student that may look like this: 


[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
    try
    {
        Student student = context.Student.FirstOrDefault(s => s.ID.Equals(id));
        UpdateModel(student);
        context.SaveChanges();

        return RedirectToAction("Details", new { id = student.ID });
    }
    catch
    {
        throw;
    }
}

UpdateModel will loop through the formValues collection and map the posted data to the student model object.  If I check the Keys in the collection I can see that they match the properties in my model, which is what makes the mapping successful. 

 

Now, what will happen if I use a ViewModel? It is good practice with MVC to create a seperate class that contains all the information required for the view.  Here is an example student view model which also contains a SelectList to populate the Title dropdown: 

 

public class StudentModel
{
    public Student Student { get; set; }
    public SelectList Titles { get; set; } 

    public StudentModel()
    { 

    } 

    public StudentModel(Student student, IEnumerable<Title> titles)
    {
        Student = student;
        Titles = new SelectList(titles, "ID", "Name", student.TitleID);
    }
} 

Here is how the edit method looks using the ViewModel: 

public ActionResult Edit(int id)
{
    Student student = context.Student.FirstOrDefault(s => s.ID.Equals(id));

    return View(new StudentModel(student, context.Title));
}

And here is how the view will now look: 

<div>
    <%= Html.LabelFor(model => model.Student.Title) %>
</div>
<div>
    <%= Html.DropDownListFor(model => model.Student.TitleID, Model.Titles)%>
</div>

<div>
    <%= Html.LabelFor(model => model.Student.Forename) %>
</div>
<div>
    <%= Html.TextBoxFor(model => model.Student.Forename)%>
    <%= Html.ValidationMessageFor(model => model.Student.Forename)%>
</div>

The problem now is that in my POST edit method the formValues collection now has the following keys: 

 

This is because the ViewModel contains the property Student which is the original model object.  It is still possible to use the UpdateModel method by using the overload that accepts a string prefix.  As I mentioned earlier it is easy to miss this as intellisense only picks it up when your specify the generic type: 

UpdateModel<Student>(student, "Student");

Here you can see I am passing the string Student as the prefix which allows the mapping to occur correctly.  You can still use the overload without specifying the type but intellisense won’t pick it up, as shown in the full edit method below.


[HttpPost]
public ActionResult Edit(int id, FormCollection formValues)
{
    try
    {
        Student student = context.Student.FirstOrDefault(s => s.ID.Equals(id));
        UpdateModel(student, "Student");
        context.SaveChanges();

        return RedirectToAction("Details", new { id = student.ID });
    }
    catch
    {
        throw;
    }
}

I hope this helps someone as it caught me out and took me a little while to work out!

Posted on by Joe in MVC

31 Responses to ASP.NET MVC – Using Controller.UpdateModel when using a ViewModel

  1. Pingback: ASP.NET MVC Archived Blog Posts, Page 1

  2. lesa

    thx.. very useful information and very timely..

  3. Torsten

    Hi!

    very nice post… but when i’ve a MultiSelectList in the ViewModel and show this list with ListBoxFor. How can i update the ViewModel with UpdateModel to get the selectet items?

  4. Senthil

    Hi Joe,
    It was really helpful to figure out ViewModel object validation issue in one of my project. Just to add some more info for anyone struggling to update deep object tree in ViewModel object validation as below…

    1. e.g view model

    public class ProductFormViewModel
    {
    private CategoryTypeService _svcCategoryType = new CategoryTypeService();
    private CategoryService _svcCategory = new CategoryService();

    public Product ProductView{ get; set;}
    public SelectList CategoryTypes { get; set; }
    public SelectList Categories { get; set; }

    public ProductFormViewModel()
    {
    CategoryTypes = new SelectList(_svcCategoryType.GetList(), “ID”, “Name”);
    Categories = new SelectList(_svcCategory.GetList(), “ID”, “Name”, null);
    }
    public ProductFormViewModel(Product Product)
    {
    ProductView = Product;
    CategoryTypes = new SelectList(_svcCategoryType.GetList(), “ID”, “Name”, ProductView.Category.CategoryTypeID);
    Categories = new SelectList(_svcCategory.GetList(), “ID”, “Name”, ProductView.CategoryID);
    }
    }

    2.Product Controller “Create” sample

    public ActionResult Create(FormCollection formValues)
    {

    ViewData["ActionFlag"] = ActionType.CREATE;
    Product entityToAdd =new Product();
    string[] excludePpty = {“Category” };
    UpdateModel(entityToAdd ,”ProductView”,null,excludePpty,formValues.ToValueProvider());
    entityToAdd.Category = _svcProduct.GetCategoryByProductCategoryID(entityToAdd.CategoryID);
    if (ModelState.IsValid)
    {
    try
    {
    if (_svcProduct.Create(entityToAdd))
    return RedirectToAction(“Index”);
    else
    return View(“Create”, new ProductFormViewModel(entityToAdd));
    }
    catch (Exception ex)
    {
    ModelState.AddModelError(“General”, ex);
    return View(“Create”, new ProductFormViewModel(entityToAdd));
    }
    }
    else
    {
    return View(“Create”, new ProductFormViewModel(entityToAdd));
    }
    }

    Thanks
    Senthil

  5. Joe

    Hi Senthil

    Glad that it helped

  6. Joe

    Hi Torsten

    You should be able to do it in the same way. What problems are you getting?

  7. Justin Stolle

    Thank you for this information!!

  8. Cheny

    Hi! That’s what I’ve been looking for! Finaly, after 30+ google and baidu searching, this article hit the spot. Thanks a lot.

  9. Joe

    Glad it helped. It had me stumped for a while.

  10. (david, "newb")

    Add one more to the thanks pile!

    Thanks…very useful blog

  11. Steve

    Thanks! Ran into this and have been looking for the obvious solution. Sad the examples don’t show this…

  12. British Developer

    Great! Helped me a lot as it seems to be strangely undocumented anyway! Thanks

  13. Ana

    Hi! That’s what I’ve been looking for! Finaly, after 30+ google and baidu searching, this article hit the spot. Thanks a lot.

  14. Area080

    Thanks, Helped a lot

  15. Conor

    This article was well organized and extremely helpful. Thanks!

  16. Sada

    Thank you very much!!!

  17. Radu

    Thanks man.

  18. Lorenzo

    Hi, good article – looks more or less like the problem I have except i’m also rendering a PartialView and am struggling to get my UpdateModel to work from my controller.

    If you imagine you changed your titles code from:-

    model.Student.TitleID, Model.Titles)%>

    to something like:-

    In the controller where you get the (FormCollection formValues) how would you get the title selected in a way that the UpdateModel helper will work?

    Thanks

  19. Tim

    Man, I owe you hour of mt time!!!

  20. Alex

    Cool, but how can I validate ViewModel with DataAnnotation?

    Thanks
    Bye

  21. Lorenzo

    Awesome. It had me there for a minute, glad to find this.

  22. Buddy Stein

    Thanks, was driving me crazy.

  23. Kahuna

    Does anybody know how could I get rid of the class names, property names, view names hardcoded as strings (e.g. “Student”, “Name”, “Details” in the original post)?

    This is so error-prone it makes me itch :)

  24. Miles

    I was having a similar problem with my ViewModel and objects contained within it.

    Thanks so much!

  25. nche

    thanx alot man. saved my day ;)

  26. Rockshow

    Thank you !!

  27. Pingback: MVC data binding (Model)

  28. Pingback: UpdateModel is not updating "deep" property

  29. TheYves

    Thank you very much, this helped me a lot!

  30. John

    Exactly what I was looking for! Found this article via a Stack Overflow reference.

  31. Laura

    Thank you.
    Really, your post saved me a lot of time!!!
    As for John, I found it via Stack Overflow reference!

Add a Comment