中间件中读写HTTP响应:响应流复制失败与文化设置问题
问题分析与解决方案
你的代码存在几个关键问题导致无法正确复制响应流:
核心问题点
- 重复调用
await next();:方法末尾的第二次调用会导致请求管道被执行两次,响应流被重复处理,直接破坏正常的响应流程。 - 提前判断响应状态码:在执行
next()之前就检查response.StatusCode == 200OK,此时Action还未执行,状态码尚未设置,这个判断完全无效。 - 未恢复原始响应流:替换响应流为内存流后,没有将
response.Body恢复为原始流,导致后续中间件无法正确写入响应。 - 未处理响应头:修改响应体后,没有更新
Content-Length头或启用分块传输,可能导致客户端接收的响应不完整。
修正后的代码
public class SetCultureAttribute : Attribute, IAsyncResourceFilter { public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { // 1. 处理区域性设置逻辑 var userManager = context.HttpContext.RequestServices.GetService<UserManager<ApplicationUser>>(); string languagePreference = "en-US"; string headerPreference = string.Empty; if (context.HttpContext.Request.Headers.ContainsKey("LanguageCode")) { headerPreference = context.HttpContext.Request.Headers["LanguageCode"].ToString(); } if (!string.IsNullOrEmpty(headerPreference)) { var culture = headerPreference == Language.English.ToString() ? "en-US" : "ja"; CultureInfo.CurrentCulture = new CultureInfo(culture); CultureInfo.CurrentUICulture = new CultureInfo(culture); } else { string token = context.HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); if (!string.IsNullOrEmpty(token)) { var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.ReadJwtToken(token); // 从Token Claims中提取用户信息并设置区域性(补充你的逻辑) } CultureInfo.CurrentCulture = new CultureInfo(languagePreference); CultureInfo.CurrentUICulture = new CultureInfo(languagePreference); } // 2. 处理登录API的响应拦截逻辑 string actionName = context.ActionDescriptor.DisplayName; if (actionName.Contains("Userlogin")) { var originalResponseBodyStream = context.HttpContext.Response.Body; using (var responseBodyMemoryStream = new MemoryStream()) { context.HttpContext.Response.Body = responseBodyMemoryStream; // 执行后续管道(Action和其他中间件) await next(); // 仅当请求成功时处理响应体 if (context.HttpContext.Response.StatusCode == StatusCodes.Status200OK) { // 重置内存流位置到起始处 responseBodyMemoryStream.Seek(0, SeekOrigin.Begin); var responseBody = new StreamReader(responseBodyMemoryStream).ReadToEnd(); // 提取UserId并更新用户语言偏好 dynamic jsonResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(responseBody); string userId = jsonResponse?.id; if (!string.IsNullOrEmpty(userId) && userManager != null) { var user = await userManager.FindByIdAsync(userId); if (user != null) { // 更新用户的语言偏好字段(需根据你的实体结构调整) user.LanguagePreference = headerPreference ?? languagePreference; await userManager.UpdateAsync(user); } } // 将响应内容写回内存流(若修改了responseBody则执行此步) responseBodyMemoryStream.Seek(0, SeekOrigin.Begin); await new StreamWriter(responseBodyMemoryStream) { AutoFlush = true }.WriteAsync(responseBody); responseBodyMemoryStream.Seek(0, SeekOrigin.Begin); // 更新响应体长度 context.HttpContext.Response.ContentLength = responseBodyMemoryStream.Length; } // 将内存流内容复制回原始响应流 responseBodyMemoryStream.Seek(0, SeekOrigin.Begin); await responseBodyMemoryStream.CopyToAsync(originalResponseBodyStream); // 恢复原始响应流 context.HttpContext.Response.Body = originalResponseBodyStream; } } else { // 非登录API,直接执行后续管道 await next(); } } }
关键优化说明
- 移除重复的
next()调用:确保整个请求管道只执行一次,避免重复处理响应。 - 延后状态码判断:在执行
next()之后再检查响应状态码,此时状态码已由Action设置完成。 - 恢复原始响应流:处理完内存流后,必须将
response.Body恢复为原始流,否则后续中间件无法正常工作。 - 处理响应头:修改响应体后更新
Content-Length头,确保客户端能正确接收完整响应。 - 完善用户更新逻辑:补充了通过
userManager更新用户语言偏好的具体代码(需根据你的实体结构调整)。
内容的提问来源于stack exchange,提问作者Amey Rane
相关产品推荐
相关产品推荐

