Using jQuery to POST [FromBody] parameters to Web API
jQuery, Web APIBy Dave Ward. Posted April 3, 2013ASP.NET Web API has been one of my favorite recent additions to ASP.NET. Whether you prefer a RESTful approach or something more RPC-oriented, Web API will do just about anything you need. Even if you’re still using ASP.NET WebForms, Web API still has you covered – a nice example of the “One ASP.NET” philosophy that’s finally beginning to come together.
However, ASP.NET Web API throws an unintuitive curveball at you when you want to accept simple primitive types as parameters to POST methods. Figuring this issue out is particularly confusing because it’s one of the rare parts of Web API that violates the principle of least astonishment.
Because of that, using jQuery to interact with those methods requires a slight contortion that seems strange at first, but makes sense once you understand why it’s necessary. In this post, I’ll briefly describe the underlying issue and show you how jQuery can be convinced to work around the issue.
POSTing primitive parameters to Web API
There are three things you need to know about Web API if you want to go the route of POSTing a primitive type (e.g. string, int, or bool) to a method. For example, say you’re working with this straightforward POST method in a Web API controller named ValuesController
:
// POST api/valuespublicstring Post(string value){return value;}
You might try POSTing a value into that Web API method using a standard jQuery $.post
, like this:
$.post('/api/values',{ value:'Dave'});
If you inspect the server’s response to that request, you’ll be greeted with a bewildering 404 error:
Confronted with that response, it’s only natural to treat the problem as an issue with your routing configuration and debug from there, but that’s not the issue.
1. Parameters must be marked as [FromBody]
As it turns out, the reason for that 404 error isn’t a routing problem. ASP.NET correctly identifies our ValuesController
as the controller to handle a POST to /api/values
, but it can’t locate an acceptable method to process the request.
The reason for that is a twofold mismatch between the required parameter that Web API expects to accompany requests to our Post
method and what we sent. The first of those mismatches is that the method’s parameter must be decorated with the [FromBody]
attribute.
[FromBody]
directs Web API to search for the parameter’s value in the body of a POST request. Adding that directive to our method is easy enough:
// POST api/valuespublicstring Post([FromBody]string value){return value;}
I can’t say that I understand why this extra hassle is necessary, especially since it wasn’t required in WCF Web API and preliminary versions of ASP.NET Web API, but it’s easy enough as long as you’re aware that it needs to be present.
Adding [FromBody]
fixes our 404 error, but things unfortunately still aren’t working quite right. Making the same request to it again works, but the value
parameter is coming back as null
now instead of the provided value:
2. Only one parameter per method
Speaking of the parameters to our method, another potentially confusing thing about accepting [FromBody]
parameters is that there can be only one.
Attempting to accept multiple parameters from the POST body will result in a (refreshingly decipherable) server-side error along these lines:
Can’t bind multiple parameters (‘foo’ and ‘bar’) to the request’s content.
It’s possible to circumvent this limitation by using manual parsing techniques, but that seems antithetical to Web API and model binding to me. If you want Web API to automatically convert POST data to your [FromBody]
input parameter, you have to limit yourself to only a single parameter.
I believe the thinking here is that, especially in a RESTful API, you’ll want to bind data to the single resource that a particular method deals with. So, pushing data into several loose parameters isn’t the sort of usage that Web API caters to.
This limitation can be a bit confusing when you’re coming from WebForms or MVC though, and trying work around it feels like going against the grain of the framework. So, if I need to accept more than one POST parameter, I just define a quick view model.
In other words, don’t try to do this:
publicstring Post([FromBody]string FirstName, [FromBody]string LastName){return FirstName +" "+ LastName;}
Instead, work with the framework and do something like this if you need to accept more than a single parameter:
// DTO or ViewModel? Potato or potato, IMO.publicclass PersonDTO {publicstring FirstName { get; set;}publicstring LastName { get; set;}}publicstring Post(PersonDTO Person){return Person.FirstName+" "+ Person.LastName;}
3. [FromBody] parameters must be encoded as =value
The final hurdle remaining is that Web API requires you to pass [FromBody]
parameters in a particular format. That’s the reason why our value parameter was null in the previous example even after we decorated the method’s parameter with [FromBody]
.
Instead of the fairly standard key=value
encoding that most client- and server-side frameworks expect, Web API’s model binder expects to find the [FromBody]
values in the POST body without a key name at all. In other words, instead of key=value
, it’s looking for =value
.
This part is, by far, the most confusing part of sending primitive types into a Web API POST method. Not too bad once you understand it, but terribly unintuitive and not discoverable.
What not to do
Now that we’ve covered what you need to know about the server-side code, let’s talk about using jQuery to POST data to [FromBody]
parameters. Again, here’s the Web API method that we’re trying to POST data into:
// POST api/valuespublic string Post([FromBody]string value){return value;}
You’ll have a hard time finding a jQuery $.post
(or $.ajax
) example that conforms to the =value
encoding approach. Typically, platform-agnostic jQuery documentation and tutorials suggest that one of these two approaches should work:
// Value will be null. $.post('api/values', value);// Value will be null. $.post('api/values',{ key: value });
Unfortunately, neither of those work with Web API in the case of [FromBody]
parameters (the latter would work to send an object, rather than a single primitive value). In both cases, our method will be invoked, but the model binder won’t assign anything to value
and it will be null
.
Making it work
There are two ways to make jQuery satisfy Web API’s encoding requirement. First, you can hard code the =
in front of your value, like this:
$.post('api/values',"="+ value);
Personally, I’m not a fan of that approach. Aside from just plain looking kludgy, playing fast and loose with JavaScript’s type coercsion is a good way to find yourself debugging a “wat” situation.
Instead, you can take advantage of how jQuery encodes object parameters to $.ajax
, by using this syntax:
$.post('api/values',{'': value });
If the data parameter has properties with empty string keys, jQuery serializes those properties in the form of =value
. And, that’s exactly what we need to make Web API happy:
Similar posts
One Mention Elsewhere
16 Comments
Nice summary of the various pitfalls of posting to Web API end points.
Any disadvantages to making the rule: “To avoid confusing your users, always use viewmodels rather than primitives for POST/PUT operations”?
That seems reasonable to me.
That’s messed-up. Did you get an explanation from the team about that weird design choice?
I haven’t.
I’ve railed against this from the beginning. I think there are some technical reasons that underly this, but it’s a pain in the butt anyway. I’ve been working with various customers who just expect this to work and this is one point where I’ve lots of whining and pissing about.
I wrote about this a long while back along with a really convoluted workaround to make simple POST parameters and single ModelState object parameters work:
Overview here:
http://www.west-wind.com/weblog/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API-Controller-Methods
Workaround/hack here:
http://www.west-wind.com/weblog/posts/2012/Sep/11/Passing-multiple-simple-POST-Values-to-ASPNET-Web-API
It’s really a shame we’re even having this discussion, but due to the async nature of Web API it’s apparently very difficult to handle multiple formatters simultaneously.
Low level response parsing is one thing where Web API usage is really inferior to the nice and easy features that ASP.NET (natively and also in MVC) offers and in fact the easiest way to get at these values might just be doing raw FormCollection stuff.
There is a technical reason, its to do with being able to single scan over the payload IIRC. So basically an optimisation – there is info about this out there.
Its horrible though, WebApi is unfortunately pretty hard to use outside of the demoware scenario as you immediately end up stepping on land mines like this. The other major stumbling block for people is no out of the box support for nested rest URIs e.g. Chatrooms\123\Messages\88.
Of course you can create your own route, but then all the convention based naming stuff goes to pot.
I wish they’d thought a little harder about both issues. The optimisation is nice, but should be opt in as for many its really not a concern, After all we’ve not all been saying our MVC controllers are too slow at param matching? Similarly building in first party support for nested rest uris would also be super useful. Could perhaps be implemented using nested classes? e.g.
publicclass ChatRooms : ApiController {publicclass Messages {// matches Chatrooms/xxx/Messages/yyystring Get(int chatroomid, int messageid){}}}
I’m in the process of porting several hundred WCF methods over to what was going to be web api, but now we are changing the signatures by having to return jsonResults which was easier than creating a viewmodel for every method and rewriting the bodies.
We are using a T4 template to generate a javascript service object which matches the signatures of the WCF generated files, but uses jquery to do the requests. It would have been nice to have the return types exposed in the method signatures for intellisense in visual studio.
Would this solve your problem? https://gist.github.com/anonymous/5311068
Add an instance of this message handler to your config.MessageHandlers collection and any form data that comes in will be converted to route parameters as if they came in on the query string. Then you just drop the [FromBody] attribute and pretend everything is a query string parameter.
With this you can map multiple body values to simple parameters. If there are extra parameters in the body it will still map to the action.
Currently I don’t deal with conflicts between query string parameters and body parameters, but I’m assuming that’s not a huge deal.
Thoughts?
That’s somewhat a WAT/ situation…
Man, this is frustrating! WebApi is Microsoft’s big opportunity to catch up to the rest of the web-world, where REST is easy to write and easy to consume. Instead, this is obscure/impossible to write (I’ve wasted several hours trying to figure it out) and impossible for the consumer to guess.
Mike,
I have found that Web API works wonderfully for me for building REST based systems. However, I prefer not to have a framework guess how to convert my representations to CLR types.
From day 1 Web API was never intended to be a fully featured REST framework. It was intended to be a low level HTTP abstraction layer, that people would use to build opinionated frameworks on top of it. That’s why there is so many extensibility points in it.
When the project merged with the ASP.NET MVC there was very quick effort to layer a bunch of MVCisms on top of it avoid the impeding cries of “why should I learn something new”,”why two ways to do the same thing”. Unfortunately, the similarities between MVC and Web API are only surface deep.
If you scrape off that “let’s make it look like MVC” layer, there is a very nicely architected HTTP framework that lets you do pretty much whatever you want.
I’m quite sure that the team, given some time, will be able to make a more faithful reproduction of the MVC experience, but considering how little time they had to port from the WCF code, when everything moved under ASP.NET, I think what they achieved is pretty darn good.
My request is to not “throw the baby out with the bath water”. Web API hasn’t done a great job of being an MVC equivalent, but there is so much more to it than that.
Great post as usual.
I’ve been struggling with the same problems since I’ve started working with Web API but I have to say, once you have learned the common pitfalls, development flow gets pretty straight forward. And you can do amazing stuff with Web API, for instance content negotiation is mind blowing, with a small amount of work you can get data in json/xml/html/whatever formats using the same REST call (only modifying HTTP headers).
Staying on topic, I’ve found JSON.NET coming handy when you need to pass dynamic complex parameters to Web API methods. This is how simple it can get:
- JS
$.ajax({ type:"POST", url:"api/MyController", data:{ PropertyA:123, PropertyB:"abc"}});
- C#
// POST api/MyControllerpublicvoid POST(JObject foo){// deserialize json object MyClass bar = foo.ToObject<MyClass>(); var a = bar.PropertyA; bar.doStuff();}
So, for me the bottom line is, I’m trying to figure out whether WebAPI is for real or demoware. I am a little shocked about issues like the parameter binding thing, and I’m at the beginning of a large project architecture.
Most disturbing from this blog article, it sounds like your javascript callers need to “know” that you are using WebApi so that they can code to its eccentricities. (the key/value thing).
I’m not building a demo, and I’m not building a small, quick-hit site. I’m building a major piece of software that needs to be extensible, and will need to be around for a while. Should I just walk away from Web Api right now?
This particular binding issue is kind of annoying, but not something that you should run into when building a large scale site. It’s always better to have API methods backed by view models instead of being ad-hoc.
Web API is a core part of ASP.NET going forward. I wouldn’t worry about basing a new project on it at all.
What do you think?
I appreciate all of your comments, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums or Stack Overflow instead.
If you're replying to another comment, use the threading feature by clicking "Reply to this comment" before submitting your own.