概要
本文主要介绍如何在ASP.NET Core的中间件中,读取Response.Body的方法,以便于我们实现更多的定制化开发。本文介绍的方法适用于.Net 3.1 和 .Net 6。
代码和实现
现象解释
首先我们尝试在自定义中间件中直接读取Response.Body,代码如下:
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的具体属性:
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之前,将其替换为可读和可写的普通内存流。代码如下:
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;}}
- 用普通的MemoryStream替代原有Response.Body中的Stream;
- 使用MemoryStream 去接收中间件后面操作产生的操作结果;
- 读取MemoryStream中的操作结果;
- 重置MemoryStream,以方便后面的操作读取;
- Response.Body虽然是不可读的,但是可写,我们可以将中间件后续操作中的操作结果写入最初的Response.Body中;
- 将context.Response.Body替换为最初的Stream流。
用上述方法,我们就可以读取甚至修改Response.Body中的内容。
我们调用一个Post请求,查看我们自定义的Middleware和后面的操作是否可以正常完成:
[HttpPost("{id}")]
public Student Post([FromBody] Student student)
{return student;
}
执行结果如下:
Body的内容在中间件中被成功读出,Post请求成功的将Student对象返回。
附录
public class Student{public int Id { get; set; }public string Name { get; set; }}