26

I am trying to read the body in a middleware for authentication purposes, but when the request gets to the api controller the object is empty as the body has already been read. Is there anyway around this. I am reading the body like this in my middleware.

var buffer = new byte[ Convert.ToInt32( context.Request.ContentLength ) ];
await context.Request.Body.ReadAsync( buffer, 0, buffer.Length );
var body = Encoding.UTF8.GetString( buffer );
Set
  • 40,147
  • 18
  • 114
  • 133
Jake Rote
  • 1,987
  • 2
  • 13
  • 40
  • 1
    Just curious: why are you reading the body of the request when performing authentication? – Evan Mulawski Jul 13 '15 at 17:35
  • 1
    Its an api where the request for example ApiKey is also required when building the response and also a hash to secure the request. Which don't make sense to be in the headers. – Jake Rote Jul 13 '15 at 17:59
  • An [example](http://stackoverflow.com/questions/42561350/read-json-post-data-in-asp-net-core-mvc/42922579#42922579) of using PinPoint's EnableRewind to read json from body. – Nathan Zaetta Mar 21 '17 at 09:17
  • An [example](http://stackoverflow.com/questions/42561350/read-json-post-data-in-asp-net-core-mvc/42922579#42922579) of using PinPoint's EnableRewind to read json from body – Nathan Zaetta Mar 21 '17 at 09:18

3 Answers3

38

If you're using application/x-www-form-urlencoded or multipart/form-data, you can safely call context.Request.ReadFormAsync() multiple times as it returns a cached instance on subsequent calls.

If you're using a different content type, you'll have to manually buffer the request and replace the request body by a rewindable stream like MemoryStream. Here's how you could do using an inline middleware (you need to register it soon in your pipeline):

app.Use(next => async context =>
{
    // Keep the original stream in a separate
    // variable to restore it later if necessary.
    var stream = context.Request.Body;

    // Optimization: don't buffer the request if
    // there was no stream or if it is rewindable.
    if (stream == Stream.Null || stream.CanSeek)
    {
        await next(context);

        return;
    }

    try
    {
        using (var buffer = new MemoryStream())
        {
            // Copy the request stream to the memory stream.
            await stream.CopyToAsync(buffer);

            // Rewind the memory stream.
            buffer.Position = 0L;

            // Replace the request stream by the memory stream.
            context.Request.Body = buffer;

            // Invoke the rest of the pipeline.
            await next(context);
        }
    }

    finally
    {
        // Restore the original stream.
        context.Request.Body = stream;
    }
});

You can also use the BufferingHelper.EnableRewind() extension, which is part of the Microsoft.AspNet.Http package: it's based on a similar approach but relies on a special stream that starts buffering data in memory and spools everything to a temp file on disk when the threshold is reached:

app.Use(next => context =>
{
    context.Request.EnableRewind();

    return next(context);
});

FYI: a buffering middleware will probably be added to vNext in the future.

Kévin Chalet
  • 33,128
  • 7
  • 104
  • 124
  • 2
    I'm curious about the caching. Is that part of the contract or implementation detail that might change in the future? – Simon Belanger Jul 14 '15 at 04:00
  • @SimonBelanger in the near future, hosts and middleware will probably share a common contract to indicate whether they support buffering or not. You can find more details on this wiki page for more information: https://github.com/aspnet/HttpAbstractions/wiki/Rolling-Notes – Kévin Chalet Jul 14 '15 at 10:35
  • @JakeRote FYI, I just updated my answer to mention another approach using `context.Request.EnableRewind()`. – Kévin Chalet Jul 14 '15 at 10:36
  • @Pinpoint Thanks I'll try both of these when I get home tonight thanks – Jake Rote Jul 14 '15 at 10:53
  • @Pinpoint FYI, I will use your for now though as they have not finished EnableRewind() so the temp file will not be deleted until GC kills the FileStream, – Jake Rote Jul 14 '15 at 11:11
  • It might help to add a couple of links to explain what Jake is saying about "they have not finished EnableRewind". https://github.com/aspnet/HttpAbstractions/issues/203 and https://github.com/aspnet/HttpAbstractions/blob/8ef20c5c5677220dcf7d4e4b6ac14f764d89c92d/src/Microsoft.AspNet.Http/BufferingHelper.cs – Nate Cook Dec 11 '15 at 21:08
  • Tried `EnableRewind` – Andrii Jul 20 '16 at 22:32
  • Get `ObjectDisposedException: Cannot access a disposed object. Object name: 'FileBufferingReadStream'.` – Andrii Jul 20 '16 at 22:32
  • Also in .Net Core 2.1 is necessary to add Request.Body.Position = 0 before read or you are going to get "" (empty) – cflorenciav Oct 15 '18 at 20:47
  • 1
    In .Net Core 3.0 context.Request.EnableRewind() should be changed to context.Request.EnableBuffering() – MikhailSP Nov 27 '19 at 12:02
9

Usage for PinPoint's mention of EnableRewind

Startup.cs
using Microsoft.AspNetCore.Http.Internal;

Startup.Configure(...){
...
//Its important the rewind us added before UseMvc
app.Use(next => context => { context.Request.EnableRewind(); return next(context); });
app.UseMvc()
...
}

Then in your middleware you just rewind and reread

private async Task GenerateToken(HttpContext context)
    {
     context.Request.EnableRewind();
     string jsonData = new StreamReader(context.Request.Body).ReadToEnd();
    ...
    }
Nathan Zaetta
  • 383
  • 3
  • 7
4

This works with .Net Core 2.1 and higher.

Today I run in a similar issue. Long story short, what used to work with

Body.Seek(0, SeekOrigin.Begin);

resulted in today in exception, at least in my case. This happened after the code was migrated to the latest version of .NET Core.

The workaround for me was to add this:

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

Add this before setting up controllers or MVC. This seems to be added as part of the .NET Core 2.1 version.

Hope this helps someone!

Cheers and happy coding.

DasSoftware
  • 886
  • 10
  • 17