A few years back I wrote a post about Accepting Raw Request Content with ASP.NET Web API. Unfortunately the process to get at raw request data is rather indirect, with no direct way to receive raw data into Controller action parameters and that hasn't really changed in ASP.NET Core's MVC/API implementation. The way the Conneg algorithm works in regards to generic data formats is roughly the same as it was with Web API.

The good news is that it's quite a bit easier to create custom formatters in ASP.NET Core that let you customize how to handle 'unknown' content types in your controllers.

Let's take a look.

Creating a Simple Test Controller

To check this out I created a new stock ASP.NET Core Web API project and changed the default ValuesController to this sample controller to start with:

public class BodyTypesController : Controller { }

JSON String Input

Lets start with a non-raw request, but rather with posting a string as JSON since that is very common. You can accept a string parameter and post JSON data from the client pretty easily.

This works to retrieve the JSON string as a plain string. Note that the string sent is not a raw string, but rather a JSON string as it includes the wrapping quotes:

"Windy Rivers are the Best!"

Don't Forget [FromBody]

Make sure you add [FromBody] to any parameter that tries to read data from the POST body and maps it. It's easy to forget and not really obvious that it should be there. I say this because I've forgotten it plenty of times and scratched my head wondering why request data doesn't make it to my method or why requests fail outright with 404 responses.

No JSON - No Workey

If you want to send a RAW string or binary data and you want to pick that up as part of your request things get more complicated. ASP.NET Core handles only what it knows, which by default is JSON and Form data. Raw data is not directly mappable to controller parameters by default.

I'm essentially doing the same thing as in the first request, except I'm not sending JSON content type but plain text. The endpoint exists, but MVC doesn't know what to do with the text/plain content or how to map it and so it fails with a 404 Not Found.

It's not super obvious and I know this can trip up the unsuspecting Newbie who expects raw content to be mapped. However, this makes sense if you think about it: MVC has mappings for specific content types and if you pass data that doesn't fit those content types it can't convert the data, so it assumes there's no matching endpoint that can handle the request.

So how do we get at the raw data?

Reading Request.Body for Raw Data

Unfortunately ASP.NET Core doesn't let you just capture 'raw' data in any meaningful way just by way of method parameters. One way or another you need to do some custom processing of the Request.Body to get the raw data out and then deserialize it.

You can capture the raw Request.Body and read the raw buffer out of that which is pretty straight forward.

The easiest and least intrusive, but not so obvious way to do this is to have a method that accepts POST or PUT data without parameters and then read the raw data from Request.Body:

Automatically Converting Binary and Raw String Values

If you'd rather use a more deterministic approach and accept raw data through parameters, a little more work is required by building a custom InputFormatter.

Create an MVC InputFormatter

ASP.NET Core has a clean and more generic way to handle custom formatting of content using an InputFormatter. Input formatters hook into the request processing pipeline and let you look at specific types of content to determine if you want to handle it. You can then read the request body and perform your own deserialization on the inbound content.

There are a couple of requirements for an InputFormatter:

You need to use [FromBody] to get it fired

You have to be able to look at the request and determine if and how to handle the content

So in this case for 'raw content' I want to look at requests that have the following content types:

text/plain (string)

application/octet-stream (byte[])

No content type (string)

You can add others to this list or check other headers to determine if you want to handle the input but you need to be explicit what content types you want to handle.

To create a formatter you either implement IInputFormatter or inherit from InputFormatter. The latter is usually the better approach, and that's what I used to create RawRequestBodyFormatter:

The formatter uses CanRead() to check requests for content types to support and then the ReadRequestBodyAsync() to read and deserialize the content into the result type that should be returned in the parameter of the controller method.

The InputFormatter has to be registered with MVC in the ConfigureServices() startup code:

Raw String

POST http://localhost:5000/api/BodyTypes/RawStringFormatter HTTP/1.1
Accept-Encoding: gzip,deflate
Raw Wind and Water make the world go 'round.

or

POST http://localhost:5000/api/BodyTypes/RawStringFormatter HTTP/1.1
Accept-Encoding: gzip,deflate
Content-type: text/plain
Raw Wind and Water make the world go plain.

The controller will now pick up the raw string text.

Note that you can call the same controller method with a content type of application/json and pass a JSON string and that will work as well. The RawRequestBodyFormatter simply adds support for the additional content types it supports.

Binary Data

Binary data works the same way but with a different signature and content type for the HTTP request.

Summary

Accepting raw data is not something you have to do all the time, but occassionally it is required for API based applications. ASP.NET MVC/Web API has never been very direct in getting at raw data, but once you understand how the pipeline manages request data and deals with content type mapping it's easy to get at binary data.

In this post I showed two approaches:

Manually grabbing the Request.Body and deserializing from there

Using a custom InputFormatter that looks at typical 'raw' Content data types

The former is easy to use but doesn't describe the API behavior via the method interface. The latter is a little more work and requires hooking up a custom formatter, but it allows keeping the API's contract visible as part of the controller methods which to me simply feels cleaner.

I'm not able to receive a json object as a string even though content-type is set to application/json. Why would this happen?

Specifically, this is happening while implementing Stripe into my ASP.NET Core API backend. Stripe takes credit card data entered by user and creates what they call a "token" which is a json object. When I send it to my API method, I'm able to hit the method but the string parameter ends up with a null value.

[HttpPost]
public async Task<IActionResult> Process([FromBody] string token)
{
// I'm able to hit this method but end up with token=null
}

I am trying to get the first example to work with a Post from a form where jquery converts the form variables into json but the value is always null. I do not understand how you were able to post to the first example successfully. Can you please show me in an html jquery form?

@Phil - you're posting form data not which has a specific content type that is handled by ASP.NET Core (ie. application/x-www-form-urlencoded) and treated like form data. What I describe here doesn't apply to that unless you extend the formatted to explicitly handle the form data content type (which it does not now).

However, if you're posting form data you should be able to use the Request.Form[] collection to retrieve individual values or the whole thing.

Hi, Rick. Do you know you know if there's any difference in performance (or any other trade-off) when reading the text/plain string from the Request.Body instead of the application/json and [FromBody] approach? Thanks for the post.

The string comparison against "text/plain" or "application/octet-stream" fails when the browser includes other elements in the content-type field, e.g. "text/plain; charset=utf-8". I changed the check to .contains() to get the middleware to work. Without it, I was getting 415 (media not supported errors).. which led me to the cause.

Can you please help me with this. We are in position where we cannot change the .net solution code. Any changes should be done in .net core solution side. We are trying to fit the new api in place of existing Endpoint. 😦

Hi Rick, great work! Could you explain on how to read context.Request multiple times using GetRawBodyStringAsync. I tried to extend it with Request.EnableRewind, but I cannot get a hold of it. Thanks, Dave

@ST - not sure what you're asking. If you have form data it should map to object properties when using [FromBody] with application/x-www-form-urlencoded. If content is application\json it should deserialize into the object.