ASP.NET Web API parameter binding part 1 – Understanding binding from URI

Β· 1464 words Β· 7 minutes to read

Today, let’s kick off a series intended to look at different aspects of HTTP parameter binding in ASP.NET Web API. Why? Aside from the awesome series by Mike Stall, there isn’t really that much material on the web on this particular subject. And developers coming from MVC background, often get surprised by differences in the model binding mechanism between MVC and Web API.

In this first post, let’s have a brief overview of parameter binding in Web API and then specifically look at binding model from URI.

More after the jump.

Model binding with MVC πŸ”—

In MVC, the model binding process is done against both the body and the URI. In other words, the framework will look for model properties in both places, and try to stitch everything together. This is possible because MVC would buffer the body of the request and that would allow it to pick pieces of key value pairs (effectively, each value in the request is part of the name value collection) and try to compose models out of them.

Consider the simple example.

public class Person  
{  
public string FirstName {get; set;}  
public string LastName {get; set;}  
}  

In MVC you can send the following request:

POST /person/index?FirstName=Filip  
Content-Type: application/json  
{"LastName":"W"}  

This will correctly bind itself to an action with this signature:

[HttpPost]  
public JsonResult Index(Person p) {}  

You can also easily bind the whole model just from the URI, which is particularly useful on GET requests where you might be passing in a complex sets of conditions. Conversely, you can bind the entire model just from the body too.

HttpParameterBinding πŸ”—

In Web API, the core concept behind binding parameters is HttpParameterBinding, which, out of the box, can execute either as a model binder, or through the use of a formatter.

In practice, this means that when Web API pipeline runs, a default implementation of IActionValueBinder will determine whether each specific parameter will be bound using the URI (model binding) or request’s body (formatter). To be even more precise, this process will not happen on every request, because in Web API binding can be statically determined for each parameter. Therefore, it will really happen just once - through a call to ApiControllerActionSelector, which will cache the resulting ActionDescriptor inside a private ActionSelectorCacheItem class. The subsequent requests will then be fed from that cache.

We’ll look at what happens when the formatter type of binding is selected in the next post. For now let’s focus on what happens when Web API decides to use ModelBinder and bind from URI.

Web API will look for any ValueProviderFactories registered against the Services property of HttpConfiguration.

public abstract class ValueProviderFactory  
{  
public abstract IValueProvider GetValueProvider(HttpActionContext actionContext);  
}  

A factory returns IValueProvider, which can be used to compose an object from the HTTP request using the most basic building block - a string key/value present in the HTTP request. The main role of ValueProviders is to abstract away the logic of retrieving the information from the HTTP request from ModelBinders and just feed them with individual bits used to compose more complex objects.

public interface IValueProvider  
{  
bool ContainsPrefix(string prefix);  
ValueProviderResult GetValue(string key);  
}  

By default, Web API ships with two registered factories that take part in the URI parameter bindings:

    • QueryStringValueProviderFactory
    • RouteDataValueProviderFactory

And their names & roles are rather self explanatory.

On a side note, it’s worth mentioning, that if the split into model binders / formatters is not enough for you, you can also introduce your own versions of HttpParameterBinding - and we’ll do that in the later posts in this series.

Understanding Web API parameter binding πŸ”—

So what happens if you just stick some parameters into your action? How does IActionValueBinder determine whether to use a formatter or a model binder?

The generic rules are:

    • simple, string-convertible parameters (value types, strings, Guids, DateTimes and so on) are by default read from URI
    • complex types are by default read from the body
    • collections of simple parameters are by default read from the body too
    • you cannot compose a single model based on input from both URI and request body, it has to be one or the other

While some of these default conventions may seem limiting and unintuitive, there are just enough customization hooks to allow you as a developer to override them.

Binding from URI πŸ”—

In order to bind a model (an action parameter), that would normally default to a formatter, from the URI you need to decorate it with either [ModelBinder] or [FromUri] attribute.

FromUriAttribute simply inherits from ModelBinderAttribute, providing you a shortcut directive to instruct Web API to grab specific parameters from the URI using the ValueProviders defined in the IUriValueProviderFactory. The attribute itself is sealed and cannot be extended any further, but you add as many custom IUriValueProviderFactories as you wish.

Mike Stall has a great post on creating a custom value provider for Web API.

Let’s use a ProductFilter as an example:

public class ProductFilter  
{  
public int? PageIndex {get; set;}  
public int? PageSize {get; set;}  
public string[] Sizes {get; set;}  
public decimal? MinPrice {get; set;}  
public decimal? MaxPrice {get; set;}  
}  

Just through the use of [FromUri] you are able to bind the following types entirely from the URI:

  1. complex types, such as our ProductFilter
public HttpResponseMessage Get([FromUri]Productfilter filter) {}

GET /products?pageindex=2&minprice=10&sizes=xl&sizes=xxl  

This will bind correctly to the type we declared, leaving other properties as null (since we explicitly declared them as nullable).

  1. collections, such as *List<string>*
GET /products?items=a&items=b&items=c

public HttpResponseMessage Get([FromUri]List<string> items) {}  
  1. key value pairs
GET /product?key=mykey&value=myvalue

public HttpResponseMessage Get([FromUri]KeyValuePair<string, string> id) {}  
  1. apparently it also works with dictionaries, but I honestly don’t know how πŸ™‚

  2. anything else, as long as you create a custom ModelBinder or a custom ValueProvider that would do the appropriate type coercion from the string

[ModelBinder], allows you to plug in your own logic of determining how a set of data fed from ValueProviders should be converted into a CLR type of your choice.

A good example might be:

GET /products?sizes=L,XL,XXL  

In this example you may want to bind sizes to an IEnumerable or Array of strings. There is nothing in the box in Web API for this particular scenario, so you’d need a custom [ModelBinder].

In the above example it would be:

public class CommaDelimitedCollectionModelBinder : IModelBinder  
{  
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)  
{  
var key = bindingContext.ModelName;  
var val = bindingContext.ValueProvider.GetValue(key);  
if (val != null)  
{  
var s = val.AttemptedValue;  
if (s != null && s.IndexOf(",", System.StringComparison.Ordinal) > 0)  
{  
var stringArray = s.Split(new[] { "," }, StringSplitOptions.None);  
bindingContext.Model = stringArray;  
}  
else  
{  
bindingContext.Model = new[] { s };  
}  
return true;  
}  
return false;  
}  
}  

And can now be used:

public HttpResponseMessage Get([ModelBinder(typeof(CommaDelimitedCollectionModelBinder))]IEnumerable<string> sizes) {};  

Notice we used the default ValueProviders and just split the string extracted by the provider in the ModelBinder itself. You could potentially split the string inside a custom value provider too.

Defaulting to [FromUri] for GET and HEAD requests πŸ”—

Web API will always try to eagerly bind non-primitive and non-string covnertible types from the body of the request. While in many cases that’s all right, semantically, it doesn’t make much sense for GET and HEAD requests, since in accordance to the HTTP specification, these are body-less requests.

The decision whether a parameter should be bound from URI or body happens inside the DefaultActionValueBinder (the default implementation of the aforementioned IActionValueBinder), which, as almost every infrastructure piece in Web API, is extensible and overridable.

Going back to our previous example, we’d like the GET request to be written without a necessity for [FromUri].

public HttpResponseMessage Get(Productfilter filter) {}  

To achieve this we can introduce a small custom action value binder, extending the default one:

public class CustomActionValueBinder : DefaultActionValueBinder  
{  
protected override HttpParameterBinding GetParameterBinding(HttpParameterDescriptor parameter)  
{  
return parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get) || parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Head) ?  
parameter.BindWithAttribute(new FromUriAttribute()) : base.GetParameterBinding(parameter);  
}  
}

config.Services.Replace(typeof(IActionValueBinder), new CustomActionValueBinder());  

With this trivial change, we tell Web API that if a given action is GET/HEAD type of action, always bind as if [FromUri] was present, otherwise, we let the base logic flow.

Using MVC style binding with Web API πŸ”—

If you feel that MVC style binding is something you’d need in your Web API, you can achieve it by incorporating a terrific MvcActionValueBinder by Mike Stall into your application. It is also now part of the WebApiContrib project, so can be instantly pulled from Nuget.

To enable it, simply install WebApiContrib and register it against your Web API configuration:

install-package webapicontrib  

And:

configuration.Services.Replace(typeof(IActionValueBinder), new MvcActionValueBinder())  

The binding is also applicable via an attribute so you can add it to your controller accordingly:

[MvcStyleBindingAttribute]  
public class MyController : ApiController {}  

without having to register against the global HttpConfiguration.

What’s next πŸ”—

In the next post of this series we’ll look at binding from the body, and after that implement customized HttpParameterBinding.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github and on Mastodon.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP