Mapping UrlEncoded POST Values in ASP.NET Web API
Posted
by Rick Strahl
on West-Wind
See other posts from West-Wind
or by Rick Strahl
Published on Thu, 16 Aug 2012 19:42:06 GMT
Indexed on
2012/08/27
21:39 UTC
Read the original article
Hit count: 1148
Web Api
If there's one thing that's a bit unexpected in ASP.NET Web API, it's the limited support for mapping url encoded POST data values to simple parameters of ApiController methods. When I first looked at this I thought I was doing something wrong, because it seems mighty odd that you can bind query string values to parameters by name, but can't bind POST values to parameters in the same way.
To demonstrate here's a simple example. If you have a Web API method like this:
[HttpGet] public HttpResponseMessage Authenticate(string username, string password) { …
}
and then hit with a URL like this:
http://localhost:88/samples/authenticate?Username=ricks&Password=sekrit
it works just fine. The query string values are mapped to the username and password parameters of our API method.
But if you now change the method to work with [HttpPost] instead like this:
[HttpPost] public HttpResponseMessage Authenticate(string username, string password) { …
}
and hit it with a POST HTTP Request like this:
POST http://localhost:88/samples/authenticate HTTP/1.1
Host: localhost:88
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-type: application/x-www-form-urlencoded
Content-Length: 30Username=ricks&Password=sekrit
you'll find that while the request works, it doesn't actually receive the two string parameters. The username and password parameters are null and so the method is definitely going to fail.
When I mentioned this over Twitter a few days ago I got a lot of responses back of why I'd want to do this in the first place - after all HTML Form submissions are the domain of MVC and not WebAPI which is a valid point.
However, the more common use case is using POST Variables with AJAX calls. The following is quite common for passing simple values:
$.post(url,{ Username: "Rick", Password: "sekrit" },function(result) {…});
but alas that doesn't work.
How ASP.NET Web API handles Content Bodies
Web API supports parsing content data in a variety of ways, but it does not deal with multiple posted content values. In effect you can only post a single content value to a Web API Action method. That one parameter can be very complex and you can bind it in a variety of ways, but ultimately you're tied to a single POST content value in your parameter definition. While it's possible to support multiple parameters on a POST/PUT operation, only one parameter can be mapped to the actual content - the rest have to be mapped to route values or the query string.
Web API treats the whole request body as one big chunk of data that is sent to a Media Type Formatter that's responsible for de-serializing the content into whatever value the method requires. The restriction comes from async nature of Web API where the request data is read only once inside of the formatter that retrieves and deserializes it. Because it's read once, checking for content (like individual POST variables) first is not possible.
However, Web API does provide a couple of ways to access the form POST data:
- Model Binding - object property mapping to bind POST values
- FormDataCollection - collection of POST keys/values
ModelBinding POST Values - Binding POST data to Object Properties
The recommended way to handle POST values in Web API is to use Model Binding, which maps individual urlencoded POST values to properties of a model object provided as the parameter. Model binding requires a single object as input to be bound to the POST data, with each POST key that matches a property name (including nested properties like Address.Street) being mapped and updated including automatic type conversion of simple types. This is a very nice feature - and a familiar one from MVC - that makes it very easy to have model objects mapped directly from inbound data.
The obvious drawback with Model Binding is that you need a model for it to work: You have to provide a strongly typed object that can receive the data and this object has to map the inbound data.
To rewrite the example above to use ModelBinding I have to create a class maps the properties that I need as parameters:
public class LoginData { public string Username { get; set; } public string Password { get; set; } }
and then accept the data like this in the API method:
[HttpPost] public HttpResponseMessage Authenticate(LoginData login) { string username = login.Username; string password = login.Password;
…
}
This works fine mapping the POST values to the properties of the login object.
As a side benefit of this method definition, the method now also allows posting of JSON or XML to the same endpoint. If I change my request to send JSON like this:
POST http://localhost:88/samples/authenticate HTTP/1.1 Host: localhost:88 Accept: application/json
Content-type: application/json Content-Length: 40 {"Username":"ricks","Password":"sekrit"}
it works as well and transparently, courtesy of the nice Content Negotiation features of Web API.
There's nothing wrong with using Model binding and in fact it's a common practice to use (view) model object for inputs coming back from the client and mapping them into these models.
But it can be kind of a hassle if you have AJAX applications with a ton of backend hits, especially if many methods are very atomic and focused and don't effectively require a model or view. Not always do you have to pass structured data, but sometimes there are just a couple of simple response values that need to be sent back. If all you need is to pass a couple operational parameters, creating a view model object just for parameter purposes seems like overkill. Maybe you can use the query string instead (if that makes sense), but if you can't then you can often end up with a plethora of 'message objects' that serve no further purpose than to make Model Binding work.
Note that you can accept multiple parameters with ModelBinding so the following would still work:
[HttpPost] public HttpResponseMessage Authenticate(LoginData login, string loginDomain)
but only the object will be bound to POST data. As long as loginDomain comes from the querystring or route data this will work.
Collecting POST values with FormDataCollection
Another more dynamic approach to handle POST values is to collect POST data into a FormDataCollection. FormDataCollection is a very basic key/value collection (like FormCollection in MVC and Request.Form in ASP.NET in general) and then read the values out individually by querying each.
[HttpPost] public HttpResponseMessage Authenticate(FormDataCollection form) { var username = form.Get("Username"); var password = form.Get("Password"); …
}
The downside to this approach is that it's not strongly typed, you have to handle type conversions on non-string parameters, and it gets a bit more complicated to test such as setup as you have to seed a FormDataCollection with data. On the other hand it's flexible and easy to use and especially with string parameters is easy to deal with. It's also dynamic, so if the client sends you a variety of combinations of values on which you make operating decisions, this is much easier to work with than a strongly typed object that would have to account for all possible values up front.
The downside is that the code looks old school and isn't as self-documenting as a parameter list or object parameter would be. Nevertheless it's totally functionality and a viable choice for collecting POST values.
What about [FromBody]?
Web API also has a [FromBody] attribute that can be assigned to parameters. If you have multiple parameters on a Web API method signature you can use [FromBody] to specify which one will be parsed from the POST content. Unfortunately it's not terribly useful as it only returns content in raw format and requires a totally non-standard format ("=content") to specify your content.
For more info in how FromBody works and several related issues to how POST data is mapped, you can check out Mike Stalls post:
How WebAPI does Parameter Binding
Not really sure where the Web API team thought [FromBody] would really be a good fit other than a down and dirty way to send a full string buffer.
Extending Web API to make multiple POST Vars work? Don't think so
Clearly there's no native support for multiple POST variables being mapped to parameters, which is a bit of a bummer. I know in my own work on one project my customer actually found this to be a real sticking point in their AJAX backend work, and we ended up not using Web API and using MVC JSON features instead. That's kind of sad because Web API is supposed to be the proper solution for AJAX backends.
With all of ASP.NET Web API's extensibility you'd think there would be some way to build this functionality on our own, but after spending a bit of time digging and asking some of the experts from the team and Web API community I didn't hear anything that even suggests that this is possible. From what I could find I'd say it's not possible primarily because Web API's Routing engine does not account for the POST variable mapping. This means [HttpPost] methods with url encoded POST buffers are not mapped to the parameters of the endpoint, and so the routes would never even trigger a request that could be intercepted. Once the routing doesn't work there's not much that can be done.
If somebody has an idea how this could be accomplished I would love to hear about it.
Do we really need multi-value POST mapping?
I think that that POST value mapping is a feature that one would expect of any API tool to have. If you look at common APIs out there like Flicker and Google Maps etc. they all work with POST data. POST data is very prominent much more so than JSON inputs and so supporting as many options that enable would seem to be crucial.
All that aside, Web API does provide very nice features with Model Binding that allows you to capture many POST variables easily enough, and logistically this will let you build whatever you need with POST data of all shapes as long as you map objects. But having to have an object for every operation that receives a data input is going to take its toll in heavy AJAX applications, with a lot of types created that do nothing more than act as parameter containers.
I also think that POST variable mapping is an expected behavior and Web APIs non-support will likely result in many, many questions like this one:
How do I bind a simple POST value in ASP.NET WebAPI RC?
with no clear answer to this question.
I hope for V.next of WebAPI Microsoft will consider this a feature that's worth adding.
Related Articles
- Passing multiple POST parameters to Web API Controller Methods
- Mike Stall's post: How Web API does Parameter Binding
- Where does ASP.NET Web API Fit?
© West-Wind or respective owner