如何在ASP.NET Core异常中间件中反序列化HttpContext响应并返回指定结果
在ASP.NET Core异常中间件中实现响应反序列化并返回自定义错误
嘿,我来给你梳理下怎么搞定这个需求——在异常中间件里反序列化响应内容,再返回指定的错误结果。首先得提个关键坑:ASP.NET Core默认的Response.Body是只写流,直接读它肯定报错,所以第一步得先让响应体变成可读取的才行。
1. 先搞定响应流的可读取性
要读取响应体,我们得把原始的只写流替换成可读写的MemoryStream,这里可以写个通用的中间件来复用这个逻辑:
public class ResponseRewindMiddleware { private readonly RequestDelegate _next; public ResponseRewindMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 保存原始响应流 var originalBody = context.Response.Body; // 替换成可读写的内存流 using var responseStream = new MemoryStream(); context.Response.Body = responseStream; try { await _next(context); // 把流指针移回开头,方便后续读取 responseStream.Seek(0, SeekOrigin.Begin); // 把内存流的内容复制回原始流,保证正常响应能发送出去 await responseStream.CopyToAsync(originalBody); } finally { // 恢复原始流 context.Response.Body = originalBody; } } } // 扩展方法方便注册 public static class ResponseRewindExtensions { public static IApplicationBuilder UseResponseRewind(this IApplicationBuilder app) { return app.UseMiddleware<ResponseRewindMiddleware>(); } }
记得在Program.cs里把这个中间件注册在你的异常中间件前面:
var app = builder.Build(); app.UseResponseRewind(); // 先注册这个,才能读取响应 app.UseCustomExceptionMiddleware(); // 你的自定义异常中间件 // ...其他中间件
2. 实现Deserialize<T>扩展方法
接下来给HttpResponse写个扩展方法,用来反序列化响应内容。这里用ASP.NET Core默认的System.Text.Json举例:
using System.Text.Json; public static class HttpResponseDeserializationExtensions { public static async Task<T?> Deserialize<T>(this HttpResponse response) where T : class { if (!response.Body.CanRead) { throw new InvalidOperationException("响应流不可读取,请先启用ResponseRewind中间件"); } // 重置流指针到开头 response.Body.Seek(0, SeekOrigin.Begin); try { return await JsonSerializer.DeserializeAsync<T>(response.Body); } catch (JsonException) { // 反序列化失败就返回null,方便后续判断 return null; } finally { // 读完再把指针移回去,不影响后续流程 response.Body.Seek(0, SeekOrigin.Begin); } } }
如果你习惯用Newtonsoft.Json,改成下面这样就行:
using Newtonsoft.Json; public static class HttpResponseDeserializationExtensions { public static async Task<T?> Deserialize<T>(this HttpResponse response) where T : class { if (!response.Body.CanRead) { throw new InvalidOperationException("响应流不可读取,请先启用ResponseRewind中间件"); } response.Body.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(response.Body); var jsonContent = await reader.ReadToEndAsync(); try { return JsonConvert.DeserializeObject<T>(jsonContent); } catch (JsonSerializationException) { return null; } finally { response.Body.Seek(0, SeekOrigin.Begin); } } }
3. 在异常中间件里实现业务逻辑
现在就可以在你的异常中间件里实现需求了:捕获异常后,尝试反序列化响应,成功的话就返回你指定的500错误对象。
示例异常中间件代码:
public class CustomExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger<CustomExceptionMiddleware> _logger; public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "出现未处理异常"); await HandleException(context, ex); } } private async Task HandleException(HttpContext context, Exception ex) { // 重要:如果响应已经开始发送,就不能再修改了,否则会报错 if (context.Response.HasStarted) { _logger.LogWarning("响应已开始发送,无法替换错误响应"); return; } // 尝试反序列化原始响应 var originalResponse = await context.Response.Deserialize<MyClass>(); if (originalResponse != null) { // 反序列化成功,返回自定义500响应 context.Response.Clear(); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; var errorResult = new { StatusCode = (int)HttpStatusCode.InternalServerError, Message = "something went wrong" }; var json = JsonSerializer.Serialize(errorResult); await context.Response.WriteAsync(json); } else { // 反序列化失败的情况,也可以返回默认错误 context.Response.Clear(); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("An unexpected error occurred."); } } } // 注册扩展方法 public static class CustomExceptionMiddlewareExtensions { public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<CustomExceptionMiddleware>(); } }
几个要注意的点
- 流指针重置:每次读取流之后一定要把指针移回开头,不然后续的中间件或者响应发送会出问题。
- 响应已发送检查:修改响应前必须判断
context.Response.HasStarted,为true的话就别碰响应了,会抛异常。 - 异常捕获:反序列化过程中可能会出现Json格式错误,一定要捕获处理,别让中间件直接崩溃。
内容的提问来源于stack exchange,提问作者Aamir
相关产品推荐
相关产品推荐

