System.NotSupportedException when trying to read response body in .NET 7

742 Views Asked by At

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.

1

There are 1 best solutions below

0
Leandro Candido On

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