Implementing MVC3 Custom Model Binders
MVC model binders simply map Http browser requests to model objects. Normally if action invoker is unable to find the proper binder of the provided type, it uses built-in binder class: DefaultModelBinder. Default model binder is using following sources to bind to model object: Request.Form, RouteData.Values, Request.QueryString, Request.Files. The search for values is also done in same order.
We can include and exclude bindings for some object properties in action method or in class attribute.
public ActionResult Create([Bind(Include="FirstName, LastName")] Client client) { //or in our class [Bind(Exclude="IsApproved")] public class Client {
In our example we will bind the model manually. This gives us more controls on how the model objects are instantiated and helps if we are using dependency resolver.
[HttpPost] public ActionResult Edit(int id, FormCollection collection) { try { // TODO: Add update logic here //client client = (client)DependencyResolver.Current.GetService(typeof(client)); var client = new Client(); UpdateModel(client, collection); return RedirectToAction("Index"); } catch { return View(); } }
Below is our custom model binder implementation. Please note that the binder class needs to inherit from IModelBinder interface. In the example, we simple check if model exists or use dependency resolver to provide one. Next we are getting prefixes and values from BindingContext.ValueProvider property, that gives us all consolidates value providers we can read from. Please note that we didn’t include any validation logic.
public class ClientModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { // check if model to update exists and create one if not Client model = (Client)bindingContext.Model ?? (Client)DependencyResolver.Current.GetService(typeof(Client)); // check if the value provider has the required prefix bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName); string searchPrefix = (hasPrefix && !string.IsNullOrEmpty(bindingContext.ModelName)) ? bindingContext.ModelName + "." : ""; // populate the fields of the model object model.ClientId = int.Parse(GetValue(bindingContext, searchPrefix, "ClientId")); model.FirstName = GetValue(bindingContext, searchPrefix, "FirstName"); model.LastName = GetValue(bindingContext, searchPrefix, "LastName"); model.BirthDate = DateTime.Parse(GetValue(bindingContext, searchPrefix, "BirthDate")); model.IsApproved = GetCheckedValue(bindingContext, searchPrefix, "IsApproved"); model.Role = (Role)Enum.Parse(typeof(Role), GetValue(bindingContext, searchPrefix, "Role")); return model; } }
Next step is to register our custom binder. We can do it either in the global file or by giving attribute to our Client class.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelBinders.Binders.Add(typeof(Client), new ClientModelBinder()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } //or [ModelBinder(typeof(ClientModelBinder))] public class Client {
You can also implement your own model binder provider which is very useful when handling multiple custom class binders.
public class CustomModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(Type modelType) { //return or apply the switch with other model type binders return modelType == typeof(Client) ? new ClientModelBinder() : null; } } //you can register provider in global app start method ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
I have included application sample, so you can test how it works by setting the break point in the binding function and see how the model values are being retrieved.
CustomModelBinder