A few years back I wrote a mail near Accepting Raw Asking Content with ASP.Internet Spider web API. Unfortunately the process to go at raw request information is rather indirect, with no direct way to receive raw information 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 permit you customize how to handle 'unknown' content types in your controllers.

Let's take a await.

Creating a Simple Exam Controller

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

          public class BodyTypesController : Controller { }                  

JSON String Input

Lets outset with a non-raw request, but rather with posting a string as JSON since that is very mutual. You lot tin accept a string parameter and post JSON data from the client pretty hands.

So given this endpoint:

          [HttpPost] [Route("api/BodyTypes/JsonStringBody")] public cord JsonStringBody([FromBody] string content) {     return content; }                  

I can post the following:

Figure 1 - JSON String inputs thankfully capture as strings in ASP.Cyberspace Core

This works to recollect the JSON string every bit a plain string. Annotation that the cord sent is non a raw string, just rather a JSON cord as it includes the wrapping quotes:

          "Windy Rivers are the Best!"                  

Don't Forget [FromBody]

Make sure you lot add [FromBody] to whatever parameter that tries to read data from the Postal service torso and maps it. It's easy to forget and not really obvious that it should exist 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 transport a RAW string or binary data and you desire to choice that up as part of your request things go more complicated. ASP.Internet Core handles only what information technology knows, which by default is JSON and Form data. Raw data is not directly mappable to controller parameters by default.

So if you trying to send this:

          Post http://localhost:5000/api/BodyTypes/JsonPlainBody HTTP/i.1 Accept-Encoding: gzip,debunk User-Amanuensis: West Wind HTTP .NET Customer Content-Type: text/obviously Host: localhost:5000 Content-Length: 28 Look: 100-continue  Windy Rivers are the all-time!                  

to this controller activity:

          [HttpPost] [Route("api/BodyTypes/PlainStringBody")] public string PlainStringBody([FromBody] cord content) {     return content; }                  

The event is a 404 Not Plant.

I'thou essentially doing the aforementioned thing as in the first request, except I'thou not sending JSON content type merely plain text. The endpoint exists, simply MVC doesn't know what to exercise with the text/plain content or how to map it then 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 y'all think about it: MVC has mappings for specific content types and if you pass information that doesn't fit those content types it tin can't convert the information, and then it assumes in that location'southward no matching endpoint that tin can handle the request.

And then how practise we get at the raw data?

Reading Request.Trunk for Raw Information

Unfortunately ASP.NET Core doesn't let you just capture 'raw' data in whatsoever meaningful style but by fashion of method parameters. One fashion or another yous need to do some custom processing of the Request.Body to become the raw information out so deserialize it.

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

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

Read a String Buffer
          [HttpPost] [Route("api/BodyTypes/ReadStringDataManual")] public async Task<cord> ReadStringDataManual() {     using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))     {           render await reader.ReadToEndAsync();     } }                  

This works with the post-obit HTTP and plainly text content:

          Mail service http://localhost:5000/api/BodyTypes/ReadStringDataManual HTTP/1.i Accept-Encoding: gzip,deflate Content-Blazon: text/apparently Host: localhost:5000 Content-Length: 37 Expect: 100-continue Connection: Keep-Alive  Windy Rivers with Waves are the best!                  

To read binary data yous can utilize the post-obit:

Read a Byte Buffer
          [Route("api/BodyTypes/ReadBinaryDataManual")] public async Task<byte[]> RawBinaryDataManual() {     using (var ms = new MemoryStream(2048))     {         wait Request.Torso.CopyToAsync(ms);         return  ms.ToArray();  // returns base64 encoded cord JSON result     } }                  

which works with this HTTP:

          POST http://localhost:5000/api/BodyTypes/ReadBinaryDataManual HTTP/i.one Take-Encoding: gzip,deflate User-Agent: Westward Wind HTTP .NET Client Content-Type: awarding/octet-stream Host: localhost:5000 Content-Length: forty Expect: 100-proceed Connection: Keep-Alive  Wind and Water make the earth become 'round.                  

I'chiliad sending a cord here to make information technology readable, but really the content could be raw binary byte data - it doesn't thing what the content is in this case but it should be considered every bit binary data.

Running this results in:

Effigy 2 - Capturing raw binary asking data.

The issue in the code is captured as binary byte[] and returned as JSON, which is why you lot meet the base64 encoded effect string that masquerades every bit a binary result.

Asking Helpers

If you lot do this a lot a couple of HttpRequest extension methods might be useful:

          public static class HttpRequestExtensions {      /// <summary>     /// Think the raw trunk as a cord from the Request.Torso stream     /// </summary>     /// <param name="request">Request instance to use to</param>     /// <param name="encoding">Optional - Encoding, defaults to UTF8</param>     /// <returns></returns>     public static async Task<string> GetRawBodyStringAsync(this HttpRequest asking, Encoding encoding = null)     {         if (encoding == null)             encoding = Encoding.UTF8;          using (StreamReader reader = new StreamReader(request.Body, encoding))             return await reader.ReadToEndAsync();     }      /// <summary>     /// Retrieves the raw trunk as a byte assortment from the Asking.Trunk stream     /// </summary>     /// <param name="request"></param>     /// <returns></returns>     public static async Task<byte[]> GetRawBodyBytesAsync(this HttpRequest request)     {         using (var ms = new MemoryStream(2048))         {             await asking.Body.CopyToAsync(ms);             return ms.ToArray();         }     } }                  

Listing 1 - HttpRequest Extensions to retrieve raw torso string and byte information. Github

which allows you to simplify those two previous controller methods to:

          [HttpPost] [Route("api/BodyTypes/ReadStringDataManual")] public async Task<string> ReadStringDataManual() {     return await Asking.GetRawBodyStringAsync(); }  [HttpPost] [Road("api/BodyTypes/ReadBinaryDataManual")] public async Chore<byte[]> RawBinaryDataManual() {     return await Asking.GetRawBodyBytesAsync(); }                  

Automatically Converting Binary and Raw Cord Values

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

Create an MVC InputFormatter

ASP.NET Core has a make clean and more than generic way to handle custom formatting of content using an InputFormatter. Input formatters claw into the request processing pipeline and let you await at specific types of content to decide if you want to handle it. You tin can so read the request body and perform your own deserialization on the entering content.

There are a couple of requirements for an InputFormatter:

  • Yous need to use [FromBody] to get information technology fired
  • You take to exist able to expect at the request and make up one's mind 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)

Y'all can add others to this list or check other headers to determine if yous want to handle the input only you demand to be explicit what content types y'all want to handle.

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

          /// <summary> /// Formatter that allows content of type text/obviously and application/octet stream /// or no content type to be parsed to raw data. Allows for a single input parameter /// in the grade of: ///  /// public string RawString([FromBody] cord information) /// public byte[] RawData([FromBody] byte[] data) /// </summary> public class RawRequestBodyFormatter : InputFormatter {     public RawRequestBodyFormatter()     {         SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plainly"));         SupportedMediaTypes.Add together(new MediaTypeHeaderValue("awarding/octet-stream"));     }       /// <summary>     /// Allow text/plain, awarding/octet-stream and no content type to     /// exist processed     /// </summary>     /// <param name="context"></param>     /// <returns></returns>     public override Boolean CanRead(InputFormatterContext context)     {         if (context == null) throw new ArgumentNullException(nameof(context));          var contentType = context.HttpContext.Asking.ContentType;         if (cord.IsNullOrEmpty(contentType) || contentType == "text/plain" ||             contentType == "application/octet-stream")             return true;          return false;     }      /// <summary>     /// Handle text/apparently or no content type for string results     /// Handle awarding/octet-stream for byte[] results     /// </summary>     /// <param name="context"></param>     /// <returns></returns>     public override async Job<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)     {         var request = context.HttpContext.Request;         var contentType = context.HttpContext.Asking.ContentType;           if (cord.IsNullOrEmpty(contentType) || contentType == "text/plain")         {             using (var reader = new StreamReader(request.Body))             {                 var content = expect reader.ReadToEndAsync();                 return await InputFormatterResult.SuccessAsync(content);             }         }         if (contentType == "application/octet-stream")         {             using (var ms = new MemoryStream(2048))             {                 look request.Body.CopyToAsync(ms);                 var content = ms.ToArray();                 render expect InputFormatterResult.SuccessAsync(content);             }         }          return await InputFormatterResult.FailureAsync();     } }                  

Listing 2 - InputFormatter to handle Raw Request inputs for selected content types. GitHub

The formatter uses CanRead() to bank check requests for content types to back up 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:

          public void ConfigureServices(IServiceCollection services) {     services.AddMvc(o => o.InputFormatters.Insert(0, new RawRequestBodyFormatter())); }                  

Accepting Raw Input

With the formatter hooked up to the MVC formatter list you can now handle requests that POST or PUT to the server using text/apparently, application/octet-stream or no content types.

Raw String

          [HttpPost] [Road("api/BodyTypes/RawStringFormatter")]         public string RawStringFormatter([FromBody] cord rawString) {     return rawString; }                  

and you tin mail service to it like this:

          Postal service http://localhost:5000/api/BodyTypes/RawStringFormatter HTTP/one.one Accept-Encoding: gzip,debunk  Raw Air current and Water brand the world go 'round.                  

or

          POST http://localhost:5000/api/BodyTypes/RawStringFormatter HTTP/ane.1 Take-Encoding: gzip,deflate Content-type: text/plain  Raw Air current and Water make the world go plain.                  

The controller will at present pick up the raw cord text.

Note that you can call the same controller method with a content type of awarding/json and pass a JSON string and that will piece of work every bit well. The RawRequestBodyFormatter just adds support for the additional content types information technology supports.

Binary Data

Binary data works the same manner merely with a different signature and content blazon for the HTTP asking.

          [HttpPost] [Route("api/BodyTypes/RawBytesFormatter")] public byte[] RawBytesFormatter([FromBody] byte[] rawData) {     return rawData; }                  

and this HTTP request data with 'binary' content:

          Mail service http://localhost:5000/api/BodyTypes/RawBytesFormatter HTTP/1.one Accept-Encoding: gzip,deflate Content-type: application/octet-stream  Raw Wind and Water make the globe go 'round.                  

Again I'm sending a string to provide something readable hither, merely the string is treated as binary data past the method and returned every bit such equally shown in Figure two.

Source Code provided

If y'all want to play with this stuff and experiment, I've uploaded my sample project to Github:

  • Sample Source Lawmaking on Github

The sample HTTP requests are setup in West Wind Web Surge and ready to test against or you can just use the BodyTypes.websurge file and pick out the raw HTTP request traces.

  • W Wind Web Surge for Http Testing

Summary

Accepting raw information is not something y'all take to practise all the fourth dimension, just occassionally it is required for API based applications. ASP.NET MVC/Spider web API has never been very direct in getting at raw information, but once you sympathize how the pipeline manages asking data and deals with content blazon mapping it'due south 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 information types

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

All of this is making me hungry for some raw Sushi...