0

I am trying to log the response body of an HTTP request in a .NET 7 application, but I keep getting a System.NotSupportedException when I try to read from the response body stream. Here is the code I am using:

using System.Diagnostics;

namespace WebApi.Middlewares;

public class RequestLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public RequestLoggerMiddleware(RequestDelegate next, ILogger<RequestLoggerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        var requestBodyStream = new MemoryStream();
        var originalRequestBody = context.Request.Body;

        try
        {
            await context.Request.Body.CopyToAsync(requestBodyStream);
            requestBodyStream.Seek(0, SeekOrigin.Begin);
            var requestBodyText = await new StreamReader(requestBodyStream).ReadToEndAsync();
            context.Request.Body = originalRequestBody;

            _logger.LogInformation("Incoming request {Method} {Path} {Query} {RequestBody}", context.Request.Method, context.Request.Path, context.Request.QueryString, requestBodyText);

            await _next(context);

            sw.Stop();
            var responseTime = sw.Elapsed.TotalMilliseconds;

            var originalResponseBody = context.Response.Body;

            using var responseBodyStream = new MemoryStream();
            await context.Response.Body.CopyToAsync(responseBodyStream);
            
            // I get the error at this line
            responseBodyStream.Seek(0, SeekOrigin.Begin);

            var responseBodyReader = new StreamReader(responseBodyStream);
            var responseBodyText = await responseBodyReader.ReadToEndAsync();

            _logger.LogInformation("Outgoing response {StatusCode} {Method} {Path} {Query} {ResponseBody} {ResponseTime:0.000}ms",
                context.Response.StatusCode,
                context.Request.Method,
                context.Request.Path,
                context.Request.QueryString,
                responseBodyText,
                responseTime);
        }

        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing request {Method} {Path} {Query}", context.Request.Method, context.Request.Path, context.Request.QueryString);
            throw;
        }
    }
}

I have also tried using a BufferedStream to read the response body, as well as creating a new MemoryStream to hold a copy of the response body, but I still get the same exception.

What could be causing this System.NotSupportedException, and how can I properly read and log the response body in my .NET 7 application? Any help would be greatly appreciated.

  • https://learn.microsoft.com/en-us/dotnet/api/system.io.stream.seek?view=net-8.0 – Chetan Apr 28 '23 at 01:49
  • 1
    @Chetan The error isn't coming from the seek, it's coming from the `CopyToAsync` before it. And even then it wouldn't make sense because you can always seek a `MemoryStream`. – Etienne de Martel Apr 28 '23 at 03:13
  • 1
    Does this answer your question? [How to read ASP.NET Core Response.Body?](https://stackoverflow.com/questions/43403941/how-to-read-asp-net-core-response-body) – Etienne de Martel Apr 28 '23 at 03:20
  • I both needed to log the Request and the Response in the scope. Whenever I try to get only one like Request or Response, it works fine. I think there is this error while accessing the stream position before calling await _next(context); or after but still didn't get the point. – Muharrem Servet ANKARALI Apr 28 '23 at 08:14
  • HTTP logging is a supported feature of ASP.NET Core. See [HTTP Logging in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging/?view=aspnetcore-7.0) If that doesn't work for you maybe check out their implementation? [HttpLoggingMiddleware source code](https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HttpLogging/src/HttpLoggingMiddleware.cs) – wanton Apr 28 '23 at 09:19

1 Answers1

0

I just faced the same issue recently and it happens because not all request body are seekable: CanSeek false

A solution for that is to use EnableBuffering, to make the request body be readable multiple times. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequestrewindextensions.enablebuffering?view=aspnetcore-7.0

You can take advantage of custom middleware and create your own middleware to enable buffering for all requests.

public class EnableRequestBodyBufferingMiddleware
{
    private readonly RequestDelegate _next;

    public EnableRequestBodyBufferingMiddleware(RequestDelegate next) =>
        _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        context.Request.EnableBuffering();

        await _next(context);
    }
}

and then on you Startup.cs file:

app.UseMiddleware<EnableRequestBodyBufferingMiddleware>();

if you want only to enable buffering for some specific requests:

app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/home/withmodelbinding"),
    ab => ab.UseMiddleware<EnableRequestBodyBufferingMiddleware>()
);

After that you can notice that attribute canSeek is true:

CanSeek true

Reference: https://markb.uk/asp-net-core-read-raw-request-body-as-string.html