The Beauty That Is the Model Binder
When I introduce people to ASP.NET MVC, if there is anything that they "ooh" and "ah" at, it is the model binding process. The model binding process happens when form or query string values need to be mapped to parameters of action methods. Though everyone new to ASP.NET MVC uses the DefaultModelBinder they often don't really know it is there. It's just a piece of magic that is a part of the framework. However, understanding how it works is important for knowing how to change how that bit functions as well as how validation works.
Let us start with a summary and then unpack the details. The model binder takes a set of value providers, model metadata providers and validator providers and creates the objects in the parameters of your action methods, performs validation, and the resulting objects are passed in as parameters when the action method on the controller is invoked. That is how a form post can become a fully hydrated and validated object before you do anything explicitly in your controller. If you did not know it did all of that, well, pay attention because there is some interesting stuff ahead.
We will start with an example action method on a controller.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AFormPost(string aSimpleType,
ACoolModel aComplexType)
{
//Do whatever...
}
Let us say that this action method would be found on your site at http://foo.com/home/aformpost and you are posting to that url. The class in the framework that is responsible for calling the method above is called the ControllerActionInvoker (which you do not need to be too concerned about remembering as you do not use it directly). When the route is parsed by the runtime, that is the class that is responsible for calling an appropriate method on a controller. The ControllerActionInvoker sees that the action method has two parameters that it has to supply. To do that it uses the DefaultModelBinder to attempt to interpret the values available and force them into one of the two parameters specified. And where does it get its values? What are the available sources? Well, it looks in this order:
- The form values.
- The route data.
- The query string.
- The http file collection.
These are called the "value providers" (and we will discuss that in further detail in a later tutorial). If it finds a value in the first provider, it will pull it from the first. If not, the second, and so on. It will look by name, so if you had a parameter named "foo" and a text input field also named "foo", it would match based on that. And it is not case sensitive.
In the case of the first parameter in the example above, it will see that it is a simple model (like string, int, et al., as opposed to a complex model, which would be one defined by a separate class such as ACoolModel above) and will convert it to that type and assign it to the parameter.
In the case of the second parameter, it will see that it is not a simple model and will instantiate an instance, figure out the properties, and for each one look in the value providers to find a value to assign to the property.
In either case, if the assignment fails because of type conversion errors or the like, an error is added to the ModelState (the thing responsible for keeping track of the validation state of the model). The DefaultModelBinder will then look for any validators on the property and check those. If validation fails, an error will be added to the ModelState. More on validation and model state in the next tutorial.
So that is the basics of the model binder. By creating your own model binder you can take complete control of the process. However, I have found that the default model binder works almost all of the time, but we will discuss how to create your own model binders at a later time.
In summary, the model binder takes a set of value providers and creates the objects in the parameters of your action methods, performs validation, and the resulting objects are passed in as parameters when the action method on the controller is invoked. That is how a form post can become a fully hydrated and validated object before you do anything explicitly in your controller.
Changing the Default Model Binder
So far we have discussed the role of the DefaultModelBinder but have not discussed how to change it. Many applications will be built that never actually need to. When you do (and you will to do some of the things we will see in this series), changing the model binder is simple. First, create a new class, say MyCustomModelBinder:
using System;
using System.Web.Mvc;
namespace A.Cool.Namespace
{
public class MyCustomModelBinder : DefaultModelBinder
{
}
}
Next set that as the new default model binder in the Application_Start of the global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
//This is the line that changes the default model binder.
ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder();
}
At this point the new model binder will do exactly what the normal model binder will do since no method of the base class has been overridden. Which methods are useful to override depends on the task at hand. Our first example of extending this will be later where we will learn how to extend view models to be wired up to automatically handle complex validation scenarios. But before that point, we will need to see how to get started with basic validation.
What's Next?
So this little discussion on the model binding process brings up four separate areas of functionality and extensibility: model binding, metadata providers, value providers and validation providers. In many applications you can get by just fine without creating a new model binder or changing the value providers, but validation is (hopefully) something everyone does. So we will begin there.
Speak Your Mind!
Have something to say? Find a grammatical mistake? Think I said something incorrect? Don't like my perspective? Hate my color scheme? Whatever it is, you can let me know. I would appreciate it if you did.
