一尘不染

如何阅读ASP.NET Core Response.Body?

c#

我一直在努力Response.Body从ASP.NET
Core动作中获取该属性,而我能够确定的唯一解决方案似乎不是最优的。该解决方案需要交换Response.Body一段MemoryStream时间,同时将流读取为字符串变量,然后将其交换回去,然后再发送给客户端。在下面的示例中,我试图Response.Body在自定义中间件类中获取值。
Response.Body是出于某种原因在ASP.NET Core中仅 设置了
一个属性?我只是在这里缺少什么,还是这是一个监督/错误/设计问题?有更好的阅读方法Response.Body吗?

当前(次优)解决方案:

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不是实际的响应正文内容中读取空字符串。

启动文件

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

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

阅读 888

收藏
2020-05-19

共1个答案

一尘不染

在我最初的答复中,我完全误解了这个问题,并以为张贴人在问如何阅读Request.Body文章Response.Body。我将保留原来的答案,以保留历史记录,但也将对其进行更新,以显示正确阅读后如何回答该问题。

原始答案

如果要缓冲流支持多次读取,则需要设置

   context.Request.EnableRewind()

理想情况下,在任何需要读取正文的中间件中尽早进行此操作。

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

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

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

更新 :从ASP.NET Core
2.1开始,Request.EnableBuffering()可以将其升级Request.BodyFileBufferingReadStream类似的名称,Request.EnableRewind()并且由于Request.EnableBuffering()它位于公共名称空间而不是内部名称空间中,因此它应优先于EnableRewind()使用。(感谢@ArjanEinbu指出)

然后,例如,要读取主体流,可以执行以下操作:

   string bodyContent = new StreamReader(Request.Body).ReadToEnd();

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

同样为了安全起见,遵循上述读取正文内容的代码行以及此代码行以将正文的流位置重置回0可能是个好主意。

request.Body.Position = 0;

这样,请求生命周期中稍后的任何代码都可以找到请求.Body处于尚未被读取的状态。

更新的答案

抱歉,我原本误读了您的问题。将关联流升级为缓冲流的概念仍然适用。但是,您必须手动执行此操作,但我不知道任何内置的.Net
Core功能,该功能使您可以在编写EnableRewind()请求后立即读取响应流,从而使开发人员在读取请求流后重新读取该请求流。

您的“
hacky”方法可能完全合适。您基本上是在将无法寻找的流转换为可以寻找的流。最终,必须将Response.Body流与已缓冲并支持查找的流换出。这是中间件的另一种实现方法,但是您会注意到它与您的方法非常相似。但是,我确实选择使用finally块作为添加保护,以将原始流放回上,Response.Body并且我使用Position了流的属性而不是Seek方法,因为语法稍微简单一些,但效果与您的方法没有什么不同。

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

        }
2020-05-19