C# 正在读取筛选器中的Response.Body流

C# 正在读取筛选器中的Response.Body流,c#,asp.net,asp.net-web-api,asp.net-core,C#,Asp.net,Asp.net Web Api,Asp.net Core,我编写了在服务器方法调用后运行的过滤器,并将其内容打印到控制台。代码是用ASP.NET core v2.1编写的: public class MyCustomFilter : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext context) { // ERROR on the next line! using (StreamRe

我编写了在服务器方法调用后运行的过滤器,并将其内容打印到控制台。代码是用ASP.NET core v2.1编写的:

public class MyCustomFilter : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext context)
    {

        // ERROR on the next line!
        using (StreamReader sr = new StreamReader(context.HttpContext.Response.Body))
        {
            Console.WriteLine(sr.ReadToEnd());
        }

        base.OnResultExecuted(context);
    }
}
结果-例外:

流不可读

进一步的研究表明,流(
context.HttpContext.Response
)具有以下值:

  • CanRead=false
  • CanSeek=false
  • 这可以解释为什么它不能阅读身体


    如何解决?

    这完全取决于你想要实现什么。 如果您想获得响应值或只是想查看结果,可以使用

    context.Result or context.Result.Value
    
    如果您想要修改响应或只是记录整个响应,您应该使用中间件

    这是一个很好的例子


    希望它能帮上忙。我不知道你为什么要这样做
    context.Result
    IActionResult
    的一个实例,您可以随意操作它。如果您确实想阅读
    Response.Body
    ,可以做一些黑客行为

    由于默认的
    响应.Body
    不是可读的
    ,为了使Body可读,我们需要劫持响应,即用我们自己的
    实例替换
    Body

  • 我们可以在执行操作之前动态创建一个全新的内存流,并劫持默认的
    Response.Body
  • 执行操作时,使用
    StreamReader
    读取流,做一些工作,并设置
    响应。Body=您的新流
  • 使用普通内存流劫持
    响应.Body
    是安全的,因为
    Body
    的类型是普通

    public class MyCustomFilter : ActionFilterAttribute
    {
        private MemoryStream responseBody ;
    
        public override void OnActionExecuting(ActionExecutingContext context){
            this.responseBody=new MemoryStream();
            // hijack the real stream with our own memory stream 
            context.HttpContext.Response.Body = responseBody;
        }
    
        public override void OnResultExecuted(ResultExecutedContext context)
        {
    
            responseBody.Seek(0, SeekOrigin.Begin);
    
            // read our own memory stream 
            using (StreamReader sr = new StreamReader(responseBody))
            {
                var actionResult= sr.ReadToEnd();
                Console.WriteLine(actionResult);
                // create new stream and assign it to body 
                // context.HttpContext.Response.Body = ;
            }
    
            // no ERROR on the next line!
    
            base.OnResultExecuted(context);
        }
    }
    
    出于测试目的,我创建了一个操作方法:

    [MyCustomFilter]
    public IActionResult Index()
    {
        return Ok("it wooooooooorks");
    }
    

    使用Actionfilter而不是中间件:

    var resultnext = await next();
    var objectResult = resultnext.Result as ObjectResult;
    var resultValue = objectResult.Value;
    
                
    

    无需对您自己的实现进行修改。只需调用
    Request.EnableRewind()
    (ASP.NET Core 2.0及更高版本)和
    请求.Stream
    将替换为可倒带版本。与您的解决方案一样,人们应该始终意识到这样做对内存/性能的影响:@Tseng我没有意识到这一点。非常感谢,你的解决方案比我的好得多。@Tseng Hi,很抱歉打扰你。但是我不知道如何为
    Response
    启用倒带,就像
    Request.EnableRewind()
    。你能帮我个忙吗?谢谢。只需调用
    Request.EnableRewind()
    ,但您必须在从请求读取任何内容之前执行此操作。如果其他中间件已经开始从中读取,那么调用
    EnableRewind()
    就太晚了
    EnableRewind()
    是一种扩展方法(如链接文档中所示),您需要声明名称空间导入以使其工作:
    使用Microsoft.AspNetCore.Http.Internal。但是要小心使用它,它是一个所谓的“pubinternal”(具有公共访问器的内部API),意味着将来可能会发生(中断)更改EnableRewind,或者启用缓冲现在发布的方式,因为它仅可用于请求,而不可用于响应。