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

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

问题描述

我一直在努力从ASP.NET Core操作中获取Response.Body属性,而我能够确定的唯一解决方案似乎不是最优的.该解决方案要求将Response.BodyMemoryStream交换出去,同时将流读入字符串变量,然后再将其交换回去,然后再发送给客户端.在下面的示例中,我试图在自定义中间件类中获取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,但他曾问过如何阅读.我将保留原来的答案,以保留历史记录,但也将对其进行更新,以显示正确阅读后如何回答该问题.

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.

更新:从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;

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

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天全站免登陆