14

I am having a simple middleware which fetches the body of the request and store it in a string. It is reading fine the stream, but the issue is it wont call my controller which called just after I read the stream and throw the error

A non-empty request body is required

. Below is my code.

  public async Task Invoke(HttpContext httpContext)
            {
                var timer = Stopwatch.StartNew();
                ReadBodyFromHttpContext(httpContext);
                await _next(httpContext);
                timer.Stop();
            }

   private string ReadBodyFromHttpContext(HttpContext httpContext)
        {
           return await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
        }
maxspan
  • 13,326
  • 15
  • 75
  • 104
  • 1
    Stream is already read. once accessed it would need to replace the stream https://stackoverflow.com/a/44500627/5233410 – Nkosi Dec 04 '17 at 01:04

5 Answers5

19

You need to convert HttpContext.Request.Body from a forward only memory stream to a seekable stream, shown below.

//  Enable seeking
context.Request.EnableBuffering();
//  Read the stream as text
var bodyAsText = await new System.IO.StreamReader(context.Request.Body).ReadToEndAsync();
//  Set the position of the stream to 0 to enable rereading
context.Request.Body.Position = 0; 
Jordan Ryder
  • 2,336
  • 1
  • 24
  • 29
mattinsalto
  • 2,146
  • 1
  • 25
  • 27
  • 2
    FYI in .NetCore 3.1 `Request.Body.Position` throws `NotSupportedException` – w00ngy Jan 09 '20 at 18:42
  • @w00ngy I've just tested in net core 3.1 and it work as expected: context.Request.Body.Position = 0; throws no exception – mattinsalto Jan 15 '20 at 12:05
  • 1
    maybe I didn't have EnableBuffering set or something. Disregard. – w00ngy Jan 16 '20 at 02:18
  • The returned string is always "". I can see `request.ContentLength` is 722. But whether I try the above code or try to use a memory stream to retrieve a byte array, it always returns empty. Any ideas why this might be happening? – Ash Jan 17 '23 at 06:26
10

when it comes to capturing the body of an HTTP request and/or response, this is no trivial effort. In ASP .NET Core, the body is a stream – once you consume it (for logging, in this case), it’s gone, rendering the rest of the pipeline useless.

Ref:http://www.palador.com/2017/05/24/logging-the-body-of-http-request-and-response-in-asp-net-core/

public async Task Invoke(HttpContext httpContext)
    {
        var timer = Stopwatch.StartNew();
        string bodyAsText = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
        var injectedRequestStream = new MemoryStream();
        var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText);
        injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
        injectedRequestStream.Seek(0, SeekOrigin.Begin);
        httpContext.Request.Body = injectedRequestStream;
        await _next(httpContext);

        timer.Stop();
    }
maxspan
  • 13,326
  • 15
  • 75
  • 104
6

Few things are crucial here:

  • enable buffering
  • last flag leaveOpen in StreamReader
  • reset request body stream position (SeekOrigin.Begin)
public void UseMyMiddleware(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        context.Request.EnableBuffering();

        using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, false, 1024, true))
        {
            var body = await reader.ReadToEndAsync();

            context.Request.Body.Seek(0, SeekOrigin.Begin);
        }

        await next.Invoke();
    });
}
szubajak
  • 455
  • 7
  • 12
1
using (var mem = new MemoryStream())
            using (var reader = new StreamReader(mem))
            {
                Request.Body.CopyTo(mem);
                var body = reader.ReadToEnd();

//and this you can reset the position of the stream.

                mem.Seek(0, SeekOrigin.Begin);
                body = reader.ReadToEnd();
            }

Here you are can read how it works. https://gunnarpeipman.com/aspnet-core-request-body/

0

You can try this

public async Task Invoke(HttpContext context)
{
  var request = context.Request;
  request.EnableBuffering();
  var buffer = new byte[Convert.ToInt32(request.ContentLength)];
  await request.Body.ReadAsync(buffer, 0, buffer.Length);
  var requestContent = Encoding.UTF8.GetString(buffer);
  request.Body.Position = 0;  //rewinding the stream to 0
}
Tony Riddle
  • 11
  • 2
  • 9