Posted by Joe | Posted in MVC | Posted on 17-02-2010
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!





[...] to VoteASP.NET MVC – Using Controller.UpdateModel when using a ViewModel … (2/16/2010)Tuesday, February 16, 2010 from JoeWhen updating a model in MVC it is common to use the [...]
thx.. very useful information and very timely..
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?
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
Hi Senthil
Glad that it helped
Hi Torsten
You should be able to do it in the same way. What problems are you getting?
Thank you for this information!!
Hi! That’s what I’ve been looking for! Finaly, after 30+ google and baidu searching, this article hit the spot. Thanks a lot.
Glad it helped. It had me stumped for a while.
Add one more to the thanks pile!
Thanks…very useful blog
Thanks! Ran into this and have been looking for the obvious solution. Sad the examples don’t show this…
Great! Helped me a lot as it seems to be strangely undocumented anyway! Thanks
Hi! That’s what I’ve been looking for! Finaly, after 30+ google and baidu searching, this article hit the spot. Thanks a lot.
Thanks, Helped a lot