1

We are trying to create a middleware that allows you to log in to an external service the body of an HTTP response.

We tried replacing it with a MemoryStream, but when we try to read it it turns out to be closed.

Could anyone help us?

Thanks in advance

Here is the code:

public class LogMiddleware 
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context) 
    {
        var originBody = context.Response.Body;
        try 
        {
            var memStream = new MemoryStream();
            context.Response.Body = memStream;

            await _next(context).ConfigureAwait(false);

            memStream.Position = 0;
            var responseBody = new StreamReader(memStream).ReadToEnd();

            var memoryStreamModified = new MemoryStream();
            var sw = new StreamWriter(memoryStreamModified);
            sw.Write(responseBody);
            sw.Flush();
            memoryStreamModified.Position = 0;

            await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
        } 
        finally 
        {
            context.Response.Body = originBody;
        }
    }
}
spaleet
  • 838
  • 2
  • 10
  • 23
w4rcT
  • 180
  • 1
  • 13
  • Can you show us what you did so far? – gsharp May 17 '22 at 19:13
  • Pop some code up. – Neil W May 17 '22 at 19:15
  • @w4rcT Can you please elaborate where in this flow you are actually performing logging out/sending the data? Is there a reason the response itself needs to be modified as part of this? As posted it looks like a bit of a shell game of swapping out streams that don't go anywhere. – Adam May 19 '22 at 14:59
  • @Adam we need to get request and response body to perform logging. While for the request is all ok, for the response the stream seems to be closed and unreadable and we cannot get the body. In the pipeline this is the first middleware – w4rcT May 20 '22 at 04:14
  • 2
    Or try a library already doing all that, like [Audit.WebApi](https://github.com/thepirat000/Audit.NET/tree/master/src/Audit.WebApi#readme) – thepirat000 May 20 '22 at 19:51

1 Answers1

5

There is a lot of good discussion on this in the following StackOverflow question: How to read ASP.NET Core Response.Body?

Because this current question calls out.NET 6, I'd specifically direct attention to this answer, which cuts through a lot of previous workarounds: https://stackoverflow.com/a/70477179/13374279

Summing that up, managing the request and response streams is a non-trivial challenge for a variety of reasons outlined in that question (buffers, conversions, leaks, data size). Depending on the level of control you need to exert over the logging parameters, you may find it beneficial just to use the built-in HttpLoggingMiddleware.

It can be registered with:

builder.Services.AddHttpLogging(opts => 
{
    opts .LoggingFields = HttpLoggingFields.ResponseBody;
});

and then added to the pipeline with app.UseHttpLogging(), at which point you could hook into the ASP NET Core ILogger to pick up the response body as part of your logging implementation:

https://source.dot.net/#Microsoft.AspNetCore.HttpLogging/HttpLoggingExtensions.cs,27

Thanks to open source, you can dig into how Microsoft implemented that middleware here if you need to build something more robust or with deeper hooks: https://source.dot.net/#Microsoft.AspNetCore.HttpLogging/HttpLoggingMiddleware.cs,35c5841599b94285

The key element is the use of the IHttpResponseBodyFeature implementation, which is not heavily documented but is the abstraction point for hooking into that part of the request/response pipeline since 3.x. Rick Strahl has a post on it in his blog that shows some of the depth of implementation required there:

https://weblog.west-wind.com/posts/2020/Mar/29/Content-Injection-with-Response-Rewriting-in-ASPNET-Core-3x

Adam
  • 3,339
  • 1
  • 10
  • 15