解决ASP.NET Core的中间件无法读取Response.Body的问题

概要

本文主要介绍如何在ASP.NET Core的中间件中,读取Response.Body的方法,以便于我们实现更多的定制化开发。本文介绍的方法适用于.Net 3.1 和 .Net 6。

代码和实现

现象解释

首先我们尝试在自定义中间件中直接读取Response.Body,代码如下:

csharp 复制代码
public class GlobalRequestManagementMiddleware : IMiddleware
    {
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            try
            {
                await next(context);
                var reader = new StreamReader(context.Response.Body, Encoding.UTF8);
                var bodyText = await reader.ReadToEndAsync();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

我们会得到一个异常消息,表示Response.Body是一个不可读的Stream流。

我们添加更多的调试信息,查看Response.Body的具体属性:

csharp 复制代码
 public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
   try
   {
       await next(context);
       Console.WriteLine("CanRead is " + context.Response.Body.CanRead);
       Console.WriteLine("CanSeek is " + context.Response.Body.CanSeek);
       Console.WriteLine("CanWrite is " + context.Response.Body.CanWrite);
       var reader = new StreamReader(context.Response.Body, Encoding.UTF8);
       var bodyText = await reader.ReadToEndAsync();

   }
   catch (Exception)
   {
       throw;
   }
}

输出结果如下:

Response.Body是一个不可读,不可查找,但是可写的Stream,CanRead,CanSeek和CanWrite全部是只读属性,不可修改。

解决方案

从Response.Body本身来解决这个问题,已经基本不可能了。因为该Stream已经被标记为不可读,并且不可修改。

我们变换解决思路,既然这个Stream无法使用,那我们就在其进入其它中间件,过滤器和Action之前,将其替换为可读和可写的普通内存流。代码如下:

csharp 复制代码
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
 {
     using ( var bodyStream = new MemoryStream())
     {
         Stream originalBody = context.Response.Body;
         context.Response.Body = bodyStream ;
         await next(context);
         bodyStream.Position = 0;
         var reader = new StreamReader(context.Response.Body, Encoding.UTF8);
         var bodyText = await reader.ReadToEndAsync();
         Console.WriteLine("bodyText is " + bodyText);
         bodyStream.Position = 0;
         await bodyStream.CopyToAsync(originalBody);
         context.Response.Body = originalBody;
     }
 }
  1. 用普通的MemoryStream替代原有Response.Body中的Stream;
  2. 使用MemoryStream 去接收中间件后面操作产生的操作结果;
  3. 读取MemoryStream中的操作结果;
  4. 重置MemoryStream,以方便后面的操作读取;
  5. Response.Body虽然是不可读的,但是可写,我们可以将中间件后续操作中的操作结果写入最初的Response.Body中;
  6. 将context.Response.Body替换为最初的Stream流。

用上述方法,我们就可以读取甚至修改Response.Body中的内容。

我们调用一个Post请求,查看我们自定义的Middleware和后面的操作是否可以正常完成:

csharp 复制代码
[HttpPost("{id}")]
public Student Post([FromBody] Student student)
{
    return student;
}

执行结果如下:

Body的内容在中间件中被成功读出,Post请求成功的将Student对象返回。

附录

csharp 复制代码
  public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
相关推荐
追逐时光者1 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~1 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581362 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳2 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾2 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭3 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding4 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者4 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
从善若水4 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust