如何在 ASP.NET Core 1.0 中记录 HTTP 响应主体

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

我正在使用 ASP.NET Core 1.0 RC2 创建 public REST Api,并且喜欢记录传入请求和传出响应。

我创建了一个中间件 class,它在调用 app.UseMvc();

之前添加到管道中
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{            
        app.UseIOMiddleware();
        app.UseMvc();            
}

我的中间件 class 看起来像这样:

public class IOMiddleware
{
    private readonly RequestDelegate _next;

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

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

        await _next.Invoke(context);            
    }

    private async void 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);
        }
    }
}

我可以读取请求正文流并使用此示例倒回它:Rewind request body stream,但我不确定如何读取响应正文,因为该流不可读。

在 Web API 2.0 中我可以使用 HttpResponseMessage.Content.ReadAsByteArrayAsync() 方法,但是我如何在 ASP.Net Core 1.0 RC2 中完成同样的事情?

问题是 request.Body 不可读,只能写 - 通常流会通过网络定期刷新到客户端。

您可以通过替换流并缓冲内容直到管道的其余部分完成来解决这个问题。

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}");

            }
        }
    }
}

不幸的是,如果您将 Request 替换为 MemoryStream,则相同的流将用于以后的调用。 这是错误: https://github.com/aspnet/KestrelHttpServer/issues/940

解决方法是将 Request.Body 流复制到局部变量,最后将 Body 设置回原始流。

像这样:

  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;
    }

到处搜索后,这就是我最终得到的 class。它对我来说工作正常并处理出现异常的情况[它过去没有 return 任何响应但成功记录它!!]。这是网上这么多帖子的集体产物。

using Microsoft.AspNetCore.Http;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNetCore.Http.Internal;



public class LoggerMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        using (MemoryStream requestBodyStream = new MemoryStream())
        using (MemoryStream responseBodyStream = new MemoryStream())
        {
            Stream originalRequestBody = context.Request.Body;
            context.Request.EnableRewind();
            Stream originalResponseBody = context.Response.Body;

            try
            {
                await context.Request.Body.CopyToAsync(requestBodyStream);
                requestBodyStream.Seek(0, SeekOrigin.Begin);

                string requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();

                requestBodyStream.Seek(0, SeekOrigin.Begin);
                context.Request.Body = requestBodyStream;

                string responseBody = "";

                context.Response.Body = responseBodyStream;

                Stopwatch watch = Stopwatch.StartNew();
                    await _next(context);
                    watch.Stop();

                responseBodyStream.Seek(0, SeekOrigin.Begin);
                responseBody = new StreamReader(responseBodyStream).ReadToEnd();
                AuditLogger.LogToAudit(context.Request.Host.Host,
                    context.Request.Path, context.Request.QueryString.ToString(), context.Connection.RemoteIpAddress.MapToIPv4().ToString(),
                        string.Join(",", context.Request.Headers.Select(he => he.Key + ":[" + he.Value + "]").ToList()),
                        requestBodyText, responseBody, DateTime.Now, watch.ElapsedMilliseconds);

                responseBodyStream.Seek(0, SeekOrigin.Begin);

                await responseBodyStream.CopyToAsync(originalResponseBody);
            }
            catch (Exception ex)
            {
                ExceptionLogger.LogToDatabse(ex);
                byte[] data = System.Text.Encoding.UTF8.GetBytes("Unhandled Error occured. Please, try again in a while.");
                originalResponseBody.Write(data, 0, data.Length);
            }
            finally
            {
                context.Request.Body = originalRequestBody;
                context.Response.Body = originalResponseBody;
            }
        }
    }
}