ASP.NET MVC Model Binding

Oh no, not another Model Binding blog!!

I’ve been playing with Model Binding in MVC and I hit a couple of interesting things I wanted to share here. Download the solution here.

Definition: MVC's mechanism for mapping HTTP request data directly into action method parameters and custom .NET objects.

Behind the scenes: Let’s say you want to execute an action like this:

   1:  public ActionResult Index(string name)

Now before the Index action gets called, there are two important components – ValueProviderDictionary, DefaultModelBinder that do some tricks behind the scenes.

The ValueProviderDictionary pulls out the values from the HTTP request and stores them as strings. The fetching itself is done in the following order:

  • Request.Form["name"], if it exists
  • Else, RouteData.Values["name"], if it exists
  • Else, Request.QueryString["name"], if it exists
  • Else returns null

Now that the ValueProviderDictionary has extracted the necessary strings from the incoming request, the DefaultModelBinder class takes on the responsibility of converting these strings into appropriate .NET objects. Of course, with above example in mind, there’s nothing that the DefaultModelBinder class will have to do as they are both strings, but there definitely will be cases where you’ll have something like:

   1:  public ActionResult Index(int id, decimal rate)

In cases like these, the DefaultModelBinder class will perform some ‘HOUDINI’ style magic and give the action method parameters what they need.

So far so good, but what if we have a scenario like below:

   1:  public ActionResult Index(Reservation reservation)

When the DefaultModelBinder is required to supply an instance of some custom .NET type, Reflection comes for the rescue. Using reflection, all the public properties exposed by the custom type receive values based on what the ValueProviderDictionary class provided. Do bear in mind that there’s some kind of recursion happening here and that’s how all properties and it's sub-properties get set with values.

Let’s take what we’ve learnt till now and put it in a project. I’m building a very very primitive Flight Ticket Reservation application and below is what my model looks like:

   1:  public class Reservation
   2:  {
   3:      public int FlightNum { get; set; }
   4:      public DateTime TravelDate { get; set; }
   5:      public int TicketCount { get; set; }
   6:      public int DiscountPercent { get; set; }
   7:  }

Yea, as I said.. very very primitive.. Flight number, date of travel, number of tickets and a discount percent that the customer would receive. For our purpose here, let’s assume our customer will not be able to avail this discount.

I’ve also built a view for this:

   1:  <h2>Book Flight Tickets</h2>
   2:  <% using (Html.BeginForm()) {%>
   3:      <table>
   4:          <tr>
   5:              <td>Flight Number:</td>
   6:              <td><%= Html.TextBox("FlightNum") %></td>
   7:          </tr>
   8:          <tr>
   9:              <td>Date of Travel:</td>
  10:              <td><%= Html.TextBox("TravelDate") %></td>
  11:          </tr>
  12:          <tr>
  13:              <td>Number of Tickets:</td>
  14:              <td><%= Html.TextBox("TicketCount") %></td>
  15:          </tr>
  16:          <tr>
  17:              <td colspan="2"><input type="submit" value="Book Tickets" /></td>
  18:          </tr>
  19:      </table>
  20:      <p><%= ViewData["TotalCost"] %></p>
  21:  <% } %>

The view just exposes the properties of the Reservation class and as per our assumption above, there’s no Discount input field in the view. We’ll see what line 20 does in a few.

My Index action looks as below:

   1:  public ActionResult Index()
   2:  {
   3:      return View();
   4:  }

There’s nothing specific in this action method as all it does is render the empty view. Once run, the initial output looks like below:

screen1

Now, let’s type in some dummy values for the fields and press the Book Tickets button. Here’s what I see:

screen2

Ok, now I see a Total Cost of 200 displayed below. That’s because the ‘Post’ version of my Index action method is coded to calculate it:

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Index(Reservation reservation)
   3:  {
   4:      int totalCost = reservation.TicketCount*100;
   5:   
   6:      if(reservation.DiscountPercent != 0)
   7:      {
   8:          totalCost -= (totalCost*reservation.DiscountPercent/100);
   9:      }
  10:      ViewData["TotalCost"] = string.Format("<b>Total Cost:</b> {0}", totalCost);
  11:      return View(reservation);
  12:  }

Each ticket costs 100 and since the customer was not able to avail any discount, the total cost comes to up 200 and is displayed as per the line 20 of the view.

Now, what if I say with the current settings you can ‘somehow’ get the discount. Huh? Copy the link below to your browser and change the ‘siteName’ to the actual localhost:<port> value:

   1:  http://siteName/default.aspx?DiscountPercent=50

Press enter to load the page. Nothing happened? Not in the front end, but continue entering the same values as before and click the ‘Book Tickets’ button. The Total Cost is now showing 100 instead of 200. Let see where the loop hole is.

This is where the basics of Model Binding come in handy. As mentioned above, the ValueProviderDictionary reads the strings from the incoming request – first from the Request.Form, then from the RouteData.Values and then from the QueryString.

You do see that the querystring ‘DiscountPercent’ matches our model’s property. So while the flight number, the travel date and number of tickets got pulled from the form values, the DiscountPercent was set to the model from the querystring causing the total cost to be only 50% of the actual cost.

Now in order to fix this, you need to either explicitly bind only the first three properties or you need to ‘exclude’ the binding of DiscountPercent property through other means.

There are two ways to cover-up this loop hole and both make use of the Bind attribute.

The first one is applied ‘locally’ to an action method:

   1:  public ActionResult Index([Bind(Include = "FlightNum,TravelDate,TicketCount")]Reservation reservation)
   2:  // or
   3:  // public ActionResult Index([Bind(Exclude = "DiscountPercent")]Reservation reservation)

and the second one is applied ‘globally’ to the model itself:

   1:  [Bind(Include = "FlightNum,TravelDate,TicketCount")]
   2:  // or
   3:  // [Bind(Exclude = "DiscountPercent")]
   4:  public class Reservation
   5:  {
   6:      public int FlightNum { get; set; }
   7:      public DateTime TravelDate { get; set; }
   8:      public int TicketCount { get; set; }
   9:      public int DiscountPercent { get; set; }
  10:  }

The ‘Include’ keyword is like setting up a white-list and the ‘Exclude’ keyword creates a black-list of properties. Also, in the rare event that [Bind] is set on both the action method parameter and on the target type, properties will be bound only if both filters allow it (like the AND binary operation).

Hope this helps in understanding a bit more about how Model Binding works in ASP.NET MVC and download the solution here.

Arun

4 Comments

  • I am writing a UI which passes data to asp.net mvc controllers using json. How can i write a custom ValueProviderDictionary to somehow convert the json-data to an instance of a model?

    Could you please email me at jaysmith024 at] gmail

    thanks

    Jay

  • Jay,

    Thanks for writing, but as you might know by now, there's no direct way of doing this. In my blog, http://weblogs.asp.net/nmarun/archive/2010/07/29/mvc-3-first-look.aspx, I did make a mention about enabling model binding for complex object graphs. Someone from MS has responded to me saying they're looking into building it in one of the (near) future releases.

    But until then, you'll have to write an intelligent parser that can walk through your object graph and build an object model. This will definitely be a cryptic code where you'll have to do a split on the '.' of a type name, decide what type it is and then use reflection to create an instance of that object. You'll also have to check if the member is a part of a collection (checking for '[' or ']' in the string) and the like.

    As I said this code WILL look ugly and I haven't tried to build one myself.

    Sorry I could not be of much help.

    Arun

  • Thank you for sharing this doc..
    It really helps for Property Binding with the help of "ViewData"
    can you Please share the other way of property binding ??
    Is there any other way of ViewData?

  • SEEMS LIKE A COPY AN PASTE WITH NAMES CHANGED

Comments have been disabled for this content.