C# – How to read request body in an asp.net core webapi controller

asp.net-coreasp.net-web-apic

I'm trying to read the request body in the OnActionExecuting method, but I always get null for the body.

var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();

I have tried to explicitly set the stream position to 0, but that also didn't work. Since this is ASP.NET Core, things are a little different I think. I can see all the samples here referring to old web API versions.

Is there any other way of doing this?

Best Answer

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>();
    }

}