Brendan Enrick

Daily Software Development

Custom Model Binders in ASP.NET MVC

In ASP.NET MVC, our system is built such that the interactions with the user are handled through Actions on our Controllers. We select our actions based on the route the user is using, which is a fancy way of saying that we base it on a pattern found in the URL they’re using. If we were on a page editing an object and we clicked the save button we would be sending the data to a URL somewhat like this one.

 

Notice that in our route that we have specified the name of the object that we’re trying to save. There is a default Model Binder for this in MVC that will take the form data that we’re sending and bind it to a CLR objects for us to use in our action. The standard Edit action on a controller looks like this.

[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
// TODO: Add update logic here

return RedirectToAction("Index");
}
catch
{
return View();
}
}

If we were to flesh some of this out the way it’s set up here, we would have code that looked a bit like this.

[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
Profile profile = _profileRepository.GetProfileById(id);

profile.FavoriteColor = collection["favorite_color"];
profile.FavoriteBoardGame = collection["FavoriteBoardGame"];

_profileRepository.Add(profile);

return RedirectToAction("Index");
}
catch
{
return View();
}
}


What is bad about this is that we are accessing the FormCollection object which is messy and brittle. Once we start testing this code it means that we are going to be repeating code similar to this elsewhere. In our tests we will need to create objects using these magic strings. What this means is that we are now making our code brittle. If we change the string that is required for this we will have to go through our code correcting them. We will also have to find them in our tests or our tests will fail. This is bad. What we should do instead is have these only appear on one place, our model binder. Then all the code we test is using CLR objects that get compile-time checking. To create our Custom Model Binder this is all we need to do is write some code like this.
public class ProfileModelBinder : IModelBinder
{
ProfileRepository _profileRepository = new ProfileRepository();

public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
int id = (int)controllerContext.RouteData.Values["Id"];
Profile profile = _profileRepository.GetProfileById(id);

profile.FavoriteColor = bindingContext
.ValueProvider
.GetValue("favorite_color")
.ToString();


profile.FavoriteBoardGame = bindingContext
.ValueProvider
.GetValue("FavoriteBoardGame")
.ToString();

return profile;
}
}

 

Notice that we are using the form collection here, but it is limited to this one location. When we test we will just have to pass in the Profile object to our action, which means that we don’t have to worry about these magic strings as much, and we’re also not getting into the situation where our code becomes so brittle that our tests inhibit change. The last thing we need to do is tell MVC that when it is supposed to create a Profile object that it is supposed to use this model binder. To do this, we just need to Add our binder to the collection of binders in the Application_Start method of our GLobal.ascx.cs file. It’s done like this. We say that this binder is for objects of type Profile and give it a binder to use.

ModelBinders.Binders.Add(typeof (Profile), new ProfileModelBinder());

Now we have a model binder that should let us keep the messy code out of our controllers. Now our controller action looks like this.

[HttpPost]
public ActionResult Edit(Profile profile)
{
try
{
_profileRepository.Add(profile);

return RedirectToAction("Index");
}
catch
{
return View();
}
}

That looks a lot cleaner to me, and if there were other things I needed to do during that action, I could do them without all of the ugly binding logic.

July HudsonSC

The Hudson Software Craftsmanship Group will be meeting July 20, 2011 at 6:00 p.m. with people arriving as early as 5:30. As usual, we will be ordering pizza, and some of us will be gathering at Kepner’s Tavern after the event to continue our discussions.

If you haven’t attended HudsonSC yet, I highly recommend the group. We have a fantastic group of software craftsmen who are always trying to improve their skills as developers. If you’re interested, please sign up to let us know you’re attending. We will also not turn people away at the door, so feel free to just show up for the event.

The event is located in downtown Hudson at 102 First Street. When you enter the building, just go to the second floor and turn right. Keep walking straight, and you will arrive in our meeting room.

This month I am going to suggest to the group that we try an AppKata instead of our usual programming exercises. These focus more on real-world scenarios and include changing requirements.

We are very loose with our agenda, so if you want to bring in something to talk about or an activity to do, go for it. In fact, you can show up and suggest topics and ideas for that day’s meeting.

See you there!