ASP.Net Core 2中如何在中间件与控制器重复读取JSON请求?
解决ASP.NET Core中间件与控制器共享读取JSON请求体的问题
这个问题我之前也踩过坑,核心原因是HTTP请求体的流默认只能被读取一次——当你在中间件里读完流之后,流的位置指针已经移到末尾了,控制器再去读的时候自然就拿不到数据了。下面给你两种可行的解决方案:
方案一:修复流的可读取性(直接修改现有代码)
你当前的代码里已经用到了EnableRewind,但缺少了重置流指针的关键步骤,而且EnableRewind在ASP.NET Core 3.0+已经被标记为过时,推荐使用EnableBuffering来替代。
修改后的中间件代码
using System; using System.IO; using System.Text; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using srpago_oxxo.Library.Logger; using srpago_oxxo.Library.Logger.Handlers; using srpago_oxxo.Library.Logger.Formatters; namespace srpago_oxxo.Middlewares { public class IOMiddleware { private readonly RequestDelegate _next; private Logger logger; public IOMiddleware(RequestDelegate next) { _next = next; logger = new Logger("SRPAGO-OXXO"); var formatter = new WebFormatter(); var handler = new StdoutHandler(); handler.SetFormatter(formatter); logger.AddHandler(handler); } public async Task Invoke(HttpContext context) { // 启用请求体缓冲,让流可以被多次读取 context.Request.EnableBuffering(); var contentLength = context.Request.ContentLength.HasValue ? context.Request.ContentLength.ToString() : "-"; var info = new Dictionary<string, string>() { {"{code}", "200"}, {"{clientIp}", context.User.ToString()}, {"{method}", context.Request.Method}, {"{path}", context.Request.Path.Value}, {"{bodyLength}", contentLength}, {"{message}", await GetJsonRequest(context)}, }; logger.Info(info); // 在调用下一个中间件/控制器之前,把流指针重置到开头 context.Request.Body.Position = 0; await _next.Invoke(context); } private async Task<string> GetJsonRequest(HttpContext context) { var request = context.Request; // 读取流之前先确保指针在开头 request.Body.Position = 0; using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true); var bodyAsText = await reader.ReadToEndAsync(); // 读完后重置指针,确保后续组件能读到数据 request.Body.Position = 0; return string.IsNullOrEmpty(bodyAsText) ? "{}" : bodyAsText; } } }
修改控制器的GetJsonRequest方法
同样,控制器里读取流的时候也要注意重置指针,或者直接使用StreamReader的leaveOpen参数:
using System.IO; using System.Text; using Microsoft.AspNetCore.Mvc; namespace srpago_oxxo.Controllers { public class BaseController : Controller { protected async Task<string> GetJsonRequest() { var request = this.HttpContext.Request; // 重置流指针到开头 request.Body.Position = 0; using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true); var body = await reader.ReadToEndAsync(); // 读完后再次重置指针(如果后续还有组件需要读的话) request.Body.Position = 0; return body.Replace("\n", "").Replace("\t", ""); } } }
关键改动说明:
- 使用
EnableBuffering()替代EnableRewind(),它会把请求体缓存到内存或磁盘,支持多次读取 - 每次读取流前后都重置
Body.Position = 0,确保流的指针在起始位置 - 使用
StreamReader的leaveOpen: true参数,避免读取后关闭流,导致后续组件无法访问
方案二:将请求体缓存到HttpContext.Items(更优雅的方式)
如果不想每次都操作流指针,可以在中间件里把读取到的请求体内容存入HttpContext.Items,控制器直接从这里获取,避免重复读取流:
中间件修改部分
在Invoke方法里,把读取到的body存入Items:
public async Task Invoke(HttpContext context) { context.Request.EnableBuffering(); var bodyAsText = await GetJsonRequest(context); // 将请求体内容存入HttpContext.Items,供后续控制器使用 context.Items["RequestBody"] = bodyAsText; var contentLength = context.Request.ContentLength.HasValue ? context.Request.ContentLength.ToString() : "-"; var info = new Dictionary<string, string>() { {"{code}", "200"}, {"{clientIp}", context.User.ToString()}, {"{method}", context.Request.Method}, {"{path}", context.Request.Path.Value}, {"{bodyLength}", contentLength}, {"{message}", bodyAsText}, }; logger.Info(info); await _next.Invoke(context); }
控制器修改部分
直接从Items里取,不用再读流:
protected string GetJsonRequest() { if (HttpContext.Items.TryGetValue("RequestBody", out var bodyObj) && bodyObj is string body) { return body.Replace("\n", "").Replace("\t", ""); } return "{}"; }
这种方式更高效,因为只需要读取一次流,后续组件直接复用缓存的内容。
内容的提问来源于stack exchange,提问作者Dante Aguilar
相关产品推荐
相关产品推荐

