How to log the HTTP Response Body in ASP.NET Core 1.0

Unfortunately if you replace Request with MemoryStream, the same stream will be used for future calls. Here is the bug: https://github.com/aspnet/KestrelHttpServer/issues/940

The workaround is to copy Request.Body stream to local variable and set Body back to original stream in the end.

Like this:

  public async Task Invoke(HttpContext context)
    {
        //Workaround - copy original Stream
        var initalBody = context.Request.Body;

        using (var bodyReader = new StreamReader(request.Body))
        {
            string body = await bodyReader.ReadToEndAsync();
            //Do something with body
            //Replace write only request body with read/write memorystream so you can read from it later

               request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));

        //handle other middlewares
        await _next.Invoke(context);

        //Workaround - return back to original Stream
        context.Request.Body = initalBody;
    }

The problem is that request.Body is not readable, only writable - typically the stream will periodically flushed to the client across the wire.

You can get round this by replacing the stream and buffering the content until the rest of the pipeline has completed.

public class IOMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        await LogRequest(context.Request);

        await LogResponseAndInvokeNext(context);
    }

    private async Task LogRequest(HttpRequest request)
    {
        using (var bodyReader = new StreamReader(request.Body))
        {
            string body = await bodyReader.ReadToEndAsync();

            request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
            System.Diagnostics.Debug.Print(body);
        }
    }

    private async Task LogResponseAndInvokeNext(HttpContext context)
    {
        using (var buffer = new MemoryStream())
        {
            //replace the context response with our buffer
            var stream = context.Response.Body;
            context.Response.Body = buffer;

            //invoke the rest of the pipeline
            await _next.Invoke(context);

            //reset the buffer and read out the contents
            buffer.Seek(0, SeekOrigin.Begin);
            var reader = new StreamReader(buffer);
            using (var bufferReader = new StreamReader(buffer))
            {
                string body = await bufferReader.ReadToEndAsync();

                //reset to start of stream
                buffer.Seek(0, SeekOrigin.Begin);

                //copy our content to the original stream and put it back
                await buffer.CopyToAsync(stream);
                context.Response.Body = stream;

                System.Diagnostics.Debug.Print($"Response: {body}");

            }
        }
    }
}