首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何读取ASP.NET核心Response.Body?

如何读取ASP.NET核心Response.Body?
EN

Stack Overflow用户
提问于 2017-04-14 01:18:07
回答 6查看 91.5K关注 0票数 107

我一直在努力从Response.Body核心操作中获得ASP.NET属性,而我所能识别的唯一解决方案似乎不太理想。该解决方案需要在将流读入字符串变量时使用Response.Body交换MemoryStream,然后在发送到客户端之前将其交换回来。在下面的示例中,我试图在自定义中间件类中获取Response.Body值。由于某种原因,Response.Body是ASP.NET核心中的唯一属性?我只是在这里遗漏了什么,还是这是一个疏忽/错误/设计问题?有没有更好的阅读Response.Body的方法?

电流(次优)解:

代码语言:javascript
运行
复制
public class MyMiddleWare
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        using (var swapStream = new MemoryStream())
        {
            var originalResponseBody = context.Response.Body;

            context.Response.Body = swapStream;

            await _next(context);

            swapStream.Seek(0, SeekOrigin.Begin);
            string responseBody = new StreamReader(swapStream).ReadToEnd();
            swapStream.Seek(0, SeekOrigin.Begin);

            await swapStream.CopyToAsync(originalResponseBody);
            context.Response.Body = originalResponseBody;
        }
    }
}  

尝试使用EnableRewind()解决方案:--这只适用于Request.Body,而不是Response.Body。这将导致从Response.Body读取空字符串,而不是读取实际的响应正文内容。

Startup.cs

代码语言:javascript
运行
复制
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifeTime)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.Use(async (context, next) => {
        context.Request.EnableRewind();
        await next();
    });

    app.UseMyMiddleWare();

    app.UseMvc();

    // Dispose of Autofac container on application stop
    appLifeTime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}

MyMiddleWare.cs

代码语言:javascript
运行
复制
public class MyMiddleWare
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        await _next(context);
        string responseBody = new StreamReader(context.Request.Body).ReadToEnd(); //responseBody is ""
        context.Request.Body.Position = 0;
    }
}  
EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2017-04-14 03:23:08

在我最初的回答中,我完全误解了这个问题,认为海报是在问如何阅读Request.Body,但他却问了如何阅读Response.Body。我留下我原来的答案,以保存历史,但也更新它,以显示我将如何回答问题,一旦正确阅读它。

原始答案

如果您想要一个支持多次读取的缓冲流,则需要设置

代码语言:javascript
运行
复制
   context.Request.EnableRewind()

理想情况下,在需要读取主体之前,在中间件中尽早完成此操作。

因此,例如,您可以在Configure文件的Startup.cs方法的开头放置以下代码:

代码语言:javascript
运行
复制
        app.Use(async (context, next) => {
            context.Request.EnableRewind();
            await next();
        });

在启用倒带之前,与Request.Body关联的流是一个只转发的流,它不支持第二次查找或读取流。这样做是为了使请求处理的默认配置尽可能轻量级和性能良好。但是,一旦启用了倒带,该流将升级到支持多次查找和读取的流。您可以通过在调用EnableRewind之前和之后设置一个断点并观察Request.Body属性来观察这种“升级”。因此,例如,Request.Body.CanSeek将从false更改为true

更新:从ASP.NET Core2.1开始,Request.EnableBuffering()是可用的,它可以像Request.EnableRewind()一样将Request.Body升级为FileBufferingReadStream,而且由于Request.EnableBuffering()位于公共名称空间而不是内部名称空间中,所以它应该比EnableRewind()更好。(感谢@ArjanEinbu指出)

然后,要读取身体流,可以这样做:

代码语言:javascript
运行
复制
   string bodyContent = new StreamReader(Request.Body).ReadToEnd();

但是,不要将StreamReader创建封装在using语句中,否则它将在using块结束时关闭底层的body流,而请求生命周期后期的代码将无法读取主体。

同样,为了安全起见,最好遵循上面的代码行,用这一行代码读取主体内容,将主体的流位置重新设置为0。

代码语言:javascript
运行
复制
request.Body.Position = 0;

这样,请求生命周期后面的任何代码都会发现request.Body处于一个状态,就像它还没有被读取一样。

最新答案

对不起,我一开始误解了你的问题。将关联流升级为缓冲流的概念仍然适用。但是,您确实必须手动完成该操作,我不知道.Net核心功能中有任何内置功能,这些功能允许您读取响应流,其方式是EnableRewind()允许开发人员在读取请求流之后重新读取请求流。

你的“讨厌”方式很可能是完全合适的。你基本上是在把一个无法寻求的流转换成一个可以。在一天结束时,Response.Body流必须用一个缓冲并支持查找的流交换掉。下面是中间件的另一个处理方法,但是您会注意到它与您的方法非常相似。但是,我确实选择使用一个finally块作为添加的保护,以便将原始流返回到Response.Body上,并且我使用了流的Position属性,而不是Seek方法,因为语法稍微简单一些,但是效果与您的方法没有什么不同。

代码语言:javascript
运行
复制
public class ResponseRewindMiddleware 
{
        private readonly RequestDelegate next;

        public ResponseRewindMiddleware(RequestDelegate next) {
            this.next = next;
        }

        public async Task Invoke(HttpContext context) {

            Stream originalBody = context.Response.Body;

            try {
                using (var memStream = new MemoryStream()) {
                    context.Response.Body = memStream;

                    await next(context);

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

                    memStream.Position = 0;
                    await memStream.CopyToAsync(originalBody);
                }

            } finally {
                context.Response.Body = originalBody;
            }

        } 
}
票数 120
EN

Stack Overflow用户

发布于 2018-09-14 08:42:45

您可以在请求管道中使用中间件,以便记录请求和响应。

但是,由于以下因素,增加了memory leak的危险性: 1.流,2.设置字节缓冲区和3.字符串转换。

可以以大对象堆结束(如果请求或响应的主体大于85,000字节)。这增加了应用程序中内存泄漏的风险。为了避免LOH,可以使用相关的可回收内存流替换内存流。

使用可回收内存流的实现:

代码语言:javascript
运行
复制
public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
    private const int ReadChunkBufferLength = 4096;

    public RequestResponseLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
    {
        _next = next;
        _logger = loggerFactory
            .CreateLogger<RequestResponseLoggingMiddleware>();
        _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
    }

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

    private void LogRequest(HttpRequest request)
    {
        request.EnableRewind();
        using (var requestStream = _recyclableMemoryStreamManager.GetStream())
        {
            request.Body.CopyTo(requestStream);
            _logger.LogInformation($"Http Request Information:{Environment.NewLine}" +
                                   $"Schema:{request.Scheme} " +
                                   $"Host: {request.Host} " +
                                   $"Path: {request.Path} " +
                                   $"QueryString: {request.QueryString} " +
                                   $"Request Body: {ReadStreamInChunks(requestStream)}");
        }
    }

    private async Task LogResponseAsync(HttpContext context)
    {
        var originalBody = context.Response.Body;
        using (var responseStream = _recyclableMemoryStreamManager.GetStream())
        {
            context.Response.Body = responseStream;
            await _next.Invoke(context);
            await responseStream.CopyToAsync(originalBody);
            _logger.LogInformation($"Http Response Information:{Environment.NewLine}" +
                                   $"Schema:{context.Request.Scheme} " +
                                   $"Host: {context.Request.Host} " +
                                   $"Path: {context.Request.Path} " +
                                   $"QueryString: {context.Request.QueryString} " +
                                   $"Response Body: {ReadStreamInChunks(responseStream)}");
        }

        context.Response.Body = originalBody;
    }

    private static string ReadStreamInChunks(Stream stream)
    {
        stream.Seek(0, SeekOrigin.Begin);
        string result;
        using (var textWriter = new StringWriter())
        using (var reader = new StreamReader(stream))
        {
            var readChunk = new char[ReadChunkBufferLength];
            int readChunkLength;
            //do while: is useful for the last iteration in case readChunkLength < chunkLength
            do
            {
                readChunkLength = reader.ReadBlock(readChunk, 0, ReadChunkBufferLength);
                textWriter.Write(readChunk, 0, readChunkLength);
            } while (readChunkLength > 0);

            result = textWriter.ToString();
        }

        return result;
    }
}

注意:LOH的危险并没有完全消除,因为textWriter.ToString(),另一方面,您可以使用支持结构化日志记录的日志客户端库(即。并注入可回收内存流的实例。

票数 13
EN

Stack Overflow用户

发布于 2021-12-24 23:42:57

.NET 6.0+解

在ASP.NET Core 6.0+中,可以考虑使用内置扩展:

代码语言:javascript
运行
复制
var builder = WebApplication.CreateBuilder(args);
//...
builder.Services.AddHttpLogging(options => // <--- Setup logging
{
    // Specify all that you need here:
    options.LoggingFields = HttpLoggingFields.RequestHeaders |
                            HttpLoggingFields.RequestBody |
                            HttpLoggingFields.ResponseHeaders |
                            HttpLoggingFields.ResponseBody;
});
//...
var app = builder.Build();
//...
app.UseHttpLogging(); // <--- Add logging to pipeline
//...
app.Run();
票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43403941

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档