Retrieving ViewModel data collection in HTTP POST method
Strongly typed Views with ViewModel pattern in ASP.NET MVC offers good binding model with input validation. However retrieving data collection from ViewModel class through HTTP POST method can cause the collection to be returned as null without updated items. To address this issue I create example of ViewModel class with some collection in it:
public class ApplicationViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public List<Module> Modules { get; set; }
}
public class Module
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsEnabled { get; set; }
}
Controller methods which takes care of GET and POST requests:
public ActionResult UpdateApplication()
{
ApplicationViewModel viewModel = new ApplicationViewModel();
viewModel.Id = 1;
viewModel.Title = "MyApp1";
viewModel.Modules = new List<Module>();
Module module1 = new Module { Id = 1, Title = "Mod1", IsEnabled = true };
viewModel.Modules.Add(module1);
Module module2 = new Module { Id = 2, Title = "Mod2", IsEnabled = false };
viewModel.Modules.Add(module2);
return View(viewModel);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateApplication(ApplicationViewModel viewModel)
{
if (ModelState.IsValid)
{
// save stuff to DB ...
}
return View(viewModel);
}
Example of View page for ApplicationViewModel update scenario:
<% using(Html.BeginForm()) { %>
<h2>Update Application</h2>
Application Title:<br />
<%= Html.TextBox("Title", Model.Title) %>
<br />
<br />
Modules:<br />
<% for (int i = 0; i < Model.Modules.Count; i++)
{ %>
Title: <%= Html.TextBox(string.Format("Modules[{0}].Title", i), Model.Modules[i].Title) %><br />
Is enabled: <%= Html.CheckBox(string.Format("Modules[{0}].IsEnabled", i), Model.Modules[i].IsEnabled)%><br />
<br />
<% } %>
<br />
<input type="submit" name="Submit" value="Submit" />
<% } %>
Whole magic can be seen inside the for loop where instead of classic Html.TextBox("ModuleTitle", Model.Modules[i].Title)
is used Html.TextBox(string.Format("Modules[{0}].Title", i), Model.Modules[i].Title)
. Difference is in generated HTML code – first TextBox helper will generate this:
<input id="ModuleTitle" name="ModuleTitle" type="text" value="Mod1" />
This is the reason why collection is returned as null since every generated id and name attribute have the very same value. Output from second TextBox example:
<input id="Modules[0]_Title" name="Modules[0].Title" type="text" value="Mod1" />
Now when I hit the submit button and set breakpoint inside UpdateApplication method accepting only HTTP POST I can see that the property values of each item in Modules collection are assigned and updated.