w3resource

Model Binding


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 tutorial, 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.

//Activity/Edit?id=1
public ActionResult Edit()
{
   var id = Result.QueryString["id"];
   //query the database
}

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 ActionResult 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.

}

As you can see in the above figure, we use the Request.QueryString and Request (Request.Form) object to get the value from HttpGet and HttpPOST request. Writing this type of code is tedious, repetitive, and ripe for being abstracted away.

With model binding, MVC framework converts the http request values (from query string or form collection) to action method parameters. These parameters can be of primitive type or complex type.

Model Binding to the Rescue

With model binding in place, the above code becomes:

//Activity/Edit?id=1
public ActionResult Edit(int id)
{
   var id = id
   //query the database
}
 
[HttpPost]
public void Create(Person person)
{            
   // person instance is already present
   // and I'm free to skip 
}
'

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.

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 five locations, in the following order:

  1. Request.Form (values submitted in a form as an HTTP post request)
  2. FormCollection(FormCollection type parameter in the action method)
  3. RouteData.Values (route value,s such as /Person/Edit/6)
  4. Request.QueryString (data passed in from the query string part of a URL, such as /Person/Edit?id=6)
  5. 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.

Summary

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.



Inviting useful, relevant, well-written and unique guest posts