Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Mapping UrlEncoded POST Values in ASP.NET Web API


:P
On this page:

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: 30

Username=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.

[Update Sept. 11, 2012]

After a long bit of searching and some help from Microsoft I managed to find a solution to this by creating a custom parameter binding, which is described in this blog post:

Passing multiple simple POST Values to ASP.NET Web API

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

Posted in Web Api  

The Voices of Reason


 

some name
August 16, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

Ugly I guess, but couldn't someone develop a template to code gen simple classes and corresponding routable methods using them that just call the non-routable [HttpPost] methods?

Mike Gale
August 17, 2012

# Another technology hits the scrap heap, unfortunate

Thanks for this post. I was thinking about using WebAPI. This puts it into the rubbish bin for me. Makes me wonder about the background of the people who are designing the API.

Jamie
August 17, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

I am loving all your digging into WebAPI. Keep this up, there's a lot to know here and your posts are really helping me understand how it works and can fit in with the architectures I use.

For this sort of situation, couldn't you just use dynamic objects? Coincidentally I came across your answer on SO as the confirmation that it works with WebAPI (I haven't actually tried it myself)

http://stackoverflow.com/questions/9421970/asp-net-web-api-controller-cant-handle-dynamic-objects

I completely agree that creating structures for every API method is egregious, this seems like a no brainer for dynamic types.

// authInfo should have members "UserName"  and "Password"
public HttpResponseMessage Authenticate(dynamic authInfo)

Of course for a method with two parameters this seems like overkill. I would probably create an API that just expected them as part of the request, e.g.

/api/authenticate/username/password

Now this gets back to my diatribe on your last post about Web API :) since you'd have to also create a new route just for this method. I think you should be able to create generic route:

/api/{query}

and a method:

public HttpResponseMessage Authenticate(string userName, string passWord)

and have any request that matches that signature, e.g.

/api/authenticate/string/string

invoke it and map to the parameters. But that's a different story...

John
August 17, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

I found these lessons out the hard way after several hours of debugging. Some of the decisions are highly counter-intuitive. One variable only? A custom format for putting data into the body that doesn't include the variable's name?

Another thing that threw me was the need to use [FromBody] on a Post([FromBody] string someValue) method (it was not necessary in the Beta, but became so in the RC). Web API assumes that since string is simple, the value must be coming from the body, no matter what which HTTP verb was used. Unfortunately, this runs counter to the well-known behavior of Html. For html, form posts will, by default, put data into the body, unless you specify otherwise.

Not to kill Web API off entirely, but when using it within an MVC project, it does not support MVC areas. This leaves your MVC controllers off in one place and your Web API controllers off in another place.

I like that Web API automatically uses the accept headers, however it's easy to write a custom filter for MVC to do the same thing and there are examples out there.

It's hard for me, at this point, to see how Web API is better than just using MVC to do the same thing.

Rick Strahl
August 17, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

@John - Yeah you hit another issue of contention and that is that there is a lot of duplication of classes in MVC and Web API and they're using separate implementations of the same classes in different namespaces. I hope in the future they'll get this sorted by closer integrating the two, although I have a feeling that isn't going to happen because of the self-hosting requirements.

Most issues I've run in have been minor, but the POST handling to me is a pretty big deal. I know that several customers I work with specifically use POST values for AJAX requests and while Model Binding works it's overkill in a lot of places.

I've been pretty bullish on Web API because I've built my own framework which in the end I still find more usable than what Web API provides (as part of the West Wind Web Toolkit). But even so I don't want to maintain that code base going forward - it would be nice to just be able to use what's in the box, without further hacking for once.

For a 1.0 product I think Web API is pretty darn close to where it needs to be. The issue I describe here is annoying to me, but I suspect most people will be fine with using the ModelBinder or sticking with strong types etc. without too much trouble. Still I hope they address this because I think it's a common use case and just plain makes sense based on behavioral expectations.

David C
August 19, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

Rick,

I’ve learned a lot from reading your blog over the last year. I’m open to the fact that I’m totally missing the point, but I'm having a hard time reconciling my experience with my understanding of what you say doesn’t work with web api. So to clarify, I’ll show you what I have seen work. If I’ve missed the point completely, please forgive my lack of understanding.

Below is a method on an ApiController.

public HttpResponseMessage PostGroupUser(int groupID, int userID)
{
. . .
}

The function below is successfully calling the method above.
    function postGroupUser(groupID, userID) {
        $.ajax({
            url: "/api/Association/PostGroupUser",
            data: ko.toJSON({
                groupID: groupID,
                userID: userID 
            }),
            type: "POST",
            contentType: "application/json;charset=utf-8"
        });
    }

If this example is different than what you are referring to in this blog post, can you please help me understand the differences?

This worked in the beta . These methods have since been replaced, so I’ll need to set it up to check. If this no longer works, I’ll be very disappointed with ApiController.

One last question, is it possible the reason the Jquery post example doesn’t work is because the client is sending “Username” and “Password” and Authenticate takes “username” and “password”?

[HttpPost]
public HttpResponseMessage Authenticate(string username, string password)
{
}

$.post(url,{ Username: "Rick", Password: "sekrit" },function(result) {…});


I'm grateful for the time you put into your blog. I look forward to learning from your response.

Respectfully,

David C

Rick Strahl
August 19, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

@David - Are you sure the code you posted works? It doesn't for me, and to be frank I'm not sure how it could. You are passing a JSON object and you're expecting it to map the JSON properties to simple parameters? There's no binding that does that AFAIK.

This request:

POST http://localhost/aspnetwebapi/samples/Authenticate HTTP/1.1
Host: localhost
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Type: application/json
Content-Length: 35
 
{username:ricks",password:"sekrit"}

To:
[HttpPost,HttpGet]
public HttpResponseMessage Authenticate(string username, string password)

produces an error: No action was found on the controller 'SamplesApi' that matches the request. Which makes sense. If I use querystring parameters for username and password then it works fine.

AFAIK, multiple simple parameters only map from query string values and that's my point for this post. Take a look at the other POST parameter post that's linked on the bottom which describes more generically how POST parameters are handled (ie. JSON, XML and UrlEncoded data)

David C
August 20, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

Rick,

The code I submitted worked in beta. I had a prototype built on Upshot and DbDataController. I used many different implementations of the code above to handle adding and deleting the records in m2m tables. It worked. I needed to add Action to the route since the signatures for each method were all (int, int). At one point all of my ajax calls were working accept one. The difference was an Upper case on the parameter sent by the client. Once case matched, all the methods worked.

Since Upshot didn’t make the RC and I was moved to another project, all of this code was re-written by someone else. I pulled the project out of TFS, but since all the GAC’d assemblies on my machine don’t include DbDataController, I can’t make it work at this time.

Now I need to set up a project just to test this in the RTM. As I previously stated, I’ll be disappointed if this doesn’t work.

Respectfully,

David C

kammushi
September 02, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

thank you very much mr.Rick , this is the first time for me in the asp.net web api 's world and i have learned so many things from you .... i was planning to build a rich web application using asp.net with api (small erp system) , but this issue disappointed me , any way ...


thank you very much and keep it up man

Matt
November 13, 2012

# re: Mapping UrlEncoded POST Values in ASP.NET Web API

Thank you for this post. I was in the midst of creating wep api Auth controller, and then changing the way I handled the form submission via XHR requests in Sencha ExtJS. It seems I can let the form post directly to the controller and capture the information right from model binding to the post values. I didn't realize how it was handling post data from forms. This was interesting and informative.

Thanks again.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024