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:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

ASP.NET Web API and Simple Value Parameters from POSTed data


:P
On this page:

In testing out various features of Web API I've found a few oddities in the way that the serialization is handled. These are probably not super common but they may throw you for a loop. Here's what I found.

Simple Parameters from Xml or JSON Content

Web API makes it very easy to create action methods that accept parameters that are automatically parsed from XML or JSON request bodies. For example, you can send a JavaScript JSON object to the server and Web API happily deserializes it for you.

This works just fine:

public string ReturnAlbumInfo(Album album)
{
    return album.AlbumName + " (" + album.YearReleased.ToString() + ")";
}

However, if you have methods that accept simple parameter types like strings, dates, number etc., those methods don't receive their parameters from XML or JSON body by default and you may end up with failures. Take the following two very simple methods:

public string  ReturnString(string message)
{            
    return message;
}

public HttpResponseMessage ReturnDateTime(DateTime time)
{
    return Request.CreateResponse<DateTime>(HttpStatusCode.OK, time);
}

The first one accepts a string and if called with a JSON string from the client like this:

var client = new HttpClient();
var result = client.PostAsJsonAsync<string>(http://rasxps/AspNetWebApi/albums/rpc/ReturnString,                                            "Hello World").Result;

which results in a trace like this:

POST http://rasxps/AspNetWebApi/albums/rpc/ReturnString HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: rasxps
Content-Length: 13
Expect: 100-continue
Connection: Keep-Alive

"Hello World"

produces… wait for it: null.

Sending a date in the same fashion:

var client = new HttpClient();
var result = client.PostAsJsonAsync<DateTime>(http://rasxps/AspNetWebApi/albums/rpc/ReturnDateTime, 
new DateTime(2012, 1, 1)).Result;

results in this trace:

POST http://rasxps/AspNetWebApi/albums/rpc/ReturnDateTime HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: rasxps
Content-Length: 30
Expect: 100-continue
Connection: Keep-Alive

"\/Date(1325412000000-1000)\/"

(yes still the ugly MS AJAX date, yuk! This will supposedly change by RTM with Json.net used for client serialization)

produces an error response:

The parameters dictionary contains a null entry for parameter 'time' of non-nullable type 'System.DateTime' for method 'System.Net.Http.HttpResponseMessage ReturnDateTime(System.DateTime)' in 'AspNetWebApi.Controllers.AlbumApiController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.

Basically any simple parameters are not parsed properly resulting in null being sent to the method. For the string the call doesn't fail just producing null, but for the non-nullable date it produces an error because the method can't handle a null value.

This behavior is a bit unexpected to say the least, but there's a simple solution to make this work using an explicit [FromBody] attribute:

public string  ReturnString([FromBody] string message)

and

public HttpResponseMessage ReturnDateTime([FromBody] DateTime time)

which explicitly instructs Web API to read the value from the body.

UrlEncoded Form Variable Parsing

updated 3/23/2012 with additional information about FormDataCollection

Another similar issue I ran into is with POST Form Variable binding. Web API can retrieve parameters from the QueryString and Route Values but it doesn't explicitly map parameters from POST values either.

Taking our same ReturnString function from earlier and posting a message POST variable like this:

var formVars = new Dictionary<string,string>();
formVars.Add("message", "Some Value");
var content = new FormUrlEncodedContent(formVars);

var client = new HttpClient();
var result = client.PostAsync(http://rasxps/AspNetWebApi/albums/rpc/ReturnString,
content).Result;

which produces this trace:

POST http://rasxps/AspNetWebApi/albums/rpc/ReturnString HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: rasxps
Content-Length: 18
Expect: 100-continue

message=Some+Value

When calling ReturnString:

public string  ReturnString(string message)
{            
    return message;
}

unfortunately it does not map the message value to the message parameter. This sort of mapping unfortunately is not available in Web API.

Web API does support binding to form variables but only as part of model binding, which binds model properties to the POST variables. Sending the same message to the following method that access a model object works:

public string ReturnMessageModel(MessageModel model)
{        
    return model.Message;
}


public class MessageModel
{
    public string Message { get; set; }}

Note that the model is bound and the message form variable is mapped to the Message property as would other POST form variables if there were more. This works but it's not very dynamic as you have to a have a static model parameter.

You can get at the form variables manually however. You can specify that the parameter is a FormDataCollection instance:

public string  ReturnFormVariableString(FormDataCollection formData)
{
    return formData.Get("message");
}

Oddly FormDataCollection does not allow for indexers to work so you have to use the .Get() or .GetValues() methods (for multi-select values) which is pretty weird for a collection type.

Note that although this isn't recommended: If you're running Web API under ASP.NET you still have access to the HttpContext.Current.Request object. This will work ONLY in ASP.NET and not in self-hosted scenarios and will significantly complicate test scenarios, but if you need to get at some data that Web API doesn't expose the Request is there for you in a pinch. There shouldn't be much that Web API doesn't expose though, so try to avoid use of it even if sometimes it just seems easier to go that route.

Access the QueryString

Querystring values are automatically mapped to parameters on the controller method fired. In most cases that's sufficient, but if for some reason you have too many querystring values to push into parameters or you otherwise need to parse your querystring explicitly here's how you can do that from within a controller method:

var queryVals = Request.RequestUri.ParseQueryString();    
var message = queryVals["message"];

Note that ParseQueryString is an Extension Method to Uri in System.Net.Http so make sure to add that namespace.

Summary

As you can see Web API makes most things related to parameter mapping pretty easy, but there are some behaviors can be a little 'different' and require a little more work than we might be used from directly accessing HttpContext.Current in plain ASP.NET applications. While HttpContext is still available in Web API when running under IIS, in general it's not a good idea to use it. First and most importantly it only works when running under IIS which means it doesn't work for self-hosting and is problematic if you are unit testing your Web API code since no HttpContext is available under test.  Inside of the controller most Http features are available from the Request property although it might require a little digging the first few times you need access to form variables and querystrings.

In a way it's funny that accessing complex values in Web API actually requires less effort than accessing simple values. I'm not fond of the decision to make the behavior between simple and complex types different when mapping parameters - when I first started playing with Web API it actually led me to a bunch of false assumptions about what wasn't working. For example I first ran into this with date values and assumed this was a problem with Web API's date parsing when in fact it was simply the parameter mapping that didn't work. I hope some revision happens here make this easier still.

Also realize that Web API doesn't have any sort of global context object. Controllers get easy access to the Request object, but lower level components like formatters, filters and message handlers don't have easy access to these objects. These APIs require special methods that can intercept and capture the Request which is a topic for another post.

One of the reasons that these inconsistencies exist is that there are a number of different binding approaches used by Web API: QueryString mapping, Post data model binding, xml and json parsing of single values etc. and the order of the paths through Web API end up causing some of these issues. While I think they are pretty annoying and can result in WTF moments, I also think that the scenarios where it happens (single value assignments) are pretty rare. I suspect most applications will opt to pass objects around or map POST/PUT data to a server side View model rather than posting individual values. But nevertheless it's important to understand that there are some… cough… unexpected behaviors.

Posted in Web Api  

The Voices of Reason


 

magellings
March 22, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Wow some surprising stuff. Hopefully they add support for this in the RTW/RTM...perhaps you should try contacting them to give you input. I would think all of these scenarios should behave as one would suspect.

Cris Barbero
March 23, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

There are some helpful extension methods in the System.Net.Http.UriExtensions class that help for parsing the QueryString off of the RequestUri property of HttpRequestMessage.

These can be used like so:
request.RequestUri.ParseQueryString (returns NameValueCollection)

Rick Strahl
March 26, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Cris - Thanks! Updated the post with the info.

Shiv Kumar
March 27, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Rick,
I my mind there is nothing unexpected here. Method parameters that are string & int are expected as part of the Url (determined by your route), while complex types are expected in the Http Content part.

There are a few bugs in the current parsing/binding in Web API (that have been fixed) but this I suspect is as designed (and in my mind is how it should be).

Didn't know about the FromBody attribute! However, I'd take a serious look at my API design if I needed to use it for some reason.

Vivek
March 29, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Rick
nice stuff . I have query:
Can we load whole message body into controller's method paramter using [FromBody]?

for example:
I am posting xml:
<Header>
<node1>node1text</node1>
<node2>node2text</node2>
.......
</Header>

I am trying to load this xml in a string parameter of my controller method:

void PostHeader([FromBody] string header)
{
}

Thanks
Vivek

Rick Strahl
March 29, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@vivek - I don't think you can do this from a string parameter unless the data is sent using a content type that Web API doesn't parse (like text/plain maybe). If the content type is text/xml Web API will try to parse the message as XML (not quite sure what it'll look for but pretty sure you won't get the whole message)

However, the better way to read the entire request data is to accept HttpRequest and then look at the Content property (stream) which you can read directly and work with.

public string ReturnRequestBody(HttpRequestMessage request)
{
   return request.Content.ReadAsStringAsync().Result;
}

Vivek
March 30, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Rick- Thanks for your suggestion. It worked well for me
Thanks,
Vivek

Alex
August 06, 2012

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Thanks you sooo much for doing this!! I spent hours scouring the web for solutions to no avail.

TMox
August 21, 2013

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Thanks for this. A simple DateTime was frustratingly failing until I saw the [FromBody] attribute in your post. All better now.

Scott Gartner
April 16, 2014

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Thanks for this, I spent a bunch of time trying to get POST data to map to a simple f(string,string) before I found your article and now know that it just won't work!

Rick Strahl
April 16, 2014

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Scott - there are some workarounds:

http://weblog.west-wind.com/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API-Controller-Methods

http://weblog.west-wind.com/posts/2012/Sep/11/Passing-multiple-simple-POST-Values-to-ASPNET-Web-API

but as you've found out, it's not exactly obvious or easy to work around.

Overall I'd say the best way to handle this is to use ViewModels to represent the data you're trying to capture - it's the most efficient and 'WebAPI' way of doing things. If you really need to map POST values to parameters and you prefer that style, then using the Parameter binder in the second post is the way to go.

vicky
November 28, 2014

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Rick
its owesome..
Can we load whole message body into controller's method paramter?

for example:
I am posting xml:
<Search>
<Criteria>
<KeywordValue>computer</Keyword>
</Criteria>
<SearchType>Keyword</SearchType>
</Search>


I am trying to load this xml in a string parameter of my controller method:

void PostMethod(search abc)
{
}

Rick Strahl
November 28, 2014

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

@Vicky - you can use something like this:

[HttpPost]
public async Task<string> PostRawBufferManual()
{
    string result = await Request.Content.ReadAsStringAsync();            
    return result;
}


You can find a lot more info on the issue and why it's designed that way in Web API in the "Accepting Raw Request Body Content with Web API" post:

http://weblog.west-wind.com/posts/2013/Dec/13/Accepting-Raw-Request-Body-Content-with-ASPNET-Web-API

vicky
February 09, 2015

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Thanks for your quick reply Rick and Sorry for the delay in my response.
so by the above example i can send XML,JSON or RAW from the body? and why the return type is async can we return anything from the method?

Regards,
Vicky

Vedran Mandić
November 20, 2019

# re: ASP.NET Web API and Simple Value Parameters from POSTed data

Hello Rick,

Firstly, a deep thank you for the valuable post (and all of your years long work and knowledge you shared here)!

I was not aware of the easy to pass parameters as Form data from an URL query string, really neat. What I am actually looking into is trying to send an email DTO from C# client to a C# .NET Core web api, i.e. an integration test for it.

Do you know what would be a good approach to pass form data + a file attachment? Is var content = new MultipartFormDataContent(); with new StringContent and ByteArrayContent a good approach? I am doing this with HttpClient. RestSharp lib is unfortunately a no go (not supported in TestServer.CreatClient() scenario for an integration test).

Thanks, V.


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