如何阅读 ASP.NET Core Response.Body? [英] How to read ASP.NET Core Response.Body?

查看:32
本文介绍了如何阅读 ASP.NET Core Response.Body?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been struggling to get the Response.Body property from an ASP.NET Core action and the only solution I've been able to identify seems sub-optimal. The solution requires swapping out Response.Body with a MemoryStream while reading the stream into a string variable, then swapping it back before sending to the client. In the examples below, I'm trying to get the Response.Body value in a custom middleware class. Response.Body is a set only property in ASP.NET Core for some reason? Am I just missing something here, or is this an oversight/bug/design issue? Is there a better way to read 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 读取一个空字符串,而不是实际的响应正文内容.

Attempted solution using EnableRewind(): This only works for Request.Body, not Response.Body. This results in reading an empty string from Response.Body rather than the actual response body contents.

Startup.cs

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

推荐答案

在我最初的回复中,我完全误读了这个问题,并认为海报是在询问如何阅读 Request.Body 但他有询问如何读取 Response.Body.我将保留我的原始答案以保留历史,但也会更新它以显示正确阅读后我将如何回答问题.

In my original response I had totally misread the question and thought the poster was asking how to read the Request.Body But he had asked how to read the Response.Body. I'm leaving my original answer to preserve history but also updating it to show how I would answer the question once reading it correctly.

原始答案

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

If you want a buffered stream that supports reading multiple times you need to set

   context.Request.EnableRewind()

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

Ideally do this early in the middleware before anything needs to read the body.

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

So for example you could place the following code in the beginning of the Configure method of the Startup.cs file:

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

在启用回退之前,与 Request.Body 关联的流是一个仅向前的流,不支持第二次查找或读取流.这样做是为了使请求处理的默认配置尽可能轻量级和高性能.但是一旦启用倒带,流就会升级为支持多次查找和读取的流.你可以观察这个升级"通过在调用 EnableRewind 之前和之后设置断点并观察 Request.Body 属性.例如,Request.Body.CanSeek 将从 false 变为 true.

Prior to enabling Rewind the stream associated with the Request.Body is a forward only stream that doesn't support seeking or reading the stream a second time. This was done to make the default configuration of request handling as lightweight and performant as possible. But once you enable rewind the stream is upgrade to a stream that supports seeking and reading multiple times. You can observe this "upgrade" by setting a breakpoint just before and just after the call to EnableRewind and observing the Request.Body properties. So for example Request.Body.CanSeek will change from false to true.

update:从 ASP.NET Core 2.1 开始 Request.EnableBuffering() 可用,它将 Request.Body 升级为 FileBufferingReadStream 就像 Request.EnableRewind() 并且由于 Request.EnableBuffering() 位于公共命名空间而不是内部命名空间中,因此应该优先于 EnableRewind().(感谢@ArjanEinbu 指出)

update: Starting in ASP.NET Core 2.1 Request.EnableBuffering() is available which upgrades the Request.Body to a FileBufferingReadStream just like Request.EnableRewind() and since Request.EnableBuffering() is in a public namespace rather than an internal one it should be preferred over EnableRewind(). (Thanks to @ArjanEinbu for pointing out)

然后要读取正文流,您可以执行以下操作:

Then to read the body stream you could for example do this:

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

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

Don't wrap the StreamReader creation in a using statement though or it will close the underlying body stream at the conclusion of the using block and code later in the request lifecycle wont be able to read the body.

同样为了安全起见,按照上面的代码行读取正文内容与这行代码以将正文的流位置重置回 0 可能是个好主意.

Also just to be safe, it might be a good idea to follow the above line of code that reads the body content with this line of code to reset the body's stream position back to 0.

request.Body.Position = 0;

这样,请求生命周期后期的任何代码都会发现 request.Body 处于一种尚未被读取的状态.

That way any code later in the request lifecycle will find the request.Body in a state just like it hasn't been read yet.

更新答案

抱歉,我最初误读了您的问题.将关联流升级为缓冲流的概念仍然适用.但是,您必须手动执行此操作,我不知道有任何内置的 .Net Core 功能可让您在以 EnableRewind() 允许开发人员重新读取请求的方式编写后读取响应流读取后流式传输.

Sorry I originally misread your question. The concept of upgrading the associated stream to be a buffered stream still applies. However you do have to do it manually, I'm unaware of any built in .Net Core functionality that lets you read the response stream once written in the way that EnableRewind() lets a developer reread the request stream after it's been read.

你的hacky"方法可能是完全合适的.您基本上是将无法搜索的流转换为可以搜索的流.在一天结束时,Response.Body 流必须被一个缓冲并支持搜索的流换出.这是另一种中间件来做到这一点,但您会注意到它与您的方法非常相似.然而,我确实选择使用 finally 块作为将原始流放回 Response.Body 的附加保护,并且我使用了流的 Position 属性而不是 Seek 方法,因为语法更简单一些,但效果与您的方法没有什么不同.

Your "hacky" approach is likely totally appropriate. You are basically converting a stream that can't seek to one that can. At the end of the day the Response.Body stream has to get swapped out with a stream that is buffered and supports seeking. Here is another take on middleware to do that but you will notice it's quite similar to your approach. I did however choose to use a finally block as added protection for putting the original stream back on the Response.Body and I used the Position property of the stream rather than the Seek method since the syntax is a bit simpler but the effect is no different than your approach.

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

        } 
}

这篇关于如何阅读 ASP.NET Core Response.Body?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆