如何在 ASP.NET Core 中间件中获取传入请求正文和传出响应正文?

Avr*_*oel 4 c# asp.net-core minimal-apis

我正在为 ASP.NET Core 7 最小 API 项目编写一些中间件。

在这个阶段,我只希望能够读取正在发送的请求正文,然后读取正在发送的响应正文,并将它们转储到控制台。

阅读请求似乎很容易。我的中间件包含以下内容...

  public async Task InvokeAsync(HttpContext httpContext) {
    try {
      httpContext.Request.EnableBuffering();
      string requestBody = await new StreamReader(httpContext.Request.Body, Encoding.UTF8).ReadToEndAsync();
      httpContext.Request.Body.Position = 0;
      Console.WriteLine($"Request body: {requestBody}");
    } catch (Exception ex) {
      Console.WriteLine($"Exception reading request: {ex.Message}");
    }

    await _next(httpContext);
  }
Run Code Online (Sandbox Code Playgroud)

这很好用。但是,当我尝试读取响应正文时,出现异常。我尝试插入以下内容(类似于在许多 SO 答案和博客文章中找到的内容)...

    try {
      using StreamReader reader = new(httpContext.Response.Body);
      httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
      string responseBody = await reader.ReadToEndAsync();
      httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
      Console.WriteLine($"Response body: {responseBody}");
    } catch (Exception ex) {
      Console.WriteLine($"Exception reading response: {ex.Message}");
    }
Run Code Online (Sandbox Code Playgroud)

但是,这会产生异常System.ArgumentException:Stream 不可读。

让我困惑的一件事是,根据Microsoft 文档,中间件被调用两次,一次是在请求传入时(在调用特定端点代码之前),第二次是在发送响应时(在端点之后)代码已经完成了它的工作)。我可以看到我的中间件仅在请求传入时被调用,因此即使上面的代码有效,我也不知道它将如何获得响应,因为端点代码尚未生成响应。

我四处搜寻,但没有找到任何地方可以澄清这一点。

任何人都可以建议我做错了什么?谢谢

Gur*_*ron 6

中间件被调用两次,一次是在请求传入时(在调用特定端点代码之前),第二次是在发送响应时

虽然这是可以根据文档中的文章和图片形成的印象,但它并不完全正确,中间件仅被调用一次(管道中的每次注册)。处理管道的更正确表示是俄罗斯嵌套娃娃 - 第一个注册的中间件调用第二个,依此类推。这await _next(httpContext);是对管道中以下中间件的调用,因此如果需要,您可以在其之前和之后执行操作。

你可以尝试这样的事情 - 用你自己控制的响应流替换响应流,然后写回所有内容(我只使用Use注册中间件,但您可以将其调整为具有单独类的方法):

app.Use(async (httpContext, next) =>
{
    try
    {
        httpContext.Request.EnableBuffering();
        string requestBody = await new StreamReader(httpContext.Request.Body, Encoding.UTF8).ReadToEndAsync();
        httpContext.Request.Body.Position = 0;
        Console.WriteLine($"Request body: {requestBody}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception reading request: {ex.Message}");
    }

    Stream originalBody = httpContext.Response.Body;
    try
    {
        using var memStream = new MemoryStream();
        httpContext.Response.Body = memStream;

        // call to the following middleware 
        // response should be produced by one of the following middlewares
        await next(httpContext); 

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

        memStream.Position = 0;
        await memStream.CopyToAsync(originalBody);
        Console.WriteLine(responseBody);
    }
    finally
    {
        httpContext.Response.Body = originalBody;
    }
});
Run Code Online (Sandbox Code Playgroud)

另外,我强烈建议您查看HttpLoggingMiddleware框架提供的HTTP 日志记录支持,它的功能完全相同(也许以更正确/性能更好的方式)。