Geeks With Blogs
Doug.Instance Improving the world one post at a time

MVC 2 provides a GREAT feature for dealing with enumerable types.  Let's say you have an object with a parent/child relationship and you want to allow users to modify multiple children at the same time.  You can simply use the following syntax for any indexed enumerables (arrays, generic lists, etc.) and then your values will bind to your enumerable model properties.

						using (Html.BeginForm("TestModelParameter", "Home"))
2: { %>
3: <table>4: <tr><th>ID</th><th>Name</th><th>Description</th></tr>
5: <%for (int i = 0; i < Model.Items.Count; i++)
6: { %>
7: <tr>
9: <%= i %>
10: </td>
11: <td>
12: <%= Html.TextBoxFor(m => m.Items[i].Name) %>
13: </td>
14: <td>
15: <%= Model.Items[i].Description %>
16: </td>
17: </tr>
18: <% } %>
19: </table>20: <input type="submit"/>21: <% } %>

Then just update your model either by passing it into your action method as a parameter or explicitly with UpdateModel/TryUpdateModel.

						public ActionResult TestTryUpdate()
2: {
3: ContainerModel model = new ContainerModel();
4: TryUpdateModel(model);
6: return View("Test", model);
7: }
9: public ActionResult TestModelParameter(ContainerModel model)
10: {
11: return View("Test", model);
12: }

Simple right?  Well, not quite.  The problem is the DefaultModelBinder and how it sets properties.  In this case our model has a property that is a generic list (Items).  The first bad thing the model binder does is create a new instance of the list.  This can be fixed by making the property truly read-only by removing the set accessor.  However this won't help because this behaviour continues.  As the model binder iterates through the items to "set" their values, it creates new instances of them as well.  This means you lose any information not passed via the UI to your controller so in the examplel above the "Description" property would be blank for each item after the form posts.

One solution for this is custom model binding.  I have put together a solution which allows you to retain the structure of your model.  Model binding is a somewhat advanced concept so you may need to do some additional research to really understand what is going on here, but the code is fairly simple.  First we will create a binder for the parent object which will retain the state of the parent as well as some information on which children have already been bound.

						class ContainerModelBinder : DefaultModelBinder
2: {
3: /// <summary>
4: /// Gets an instance of the model to be used to bind child objects.
5: /// </summary>
6: public ContainerModel Model { get; private set; }
8: /// <summary>
9: /// Gets a list which will be used to track which items have been bound.
10: /// </summary>
11: public List<ItemModel> BoundItems { get; private set; }
13: public ContainerModelBinder()
14: {
15: BoundItems = new List<ItemModel>();
16: }
18: protectedoverrideobject CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
19: {
20: // Set the Model property so child binders can find children.
21: Model = base.CreateModel(controllerContext, bindingContext, modelType) as ContainerModel;
23: return Model;
24: }
25: }

Next we will create the child binder and have it point to the parent binder to get instances of the child objects.  Note that this only works if there is only one property of type ItemModel in the parent class since the property to find the item in the parent is hard coded.

						class ItemModelBinder : DefaultModelBinder
2: {
3: /// <summary>
4: /// Gets the parent binder so we can find objects in the parent's collection
5: /// </summary>
6: public ContainerModelBinder ParentBinder { get; private set; }
8: public ItemModelBinder(ContainerModelBinder containerModelBinder)
9: {
10: ParentBinder = containerModelBinder;
11: }
13: protectedoverrideobject CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
14: {
15: // Find the item in the parent collection and add it to the bound items list.
16: ItemModel item = ParentBinder.Model.Items.FirstOrDefault(i => !ParentBinder.BoundItems.Contains(i));
17: ParentBinder.BoundItems.Add(item);
19: return item;
20: }
21: }

Finally, we will register these binders in Global.asax.cs so they will be used to bind the classes.

						void Application_Start()
2: {
3: AreaRegistration.RegisterAllAreas();
5: ContainerModelBinder containerModelBinder = new ContainerModelBinder();
6: ModelBinders.Binders.Add(typeof(ContainerModel), containerModelBinder);
7: ModelBinders.Binders.Add(typeof(ItemModel), new ItemModelBinder(containerModelBinder));
9: RegisterRoutes(RouteTable.Routes);
10: }

I'm sure some of my fellow geeks will comment that this could be done more efficiently by simply rewriting some of the methods of the default model binder to get the same desired behavior.  I like my method shown here because it extends the binder class instead of modifying it so it minimizes the potential for unforseen problems.

In a future post (if I ever get around to it) I will explore creating a generic version of these binders.

Posted on Tuesday, November 29, 2011 5:29 PM MVC | Back to top

Comments on this post: Custom Model Binding of IEnumerable Properties in ASP.Net MVC 2

# re: Custom Model Binding of IEnumerable Properties in ASP.Net MVC 2
Requesting Gravatar...
Hi, Can you please send or upload working example of IEnumerable model.
Left by Sai Laxman on Sep 04, 2013 3:28 PM

# re: Custom Model Binding of IEnumerable Properties in ASP.Net MVC 2
Requesting Gravatar...
One way that I do this is by using a customised model binder and associated attribute: public sealed class BindFromJsonAttribute :. you can do with it before dropping back to writing your own custom model binder.
Left by Worful on May 09, 2016 6:27 AM

Your comment:
 (will show your gravatar)

Copyright © Doug Lampe | Powered by: