Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Accepting Raw Request Body Content with ASP.NET Web API


Looking for an ASP.NET Core version of this post? Go here: Accepting Raw Request Body Content in ASP.NET Core API Controllers.

ASP.NET Web API is a great tool for building HTTP services with ASP.NET. It makes many things HTTP, that used to be ill defined in .NET easy and allows you to build a variety of services very easily. Like other complex abstraction frameworks it makes complex scenarios super easy, while some very simple and common operations are not quite as straight forward or as transparent as they should be.

During this year’s MVP summit it was pointed out that ASP.NET Web API makes some very simple operations that a newbie might try when getting started very non-obvious. One of those I wrote about last year, which is mapping post values to controller method parameters and turns out to require a fairly complex workaround. Another one that was brought to my attention by Scott Hanselman and is the topic of this post, is how to capture the raw content of an HTTP request.

Capturing raw request content is by no means difficult with Web API, but it’s not exactly obvious either. Yet, it’s one of those first steps that somebody kicking the tires of ASP.NET Web API is very likely to do.

“Hey let’s take a string and just post it to the server and see what happens, right?”

Yeah well… if you try that, you’re likely going to hit a wall because Web API’s behavior for simple value content mapping is not well defined nor easy to discover.

So in this post I’ll look at posting raw data – not JSON or XML, but just plain text or binary data – to an Web API controller and picking up that data in a controller method. In the process we’ll create a [NakedBody] attribute (the name is Scott’s idea, but it I like it!) that’ll handle the task of capturing raw request data to a parameter.

Complex Values are easy

ASP.NET Web API makes it pretty easy to pass complex data in the form of JSON or XML to the server. It’s easy to do and intuitive. If you want to capture an object you can simply create a controller method that has an object parameter and Web API automatically handles decoding JSON or XML into the object's structure for you.

[HttpPut] 
public HttpResponseMessage PutAlbum(Album album)
{
}

You don't have to do anything special to get album to parse from say JSON or XML - Web API's Conneg logic automatically detects the content type, maps it to a MediaFormatter and converts the incoming JSON or XML (or whatever other formatters are configured) data and converts it to the type of the parameter of the controller method.

If the data happens to be POST form data (ie. urlencoded key value pairs), Web API’s Model Binding can automatically map each of the keys of the form data to the properties of the object, including nested object paths.

So that's very easy and as it should be, and it actually addresses most of the realistic use cases. This is the ‘complex stuff is easy’ part.

Capturing the raw Request Body

But things are not so easy and transparent when you want to pass simple parameters like strings, numbers, dates etc. Because Web API is based on a host of conventions, some things that should be easy, such as easily picking up the raw content of a POST or PUT operation and capturing it, aren't quite so transparent as the automatic type mapping shown above.

One thing you might expect to be able to easily do, is to pick up the entire content body and map it to a parameter like this:

[HttpPost]
public string PostRawBuffer(string raw)
{
    return raw;
}

Quick, what do you think is required by the client to call this method the string parameter? I’ll wait here while you ponder…

The answer is - not easily and not without some additional ‘hints’.

There are a number of issues here that actually make this one of the worst parameter signatures in Web API.
This parameter signature does not work with any of these posted values:

  • Raw Content Buffer Data (entire buffer)
  • A JSON string with application/json content type
  • A UrlEncoded Form Variable
  • A QueryString Variable

In fact, no matter what you pass here in posted body content – the string parameter is always null. The same is true if you have any other simple parameter type – numbers, bools, dates, byte[] etc. Plain parameter mapping (without special attributes) works only complex types like objects and arrays.

If you really think about how Web API’s parameter bindings work, this sort of makes sense with the exception of the JSON string parameter. Parameter bindings map based on media types (ie. content-type header) using the Conneg algorithm by default and try to map parameters as whole entities. If you post a a raw string or buffer Web API internally really has no idea how to map this to anything. Should it map the entire buffer? A form variable? A JSON string? So it needs some hints to do it’s thing.

Why doesn’t a JSON string work?

That explains raw strings, but not the JSON string. It’s definitely baffling that a JSON string posted with an application/json content type doesn’t work. So if you POST something like this:

POST http://dev.west-wind.com/aspnetwebapi/samples/PostJsonString HTTP/1.1
Host: dev.west-wind.com
Content-type: application/json; charset=utf-8 
Content-Length: 24

"Posting a JSON string."

this is a valid JSON request, but it still fails to map.

[FromBody] to retrieve Content

I mentioned hints, and Web API allows you to use parameter binding attributes to provide these hints. These allow you to tell Web API where the content is coming from explicitly. There are [FromBody] and [FromUri] attributes that can force content to be mapped from POST or query string content for example.

Using [FromBody] like this:

[HttpPost]
public string PostJsonString([FromBody] string text)
{
    return text;
}

now allows JSON or XML content to be mapped from the body. Same goes for other simple parameter types like numbers, dates, bools etc.

So if you now post:

POST http://rasxps/aspnetwebapi/samples/PostRawBuffer HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: rasxps
Content-Length: 22
 
"Hello World Suckers."

you now get the raw parameter mapped properly because the input string is in JSON format.

[FromBody] works for properly formatted content – ie. JSON, XML and whatever other media formatters that are configured in the Conneg pipeline. It requires that the data is formatted in JSON or XML. [FromBody] also works with a single POST form variable in urlencoded form data, but because it only works with a single parameter it’s kind of limited for that.

But [FromBody] still doesn’t work if you just want to capture the entire raw content – so if the data is not JSON or XML encoded and you just want the raw data [FromBody] is no help.

Posted in Web Api  

The Voices of Reason


 

Steve
December 13, 2013

# re: Accepting Raw Request Body Content with ASP.NET Web API

Thanks much for this, very helpful.

I suspect that the dev group working on Web API touched on this and said, "Why would anyone do a simple string? That's so un-cool." and left it out.

Vince
December 14, 2013

# re: Accepting Raw Request Body Content with ASP.NET Web API

Even I asked myself why anyone would want this but I'm sure there's a use case somewhere.

Even so I learned a little more about parameter bindings. Thanks Rick.

Rick Strahl
December 14, 2013

# re: Accepting Raw Request Body Content with ASP.NET Web API

@Vince - Think of this as a raw HTTP handler type input. Web API is being pitched as the solution for all non-HTML data situations and raw data is not uncommon at call. Any kind of 'raw data' upload would have to be handled this way. If you think of Web API as a generic HTTP solution, then yes, I think this is not an uncommon scenario.

frantisek
January 02, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

if content body starts with "=", then binding to raw string works out of the box, IMHO

Rick Strahl
January 02, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

@frantisek - Ah yes, forgot about that, but that's not very useful since it's some oddball format. If you're building an API would you really ever ask anyone, but when you send your content just add an = at the front? :-) I don't even consider that a feature...

Tim Hardy
April 07, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

The following...
[HttpPost]
public string PostJsonString([FromBody] string text)


does NOT work with property formatted json...
{ "name": "widget", "description":"some description" }

It always returns null for text, and it's very frustrating. That's with Content-Type: application/json; While "Posting a JSON string" might be a valid json request, I've never seen one that's a simple string. Practical json requests, anything starting with a curly brace, don't work.

It works if you put an = in front of the first curly brace, but I'm not going to change the client code to hack that in. The only way I can get dynamic json to be accepted in a web api controller is to accept it as a JObject, and incur a couple of extra transformations that I'd rather avoid.

I'll try your NakedBody attribute, but I'd like something that at least validates the body is valid json. It's crazy to me that this isn't possible in Web Api out of the box.

Rick Strahl
April 07, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

@Tim - That makes no sense to me. If you want the raw body why would you want to validate the JSON? You get a string or raw buffer because that's what you're asking for with 'raw' data. If you want validation for your JSON match it to an object or use dynamic and get back a JsonValue object you can iterate through without typing.

Mick
April 17, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

I change this code to put more parameters.

In NakedBodyParameterBinding in ExecuteBindingAsync

I change:
if (binding.ParameterBindings.Length > 1 ||
actionContext.Request.Method == HttpMethod.Get)
return EmptyTask.Start();

var type = binding
.ParameterBindings[0]
.Descriptor.ParameterType;


To

if (binding.ParameterBindings.Length == 0 ||
actionContext.Request.Method == HttpMethod.Get)
return EmptyTask.Start();

var parameter = binding.ParameterBindings.FirstOrDefault(x => x.Descriptor.ParameterBinderAttribute !=null && x.Descriptor.ParameterBinderAttribute.GetType() == typeof(NakedBodyAttribute));
if(parameter ==null)
return EmptyTask.Start();
var type = parameter.Descriptor.ParameterType;

Isaac
July 31, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

Small adjustment to fix issues where the current thread values (Principal, etc.) are being lost before it gets to the controller.

Instead of:

return actionContext.Request.Content
                    .ReadAsStringAsync()
                    .ContinueWith((task) =>
                    {
                        var stringResult = task.Result;
                        SetValue(actionContext, stringResult);
                    });


Do this:

var rawData = actionContext.Request.Content.ReadAsStringAsync().Result;
SetValue(actionContext, rawData);
return EmptyTask.Start();

Highdown
September 21, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

I have been reading your posts on Web API, and they have been very helpful in solving some of my client/server communication issues. Here are two other sites that I have garnered information to deal with uploading/downloading byte[]'s.

http://byterot.blogspot.com/2012/04/aspnet-web-api-series-part-5.html

http://stackoverflow.com/questions/9541351/returning-binary-file-from-controller-in-asp-net-web-api

I am using TypeScript/AngularJS on the client, and there wasn't much information readily available for something as simple as a byte[] upload/download with Web API. I was used to Silverlight and WCF RIA Services where you could easily upload strings, numbers, and multiple byte arrays in a single server upload.

I have finally been able to get a working client/server setup to upload/download a single byte[]. However, what I was hoping to achieve was to be able to upload more than one byte array at the same time. I do not think the Web API technology is designed to support this, but I could be wrong. Any thoughts?

What I may do is to merge my byte arrays on the client and then break them apart on the server. This is a little messier, but so far it is the only option that I have come up with.

Anyway, thanks for all your posts.

Regards...

Chandana
November 11, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

Thanks a ton!! works perfectly for my need.

Phil
November 14, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

This is cool and helped me handle this scenario, but this whole block is unnecessary, as well as the empty task class.

var binding = actionContext
            .ActionDescriptor
            .ActionBinding;
 
        if (binding.ParameterBindings.Length > 1 || 
            actionContext.Request.Method == HttpMethod.Get)            
            return EmptyTask.Start();            
 
        var type = binding
                    .ParameterBindings[0]
                    .Descriptor.ParameterType;


1. Assuming it can be the only parameter period is false - A FromUri parameter, or any other parameter whose binding returns false for WillReadBody, should work fine alongside this. (note that if you pair this with FromBody, instead of failing gracefully like this attempts the controller just refuses to bind)
2. Hardcoding GET as not working with this is kind of unnecessary.
3. The type parameter is searching through an array for, what ends up being, "this".
You can replace it with
var type = Descriptor.ParameterType;

Rob
November 21, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

I like it. Can this be done in VS2008 with .NET framework 3.5 or are more current versions required? Thanks

Rick Strahl
November 21, 2014

# re: Accepting Raw Request Body Content with ASP.NET Web API

@Rob - You need .NET 4.0 at least.

Hemang
January 28, 2015

# re: Accepting Raw Request Body Content with ASP.NET Web API

I am a newbie to angular and wanted your help on consuming the following method using $resource service instead of $http from my angular script, how can I achieve this?

[HttpPost]
public string PostJsonString([FromBody] string text)
{
return text;
}

Thanks in advance.

Gang Luo
November 26, 2015

# re: Accepting Raw Request Body Content with ASP.NET Web API

It's really helpful. Thanks for sharing.
Add two my cents, it's not necessary to limit this to the only parameter since developers may need pass other parameters from the Url's query string. EmptyTask is not necessary. My version is as below, it works for me.
    public class RawHttpReqeustBodyParameterBinding : HttpParameterBinding
    {
        public RawHttpReqeustBodyParameterBinding(HttpParameterDescriptor descriptor)
            : base(descriptor) { }
 
        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                                    HttpActionContext actionContext,
                                                    CancellationToken cancellationToken)
        {
            if (actionContext.Request.Method == HttpMethod.Get)
            {
                SetValue(actionContext, null);
 
                var tsc = new TaskCompletionSource<object>();
                tsc.SetResult(null);
                return tsc.Task;
            }
            else if (Descriptor.ParameterType == typeof(string))
            {
                return actionContext.Request.Content
                        .ReadAsStringAsync()
                        .ContinueWith((task) =>
                        {
                            var stringResult = task.Result;
                            SetValue(actionContext, stringResult);
                        });
            }
            else if (Descriptor.ParameterType == typeof(byte[]))
            {
                return actionContext.Request.Content
                    .ReadAsByteArrayAsync()
                    .ContinueWith((task) =>
                    {
                        byte[] result = task.Result;
                        SetValue(actionContext, result);
                    });
            }
 
            throw new InvalidOperationException("Only string and byte[] are supported for [RawHttpRequestBody] parameters");
        }
 
        public override bool WillReadBody
        {
            get
            {
                return true;
            }
        }
    }


Usage:
public HttpResponseMessage DoSmoething(string para1, [RawHttpRequestBody] string body = null, string para2, ...)
{
}

AK3800
December 16, 2015

# re: Accepting Raw Request Body Content with ASP.NET Web API

I've been using this for a while and it has been working fine as long as the parameter marked with this attribute is the only parameter, but now I have a need to do this along with another parameter from the URI. @Gang Luo and @Phil, how are you getting your versions to work with additional parameters. I tried making the changes as you suggested, at least the routing made it to the method but when I try to access the other variable that was part of the route, I get this error:
Cannot obtain value of the local variable or argument because it is not available at this instruction pointer, possibly because it has been optimized away.


Here's a snippet of the method where I'm using this:
      <Route("audit/lastxdays/{days:int}")>
      <HttpPost>
      Public Function GetOppAuditLastXDays(days As Integer, <RawContent> fieldlist As String) As HttpResponseMessage

In this example, the days parameter gives me the error when I try to access it, but the fieldlist parameter is available as expected. Thoughts?

Bradley
January 07, 2016

# re: Accepting Raw Request Body Content with ASP.NET Web API

What you you suggest for binding httpFile? WebApi doesn't seem to understand how to accept mutli/part data. I understand I can access this data in HttpContext.Current.Request.Files or MultipartFormDataStreamProvider, but neither of these approaches seem very web api because they undo some of the magic model binding. In mvc, I used the signature upload(int id, httpFile file), but again this does not work for web api. It seems like you could write a custom mediaformatter, but I have doubts about going down that path. Any thoughts Rick? You always seem to have the best answers for the more complicated stuff? Thanks,
Bradley

jeremy
June 02, 2016

# re: Accepting Raw Request Body Content with ASP.NET Web API

this works great, but is there a way I can have multiple parameters??? I have tinkered with the code, and i can't even enter the ExecuteBindingAsync with more than 1 parameter otherwise I think I could get it to work

Rick Strahl
June 03, 2016

# re: Accepting Raw Request Body Content with ASP.NET Web API

@Jeremy - Web API doesn't make that easy. Wrote a separate post about accepting multiple POST paramters as parameters rather than objects: http://weblog.west-wind.com/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API-Controller-Methods

Alastair
August 11, 2016

# re: Accepting Raw Request Body Content with ASP.NET Web API

Thanks for your blogs - always like reading yours Rick.

I may have misread and I realise this is from 2013 and WebApi has come on since then, but I landed here because I'm trying to not only capture the raw request payload (for logging purposes) but I want to do that transparently to the rest of the pipeline so that I still make use of model binding.

What I suspect I'll end up doing is hooking into the default binder implementation and extend / override it but ignoring my particular model binding requirement, I was able to get at the raw http request by using the following signature:

public void FooEvent(HttpRequestMessage request)


Then I just call the ReadAsStringAsync() method on the request.Content property.

string requestContent = request.Content.ReadAsStringAsync().Result;


(Yes I know I'm blocking :) )

I'm actually posting XML to this endpoint but you can post anything you want.

At first glance that appears to be what you're doing here but as I say I may have misread and that was a comparatively long time ago :) I also think your custom attribute is more elegant and expressive.

My sample is from an OWIN-hosted WebApi so not sure if the objects are the same as your article - the HttpRequestMessage object is in the System.Net.Http namespace.

Good stuff!
Alastair

Erik
February 09, 2017

# re: Accepting Raw Request Body Content with ASP.NET Web API

Hello @RickStrahl nice post, I'm new in the .NET world. I can't read the body content from a Request(System.Web...) object. always is null. I've been trying different ways, is an MVC Project created in Xamarin. The thing is that the JSON sended in the POST request may has differents properties.


Kashif
August 30, 2017

# re: Accepting Raw Request Body Content with ASP.NET Web API

Excellent post, is there anything similar available for .net core.


Kashif
September 10, 2017

# re: Accepting Raw Request Body Content with ASP.NET Web API

Hi, I have written an post for accepting raw request body content with asp.net web api .net core 2 https://medium.com/@kashifsoofi/accepting-raw-request-body-content-with-asp-net-core-2-web-api-ba8f0072a0eb.


Daniel
November 15, 2017

# re: Accepting Raw Request Body Content with ASP.NET Web API

Hi Rick, thanks for this article. Works for me. I would like to use more than one parameter for the method besides [NakedBody].

I.e, I want to do something like this:

public string Operation([NakedBody] string body, string other, int other, ...){...}

Any way to do this? I know you stated explicitly that you can only use 1 parameter (the NakedBody parameter), but I was hoping to do something like this.

Currently if I try something like this I get the response:

{
    "Message": "The request is invalid.",
    "MessageDetail": "The parameters dictionary does not contain an entry for parameter 'body' of type 'System.String' for method 'System.String GetLicense(System.Net.Http.HttpRequestMessage, AbacusLicensingServer.Providers.ProductName, System.String, System.String)' in 'AbacusLicensingServer.Controllers.LicensingController'. The dictionary must contain an entry for each parameter, including parameters that have null values."
}

Thanks

Daniel

 

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