In an ASP.NET MVC project, the model binder is a feature of the framework that performs a lot of the heavy lifting behind the scenes. In this article, we are going to talk about everything you need to know to get the most out of model binding, with an eye toward simplicity.
But before we can do that, we need to go through what things looked like before model binding arrived on the scene.
Before Model Binding
When an HTTP request hits the server, it carries data. That data lives in various places within the request, such as the URL or the body of the request.
Before the advent of model binding, a programmer had to write a considerable amount of code to pick out all the data from the request, coercing it into various data types. Consider the following code.
[HttpPost] public void Create() { Person person = new Person(); person.Name = Request.Form["Name"]; person.Age = Convert.ToInt32(Request.Form["Age"]); //oh we're not done yet. still need to parse out 20 more fields. }
Writing this type of code is tedious, repetitive, and ripe for being abstracted away. Enter model binding.
Model Binding to the Rescue
With model binding in place, the above code becomes:
[HttpPost] public void Create(Person person) { // person instance is already hydrated // and I'm free to skip the boilerplate code }
Ahh, much better. Now the burden of pulling pieces of data from the request has been lifted, and the framework has taken care of it for us.
In an ASP.NET MVC project, this just works out of the box. The DefaultModelBinder is in play automatically when you create your project. If the DefaultModelBinder?does not meet your needs, there are supported ways for you to write your own custom model binder.
I’d say that for 80% of developers using ASP.NET MVC, this is the extent of what they need to know about model binding: it instantiates an instance of your model class and maps fields from the HTTP request into the model.
Simple, right?
Let’s go ahead and take a peek behind the curtain to understand this magic a little more deeply.
An HTTP request can carry data in different ways, and the DefaultModelBinder?has a set of rules for how it will go about looking for that data. It will search in four locations, in the following order:
- Request.Form (values submitted in a form as an HTTP post request)
- RouteData.Values (route value,s such as /Person/Edit/6)
- Request.QueryString (data passed in from the query string part of a URL, such as /Person/Edit?id=6)
- Request.Files (uploaded files)
As soon as the model binder finds a value, it ceases to search further.
Now that we’ve covered the basics, let’s talk about how model binding works for different types of HTTP requests.? We’ll also touch on more advanced usage techniques such as customizing the model binder’s behavior.
1. HTTP GET Requests
For GET requests, the model binder will match parameter names in the method signature to those found in the request. Consider the following controller action method:
[HttpGet] public ActionResult Contact(int id, string description) { ViewBag.Message = id.ToString() + description; return View(); }
If an HTTP GET request is issued in the form ~/Contact?id=6&description=business, then the above action will be invoked and the values 6 and “business” would be bound to the parameter’s ID and description, respectively.
Note that if the parameter is not provided, then the model binder will bind a null value for that parameter, if the parameter is a nullable type.
So a URL of the form ~/Contact?id=5 will pass a null value for the description parameter. However, a URL of the form ~/Contact?description=hello will throw an exception of the following variety:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Contact(Int32, System.String)'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
2. HTTP POST Requests
An HTTP POST request works a little differently. The following ajax call:
$.post('/Home/SubmitData', {id:5, description:'thin'} )
Would invoke either one these controller action methods:
public class DataModel { public int id { get; set; } public string description { get; set; } } public class HomeController : Controller { public void SubmitData(DataModel model) { } public void SubmitData(int id, string description) { } }
But the caveat here is that you can’t have both?methods or you’ll confuse the model binder and receive an error similar to the following:
The current request for action 'SubmitData' on controller type 'HomeController' is ambiguous between the following action methods: Void SubmitData(Controllers.DataModel) Void SubmitData(Int32, System.String)
3. Bind Attribute
You’ve now seen that model binding is fairly simple and there’s really no need to complicate it.
In your coding adventures, you may have to experiment and do some debugging to verify that your controller actions are being called and the model binding is happening as expected.
You may also have occasion to use some more advanced techniques. One of these is the Bind attribute. Using this attribute, you can whitelist or blacklist specific properties. Here’s an example of only binding a specified property.
public void SubmitData([Bind(Include = "description")]DataModel model) { //even if id property was provided, model binder will ignore it }
Likewise, you can specifically exclude properties:
public void SubmitData([Bind(Exclude= "id")]DataModel model) { //even if id property was provided, model binder will ignore it }
You may be thinking to yourself, “Why not just define a new model that only includes the properties that I care about?”
That is a completely valid question.
Every dev shop has its own philosophies on whether or not you want the so-called “view model explosion,” where your solution is littered with model classes representing many permutations and subsets of the same properties to support different CRUD screens. Using the Bind attribute is one way to reuse the same model class while only caring about a subset of its properties.
4. Custom Model Binder
Not happy with the behavior of the DefaultModelBinder? Need some custom capability? The ASP.NET MVC framework has made it easy to define your own custom model binder. Let’s suppose we have a model as follows:
public class PersonModel { public int Id { get; set; } public string FullName { get; set; } }
We are responsible for writing a view that gathers a person’s name. Asking for the user to input his or her full name into a single input box seems…odd. We would rather follow a standard practice of providing three input boxes, and our view code would look like this:
<p> @Html.Label("FirstName") @Html.TextBox("FirstName") </p> <p> @Html.Label("MiddleName") @Html.TextBox("MiddleName") </p> <p> @Html.Label("LastName") @Html.TextBox("LastName") </p>
For reasons beyond our control, we are not able to change the model, nor are we allowed to create a different model. Our controller action method must be of the signature:
[HttpPost] public ActionResult Create(PersonModel person) { ... return RedirectToAction("Index"); }
We’ve got an impedance mismatch, so to speak. The default model binder will not take three fields and stuff them into a single FullName field.
What can we do here?
We could resort to pulling the three fields out of the request, as was common practice prior to the advent of model binding. But that would be fairly crude. Custom model binding is a more elegant solution. This will keep the code in our controller action cleaner and much thinner.
What we’ll need to do is to create a new class named PersonModelBinder and have it implement IModelBinder:
public class PersonModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { HttpRequestBase request = controllerContext.HttpContext.Request; int id = Convert.ToInt32(request.Form.Get("Id")); string first = request.Form.Get("FirstName"); string middle = request.Form.Get("MiddleName"); string last = request.Form.Get("LastName"); return new PersonModel() { Id = id, FullName = string.Concat(first, middle, last) }; } }
Lastly, we’ll need to register our custom model binder. We have two options here. We can register it globally, as such:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ModelBinders.Binders.Add(typeof(PersonModel), new PersonModelBinder()); }
Alternatively, we can use an attribute right in our controller action to use our custom model binder:
[HttpPost] public ActionResult Create([ModelBinder(typeof(PersonModelBinder))]PersonModel person) { ... return RedirectToAction("Index"); }
Now we’ve bridge the gap between our view and our model.
When we submit our form with three separate name fields, our custom model binder will concatenate them and bind the value to our FullName field on the model.
Conclusion
There you have it: everything you need to know about model binding! The model binder built into the ASP.NET MVC framework is very robust. For the most part, it just works. And when the out-of-the-box behavior doesn’t meet your needs, you have the power to create your own model binder.
4 Comments. Leave new
[…] Model Binding: A Dead Simple Guide With Everything You Need to Know – Rodney Smith […]
Very good article; succinctly and clearly written. Thanks!
[…] Model Binding: A Dead Simple Guide With Everything You Need to KnowRodney Smith […]
Clear, precise, enlightening and distinctly lacking in BS. Great article.