Creating a Custom Web API Model Binder

When the Web API accepts a request from a client to a service, the Web API attempts to map the data in the request to a .NET object to pass as a parameter to the appropriate method in the service–a process called model binding. I’m consistently amazed at how powerful model binding is. But, as powerful as the process is, it is possible to have a request that defeats model binding. Fortunately, that doesn’t mean that you’re defeated: you can create your own model binder that will map the data in an incoming request to the .NET object that your service method accepts as a parameter.

As an example for creating a model binder, I first need a request that defeats the default model binder–not easy to do because the model binder is so powerful. So, I cheated: I created an ASP.NET WebForm with three TextBoxes (with IDs of CustomerID, CompanyName, and City), a Label (ID of Messages), and a GridView (GridView1). I then posted that whole form to a Web API service using a JavaScript function attached to the OnClientClick event of an ASP.NET Button on the WebForm:

<asp:Button ID="PostButton" runat="server" Text="Update" OnClientClick="return UpdateCustomer();" />

The JavaScript function serializes the form and posts it to a Web API method at “<server/site name>/CustomerManagement” (the function returns false to prevent the ASP.NET Button from posting the form back to the server):

function UpdateCustomer() {
    $.post('CustomerManagement', $('#form1').serialize())
     .success(function (data, status) {
              $("#Messages").text(status);                        
     })
     .error(function (data, msg, detail) {
        alert(data + '\n' + msg + '\n' + detail)
     });
   return false;
}

I defined the the Web API method that will process the request to accept the request’s values in the properties of a class I created and called CustomerOrderDTO:

public HttpResponseMessage Post(CustomerOrderDTO custOrderDTO)
{

And here’s the first version of that CustomerOrderDTO class  with properties whose names match the Ids on the TextBoxes:

public class CustomerOrderDTO
{
  public string CustomerID { get; set; }
  public string CompanyName { get; set; }
  public string City { get; set; }
}

And, amazingly, this works! Model binding instantiates a CustomerOrderDTO object and fills the properties on the object with the matching values from the TextBoxes on the form. But what about the GridView in my WebForm?

Defeating the Default Model Binder

The first thing to recognize is that, normally, the data on the GridView isn’t sent in the request to the Service when I use the serialize method in my JavaScript function: in Display mode, the GridView is just text in an HTML table which the serialize method ignores. However, if the user puts a row in the GridView into Edit mode, the values in the row are displayed in TextBoxes–and the values from those TextBoxes are included in the serialized request sent to the service.

What I would like is to use a DTO class like the following, where I want the OrderId, OrderDate, and RequiredDate properties to hold the values from the row in the GridView that the user has put in Edit mode:

public class CustomerOrderDTO
{
  public string CustomerID { get; set; }
  public string CompanyName { get; set; }
  public string City { get; set; }
  public string OrderId {get; set;}
  public DateTime OrderDate { get; set; }
  public DateTime RequiredDate { get; set; }        
}

The model binder can’t do that and looking into the request shows why. The content of the request sent by my JavaScript code looks something like this (the ellipses mark parts of the request that I’ve omitted to focus on what matters to this example):

__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=m0iKSCROBo3ZT9WDkCx8znpAEM2kV5SIg9Gd
OHK%2Bdy...Khbx%2BA%3D%3D&CustomerID=ALFKI&CompanyName=PH%26V+Information+Servic
es&City=&GridView1%24ctl05%24ctl02=10835&GridView1%24ctl05%24ctl03=ALFKI&GridVie
w1%24ctl05%24ctl04=1%2F15%2F1998+12%3A00%3A00+AM&GridView1%24ct...

If you look carefully you can see the TextBoxes’ names with their values in the request, with each name/value pair separated by an equals sign and ending with an ampersand (e.g. CustomerID=ALFKI&CompanyName=…). You can also find the values from the row of the GridView that’s being sent to the service with its TextBoxes’ names and their values though those names and values aren’t as obvious:

...&GridView1%24ctl05%24ctl02=10835&GridView1%24ctl05%24ctl03=...

The GridView’s TextBoxes have names that are auto-generated by the GridView control and look like “GridView1%24ctl05%24ctl02” (that’s the name of TextBox in the fifth row, second column). These names, in addition to being opaque, will change depending on which row the user has put in Edit mode (for instance, the equivalent TextBox from the fourth row has the name GridView1%24ctl04%24ctl02). The default model binding provided with the Web API can’t match those changing names to properties on a class. However, if I create my own model binder, I can make this work.

Creating a Custom Model Binder

The first step in creating a custom model binder is to add a class to your project and have the class implement the IModelBinder interface (for the following code to work, you’ll need using statements for System.Web.Http.Controllers, System.Web.Http.ModelBinding, System.Web.Http.ValueProviders, and System.Net). The interface will give add a single method to your model binding class, called BindModel:

public class CustomerOrderModelBinder: IModelBinder
{
  public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
  {

The BindModel method will be called by the Web API as it attempts to map the incoming request to my CustomerOrderDTO object (or will called be after I do a little more work–see the end of this post). The two parameters passed to the BindModel method give you access to all the data that you might need to retrieve the values in the request and move them to the object that your service method is accepting–but I’ll use very little of those features in this example. There are also a couple of ways that you can, after extracting values from the request sent to the service, pass those values to the service’s method. For this example, I’ll just instantiate my CustomerOrderDTO object and put it into the Model property of the ModelBindingContext object that’s passed as a parameter to the BindModel method as the first steps in my BindModel code:

CustomerOrderDTO coDto = new CustomerOrderDTO();
bindingContext.Model = coDto;

My next step is extract the values from the request by reading the content of the request from the HttpActionContext object and storing it in a string. Then I find the start of my data by looking for the name of the first TextBox in the form (CustomerID) and splitting the following text wherever an ampersand appears–that will give me an array of name/value pairs (e.g. “CustomerID=ALFKI”):

string ct = actionContext.Request.Content.ReadAsStringAsync().Result;
ct = ct.Substring(ct.IndexOf("CustomerID"));
string[] vals = ct.Split('&');

I can now use LINQ statements to pull the name/value pairs that I want out of this array. Once I do that, I can extract the data that I want by looking for the equals sign that separates the name from the value. However, if the values contain “sensitive” characters (like forward slashes, spaces, or ampersands) those values will be URL encoded: A date like “1/15/1998 1:20:05 AM”, for instance, will turn up in the request’s content as the value “1%2F15%2F1998+1%3A20%3A05+AM.” I can use the WebUtility’s UrlDecode method to convert those encoded values back into their original characters.

So, to set the CustomerID property on my object, I use this code:

coDto.CustomerID = (from val in vals
                    where val.StartsWith("CustomerID")
                    select WebUtility.UrlDecode(val.Substring(val.IndexOf('=') + 1))).First();

Extracting the three GridView values is only slightly more difficult. One of the constants in the names of those TextBoxes is that they all begin with the name of the GridView they’re part of (GridView1 in my example). I use that to create another collection of just the name/value pairs belonging to the GridView:

var res = (from val in vals
           where val.StartsWith("GridView1")
           select val).ToList();

Then it’s just a matter of finding the value following the equals sign for each item in the collection, decoding the value,  doing a type conversion (for the dates), and putting the result in the appropriate property. Here’s the code for setting the OrderDate property the previous code will have put in the third position of the collection:

coDto.OrderDate = DateTime.Parse(WebUtility.UrlDecode(res[2].Substring(res[2].IndexOf('=') + 1)));

After moving all the values you can find into your object’s property, your method should return true to indicate successful processing.

Invoking the Model Binder

But there’s still a little work required to have the Web API use your custom model binder. First you need to create a factory object that will instantiate your model binder and return it. That’s just a class that inherits from ModelBinderProvider. Override its GetBinder method and, in the method, create your binder and return it:

public class CustomerOrderModelBinderProvider : ModelBinderProvider
{  
  public override IModelBinder GetBinder(System.Web.Http.HttpConfiguration configuration, Type modelType)
  {
   return new CustomerOrderModelBinder();
  }
}

The parameters passed to the GetBinder method allow you to create more complex logic here to choose among binders if you need to–but, again, I don’t need them for this example.

The last step is to tell the Web API when to use your factory. The most obvious way is to use the ModelBinder attribute on the parameter passed to my Post method. You just need to pass the attribute the type of your binder factory object (you’ll need a using statement for System.Web.Http.ModelBinding for this code to work). This mechanism assumes that I only want to use this model binder for this particular method:

public HttpResponseMessage Post([ModelBinder(typeof(CustomerOrderModelBinderProvider))]CustomerOrderDTO custOrderDTO)
{

You can also use the ModelBinder attribute on the DTO class itself if you want to use this custom model binder every time you use the class:

[ModelBinder(typeof(CustomerOrderModelBinderProvider))]
public class CustomerOrderDTO
{

This is a very bare bones, brute-force-and-ignorance model binder that doesn’t take advantage of much of the power built into the model binding framework. For instance, if I knew the names of my TextBoxes (something not possible with the GridView’s constantly changing names) I might have been able to use the GetValue method on the ValueProvider property of the ModelBindingContext to extract the values from the request; the HttpActionContext’s GetValidators method would allow me to execute any validation attributes applied to the properties on my object; if my conversions failed (or I couldn’t find any values), I should use the AddModelError method on the ModelState property of the HttpActionContext object to pass error messages to the service’s methods along with the DTO. However, all I wanted to demonstrate was that it’s possible to bind almost anything sent to your service to the resulting object: I figure if you can bind this WebForm, you can bind anything.

Peter Vogel

2 Responses to “Creating a Custom Web API Model Binder”


  1. 1 Naunihal February 7, 2014 at 10:03 pm

    I tried exactly as posted in your article, but for me the model binder is never invoked. I am using WebAPI 2.0. Is there any other setting which i might be missing

    • 2 Peter Vogel, Learning Tree February 8, 2014 at 3:52 am

      If you wanted to send me your code, I could take a look at it but it’s hard to say, otherwise.


Comments are currently closed.



Learning Tree International

.NET & Visual Studio Courses

Learning Tree offers over 210 IT training and Management courses, including a full curriculum of .NET training courses.

Free White Papers

Questions on current IT or Management topics? Access our Complete Online Resource Library of over 65 White Papers, Articles and Podcasts

Enter your email address to subscribe to this blog and receive notifications of new posts by e-mail.

Join 29 other followers

Follow Learning Tree on Twitter

Archives

Do you need a customized .NET training solution delivered at your facility?

Last year Learning Tree held nearly 2,500 on-site training events worldwide. To find out more about hosting one at your location, click here for a free consultation.
Live, online training

%d bloggers like this: