Condividi tramite


How WebAPI does Parameter Binding

Here’s an overview of how WebAPI binds parameters to an action method.  I’ll describe how parameters can be read, the set of rules that determine which technique is used, and then provide some examples.

 

[update] Parameter binding is ultimately about taking a HTTP request and converting it into .NET types so that you can have a better action signature. 

The request message has everything about the request, including the incoming URL with query string, content body, headers, etc.  Eg, without parameter binding, every action would have to take the request message and manually extract the parameters, kind of like this:

 public object MyAction(HttpRequestMessage request)
{
        // make explicit calls to get parameters from the request object
        int id = int.Parse(request.RequestUri.ParseQueryString().Get("id")); // need error logic!
        Customer c = request.Content.ReadAsAsync<Customer>().Result; // should be async!
        // Now use id and customer
}
  

That’s ugly, error prone, repeats boiler plate code, is missing corner cases, and hard to unit test. You want the action signature to be something more relevant like:

 public object MyAction(int id, Customer c) { }

So how does WebAPI convert from a request message into real parameters like id and customer?

Model Binding vs. Formatters

There are 2 techniques for binding parameters: Model Binding and Formatters. In practice, WebAPI uses model binding to read from the query string and Formatters to read from the body. 

(1) Using Model Binding:

ModelBinding is the same concept as in MVC, which has been written about a fair amount (such as here). Basically, there are “ValueProviders” which supply pieces of data such as query string parameters, and then a model binder assembles those pieces into an object.

(2) Using Formatters:

Formatters (see the MediaTypeFormatter class) are just traditional serializers with extra metadata such as the associated content type. WebAPI gets the list of formatters from the HttpConfiguration, and then uses the request’s content-type to select an appropriate formatter. WebAPI has some default formatters. The default JSON formatter is JSON.Net. There is an Xml formatter and a FormUrl formatter that uses JQuery’s syntax.

The key method is MediaTypeFormatter.ReadFromStreayAsync, which looks :

 public virtual Task<object> ReadFromStreamAsync(
    Type type, 
    Stream stream, 
    HttpContentHeaders contentHeaders, 
    IFormatterLogger formatterLogger)

Type is the parameter type being read, which is passed to the serializer. Stream is the request’s content stream. The read function then reads the stream, instantiates an object, and returns it.

HttpContentHeaders are just from the request message. IFormatterLogger is a callback interface that a formatter can use to log errors while reading (eg, malformed data for the given type).

Both model binding and formatters support validation and log rich error information.  However, model binding is significantly more flexible.

When do we use which?

Here are the basic rules to determine whether a parameter is read with model binding or a formatter:

  1. If the parameter has no attribute on it, then the decision is made purely on the parameter’s .NET type. “Simple types” uses model binding. Complex types uses the formatters. A “simple type” includes: primitives, TimeSpan, DateTime, Guid, Decimal, String, or something with a TypeConverter that converts from strings.
  2. You can use a [FromBody] attribute to specify that a parameter should be from the body.
  3. You can use a [ModelBinder] attribute on the parameter or the parameter’s type to specify that a parameter should be model bound. This attribute also lets you configure the model binder.  [FromUri] is a derived instance of [ModelBinder] that specifically configures a model binder to only look in the URI.
  4. The body can only be read once.  So if you have 2 complex types in the signature, at least one of them must have a [ModelBinder] attribute on it.

It was  a key design goal for these rules to be static and predictable.

Only one thing can read the body

A key difference between MVC and WebAPI is that MVC buffers the content (eg, request body). This means that MVC’s parameter binding can repeatedly search through the body to look for pieces of the parameters. Whereas in WebAPI, the request body (an HttpContent) may be a read-only, infinite, non-buffered, non-rewindable stream.

That means that parameter binding needs to be very careful about not reading the stream unless it’s guaranteeing to bind a parameter.  The action body may want to read the stream directly, and so WebAPI can’t assume that it owns the stream for parameter binding.  Consider this example action:

   

         // Action saves the request’s content into an Azure blob 
        public Task PostUploadfile(string destinationBlobName)
        {
            // string should come from URL, we’ll read content body ourselves.
            Stream azureStream = OpenAzureStorage(destinationBlobName); // stream to write to azure
            return this.Request.Content.CopyToStream(azureStream); // upload body contents to azure. 
        }

The parameter is a simple type, and so it’s pulled from the query string. Since there are no complex types in the action signature, webAPI never even touches the request content stream, and so the action body can freely read it.

Some examples

Here are some examples of various requests and how they map to action signatures.

/?id=123&name=bob

void Action(int id, string name) // both parameters are simple types and will come from url

 

/?id=123&name=bob

void Action([FromUri] int id, [FromUri] string name) // paranoid version of above.

void Action([FromBody] string name); // explicitly read the body as a string.

public class Customer {   // a complex object

  public string Name { get; set; }

  public int Age { get; set; }

}

/?id=123

void Action(int id, Customer c) // id from query string, c is a complex object, comes from body via a formatter.

void Action(Customer c1, Customer c2) // error! multiple parameters attempting to read from the body

void Action([FromUri] Customer c1, Customer c2) // ok, c1 is from the URI and c2 is from the body

void Action([ModelBinder(MyCustomBinder)] SomeType c) // Specifies a precise model binder to use to create the parameter.

[ModelBinder(MyCustomBinder)] public class SomeType { } // place attribute on type declaration to apply to all parameter instances

void Action(SomeType c) // attribute on c’s declaration means it uses model binding.

Differences with MVC

Here are some differences between MVC and WebAPI’s parameter binding:

  1. MVC only had model binders and no formatters. That’s because MVC would model bind over the request’s body (which it commonly expected to just be FormUrl encoded), whereas WebAPI uses a serializer over the request’s body.
  2. MVC buffered the request body, and so could easily feed it into model binding. WebAPI does not buffer the request body, and so does not model bind against the request body by default.
  3. WebAPI’s binding can be determined entirely statically based off the action signature types. For example, in WebAPI, you know statically whether a parameter will bind against the body or the query string. Whereas in MVC, the model binding system would search both body and query string.

Comments

  • Anonymous
    April 16, 2012
    I've had problems with the applicable value providers parsing decimals according to the wrong culture (the local culture rather than the invariant culture, as I'd like it to use in this case). There are workarounds online for MVC, but they don't translate to Web API's architecture. Having fought this for the better part of a day, I gave up, accepted a string and did the handling inside the action instead. Is this something the Web API team has seen and corrected? Is there a valve to turn to configure this that I've missed? Also, is there a way to handle binding errors in a more structured way than a catch-all handler? An API seems like a case where you might want to handle these errors differently depending on different calls and certainly different return types.

  • Anonymous
    April 17, 2012
    I did see a WebAPI bug go by about using the wrong culture on query strings. It may be fixed in the RC release. FWIW, here's the best forum to bring up issues regarding bugs or usage problems in WebAPI: forums.asp.net/.../1 Binding errors will show up as model state errors. You could detect them in a filter and handle them there.

  • Anonymous
    April 17, 2012
    Great post Mike! BTW, noticed a typo: "The key method is MediaTypeFormatter.ReadFromStreayAsync, which looks"

  • Anonymous
    April 18, 2012
    Gotcha. Thanks.

  • Anonymous
    May 08, 2012
    One scenario that doesn't work in WebAPI that worked with previous APIs is passing multiple parameters with POST operations. If you wanted to pass multiple objects for example, I don't think there's a way to do that now: [HttpPost] public string Blah(Album album, User user) {     return album.Name + " " + user.Name; } Granted this is not a very common scenario, but in AJAX applications of past this was easy and therefore somewhat common. As it stands now the only way I can see to pass multiple parameters is:

  • Use simple types only

  • Mix query string and POST parameters

  • Create custom types that hold top level instances of parameters ( { album: {}, user: {}, id: {}} ) The latter works but requires that we create custom types for each 'message' object which gets tedious quickly or else rely on untyped JObject() to do dynamic parsing.

  • Anonymous
    May 11, 2012
    @Rick - you're right on all accounts, including workarounds. Think of this as a feature that just didn't make the v1.0 schedule. Currently, this would fail (multiple parameters trying to bind from the body), which means that we can enable this behavior in the (hopefully near) future without it being a breaking change.

  • Anonymous
    June 07, 2012
    I tried to build a custom binder to allow for a colon-less of an ISO date (i.e., 2012-06-07T145558.865Z) to be passed through the URL route directly.  I do something similar with the custom formatter when passing through JSON, but while it hits and parses my custom timestamp with no problem, it throws an error afterward that shows that it's trying to add the same parameter to the ActionArguments again through the DefaultActionValueBinder. Example would be: public Guid? Get(string clientId, string token, [System.Web.Http.ModelBinding.ModelBinder(typeof(CustomModelBinderProvider))] DateTime? timestamp)... The CustomModelBinderProvider populates timestamp, then the engine throws "An item with the same key has already been added." from the DefaultActionValueBinder.   Is there a way to override the model binder for all of type DateTime? and/or prevent the default binder from also firing?   Thanks!

  • Anonymous
    June 13, 2012
    If a complex type defaults to reading from the body, is it possible to "fall back" on the URI should the body be null? For example, a complex Customer with Name,Age parameters is expected from the body. However, if the body is empty, will the WebApi examine the querystring to look for &Name=Test&Age=23 parameters?

  • Anonymous
    June 14, 2012
    @Marcus -  by default, there's no "fallback" because that would involve a searching / pattern matching strategy and that can become unpredictable. In the default case, we wanted you to know for sure whether a parameter was coming from the URL or body, without having to guess per-request. That also enables optimizations in WebAPI But it looks like you found an alternative approach here: blogs.msdn.com/.../10295166.aspx