Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read request body in an asp.net core webapi controller?

In ASP.Net Core it seems complicated to read several times the body request, however if your first attempt does it the right way, you should be fine for the next attempts.

I read several turnaround for example by substituting the body stream, but I think the following is the cleanest:

The most important points being

  1. to let the request know that you will read its body twice or more times,
  2. to not close the body stream, and
  3. to rewind it to its initial position so the internal process does not get lost.

[EDIT]

As pointed out by Murad, you may also take advantage of the .Net Core 2.1 extension: EnableBuffering It stores large requests onto the disk instead of keeping it in memory, avoiding large-streams issues stored in memory (files, images, ...). You can change the temporary folder by setting ASPNETCORE_TEMP environment variable, and files are deleted once the request is over.

In an AuthorizationFilter, you can do the following:

// Helper to enable request stream rewinds
using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableBodyRewind : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var bodyStr = "";
        var req = context.HttpContext.Request;

        // Allows using several time the stream in ASP.Net Core
        req.EnableRewind(); 

        // Arguments: Stream, Encoding, detect encoding, buffer size 
        // AND, the most important: keep stream opened
        using (StreamReader reader 
                  = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
        {
            bodyStr = reader.ReadToEnd();
        }

        // Rewind, so the core is not lost when it looks the body for the request
        req.Body.Position = 0;

        // Do whatever work with bodyStr here

    }
}



public class SomeController : Controller
{
    [HttpPost("MyRoute")]
    [EnableBodyRewind]
    public IActionResult SomeAction([FromBody]MyPostModel model )
    {
        // play the body string again
    }
}

Then you can use the body again in the request handler.

In your case if you get a null result, it probably means that the body has already been read at an earlier stage. In that case you may need to use a middleware (see below).

However be careful if you handle large streams, that behavior implies that everything is loaded into memory, this should not be triggered in case of a file upload.

You may want to use this as a Middleware

Mine looks like this (again, if you download/upload large files, this should be disabled to avoid memory issues):

public sealed class BodyRewindMiddleware
{
    private readonly RequestDelegate _next;

    public BodyRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try { context.Request.EnableRewind(); } catch { }
        await _next(context);
        // context.Request.Body.Dipose() might be added to release memory, not tested
    }
}
public static class BodyRewindExtensions
{
    public static IApplicationBuilder EnableRequestBodyRewind(this IApplicationBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        return app.UseMiddleware<BodyRewindMiddleware>();
    }

}

A clearer solution, works in ASP.Net Core 2.1 / 3.1

Filter class

using Microsoft.AspNetCore.Authorization;
// For ASP.NET 2.1
using Microsoft.AspNetCore.Http.Internal;
// For ASP.NET 3.1
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;

public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // For ASP.NET 2.1
        // context.HttpContext.Request.EnableRewind();
        // For ASP.NET 3.1
        // context.HttpContext.Request.EnableBuffering();
    }
}

In an Controller

[HttpPost]
[ReadableBodyStream]
public string SomePostMethod()
{
    //Note: if you're late and body has already been read, you may need this next line
    //Note2: if "Note" is true and Body was read using StreamReader too, then it may be necessary to set "leaveOpen: true" for that stream.
    HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);

    using (StreamReader stream = new StreamReader(HttpContext.Request.Body))
    {
        string body = stream.ReadToEnd();
        // body = "param=somevalue&param2=someothervalue"
    }
}

A quick way to add response buffering in .NET Core 3.1 is

    app.Use((context, next) =>
    {
        context.Request.EnableBuffering();
        return next();
    });

in Startup.cs. I found this also guarantees that buffering will be enabled before the stream has been read, which was a problem for .Net Core 3.1 with some of the other middleware/authorization filter answers I've seen.

Then you can read your request body via HttpContext.Request.Body in your handler as several others have suggested.

Also worth considering is that EnableBuffering has overloads that allow you to limit how much it will buffer in memory before it uses a temporary file, and also an overall limit to you buffer. NB if a request exceeds this limit an exception will be thrown and the request will never reach your handler.


To be able to rewind the request body, @Jean's answer helped me come up with a solution that seems to work well. I currently use this for Global Exception Handler Middleware but the principle is the same.

I created a middleware that basically enables the rewind on the request body (instead of a decorator).

using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableRewind();
        await _next(context);
    }
}

public static class EnableRequestRewindExtension
{
    public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<EnableRequestRewindMiddleware>();
    }
}

This can then be used in your Startup.cs like so:

[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    [...]
    app.UseEnableRequestRewind();
    [...]
}

Using this approach, I have been able to rewind the request body stream successfully.


Recently I came across a very elegant solution that take in random JSON that you have no idea the structure:

    [HttpPost]
    public JsonResult Test([FromBody] JsonElement json)
    {
        return Json(json);
    }

Just that easy.


This is a bit of an old thread, but since I got here, I figured I'd post my findings so that they might help others.

First, I had the same issue, where I wanted to get the Request.Body and do something with that (logging/auditing). But otherwise I wanted the endpoint to look the same.

So, it seemed like the EnableBuffering() call might do the trick. Then you can do a Seek(0,xxx) on the body and re-read the contents, etc.

However, this led to my next issue. I'd get "Synchornous operations are disallowed" exceptions when accessing the endpoint. So, the workaround there is to set the property AllowSynchronousIO = true, in the options. There are a number of ways to do accomplish this (but not important to detail here..)

THEN, the next issue is that when I go to read the Request.Body it has already been disposed. Ugh. So, what gives?

I am using the Newtonsoft.JSON as my [FromBody] parser in the endpiont call. That is what is responsible for the synchronous reads and it also closes the stream when it's done. Solution? Read the stream before it get's to the JSON parsing? Sure, that works and I ended up with this:

 /// <summary>
/// quick and dirty middleware that enables buffering the request body
/// </summary>
/// <remarks>
/// this allows us to re-read the request body's inputstream so that we can capture the original request as is
/// </remarks>
public class ReadRequestBodyIntoItemsAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (context == null) return;

        // NEW! enable sync IO beacuse the JSON reader apparently doesn't use async and it throws an exception otherwise
        var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
        if (syncIOFeature != null)
        {
            syncIOFeature.AllowSynchronousIO = true;

            var req = context.HttpContext.Request;

            req.EnableBuffering();

            // read the body here as a workarond for the JSON parser disposing the stream
            if (req.Body.CanSeek)
            {
                req.Body.Seek(0, SeekOrigin.Begin);

                // if body (stream) can seek, we can read the body to a string for logging purposes
                using (var reader = new StreamReader(
                     req.Body,
                     encoding: Encoding.UTF8,
                     detectEncodingFromByteOrderMarks: false,
                     bufferSize: 8192,
                     leaveOpen: true))
                {
                    var jsonString = reader.ReadToEnd();

                    // store into the HTTP context Items["request_body"]
                    context.HttpContext.Items.Add("request_body", jsonString);
                }

                // go back to beginning so json reader get's the whole thing
                req.Body.Seek(0, SeekOrigin.Begin);
            }
        }
    }
}

So now, I can access the body using the HttpContext.Items["request_body"] in the endpoints that have the [ReadRequestBodyIntoItems] attribute.

But man, this seems like way too many hoops to jump through. So here's where I ended, and I'm really happy with it.

My endpoint started as something like:

[HttpPost("")]
[ReadRequestBodyIntoItems]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value)
{
    val bodyString = HttpContext.Items["request_body"];
    // use the body, process the stuff...
}

But it is much more straightforward to just change the signature, like so:

[HttpPost("")]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData()
{
    using (var reader = new StreamReader(
           Request.Body,
           encoding: Encoding.UTF8,
           detectEncodingFromByteOrderMarks: false
    ))
    {
        var bodyString = await reader.ReadToEndAsync();

        var value = JsonConvert.DeserializeObject<MyJsonObjectType>(bodyString);

        // use the body, process the stuff...
    }
}

I really liked this because it only reads the body stream once, and I have have control of the deserialization. Sure, it's nice if ASP.NET core does this magic for me, but here I don't waste time reading the stream twice (perhaps buffering each time), and the code is quite clear and clean.

If you need this functionality on lots of endpoints, perhaps the middleware approaches might be cleaner, or you can at least encapsulate the body extraction into an extension function to make the code more concise.

Anyways, I did not find any source that touched on all 3 aspects of this issue, hence this post. Hopefully this helps someone!

BTW: This was using ASP .NET Core 3.1.


for read of Body , you can to read asynchronously.

use the async method like follow:

public async Task<IActionResult> GetBody()
{
      string body="";
      using (StreamReader stream = new StreamReader(Request.Body))
      {
           body = await stream.ReadToEndAsync();
      }
    return Json(body);
}

Test with postman:

enter image description here

It's working well and tested in Asp.net core version 2.0 , 2.1 , 2.2, 3.0.

I hope is useful.