在ActionFilterAttribute/IActionFilter中替换HttpResponseBody失败求助
在ActionFilterAttribute中替换响应内容失败的问题修复
问题场景
尝试通过ActionFilterAttribute替换HttpContext.Response.Body的响应内容,已成功劫持流,但修改后的内容无法写入最终响应。以WeatherForecast WebAPI模板测试,字符串替换逻辑正常,但响应未更新。
原过滤器代码:
public class MessageFilter : ActionFilterAttribute { private MemoryStream responseBody; public override void OnActionExecuting(ActionExecutingContext context) { this.responseBody = new MemoryStream(); context.HttpContext.Response.Body = responseBody; } public override void OnActionExecuted(ActionExecutedContext context) { } public override void OnResultExecuted(ResultExecutedContext context) { responseBody.Seek(0, SeekOrigin.Begin); var originalBody = context.HttpContext.Response.Body; using var sr = new StreamReader(responseBody); var actionResult = sr.ReadToEnd(); using var memStream = new MemoryStream(); context.HttpContext.Response.Body = memStream; var fake = actionResult.Replace("Warm", "REPLACED", StringComparison.InvariantCultureIgnoreCase).Replace("Hot", "REPLACED", StringComparison.InvariantCultureIgnoreCase); memStream.Position = 0; var data = GetStreamWithGetBytes(fake, Encoding.UTF8); memStream.Write(data, 0, data.Length); memStream.Position = 0; memStream.CopyToAsync(originalBody); context.HttpContext.Response.Body = originalBody; } public static byte[] GetStreamWithGetBytes(string sampleString, Encoding? encoding = null) { encoding ??= Encoding.UTF8; var byteArray = encoding.GetBytes(sampleString); return byteArray; } }
原控制器代码:
using Microsoft.AspNetCore.Mvc; using WebApplicationHttpMessageHandler.Filters; namespace WebApplicationHttpMessageHandler.Controllers; [ApiController] [Route("[controller]/[action]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] [ServiceFilter(typeof(MessageFilter))] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } }
问题分析
原代码存在几个核心问题导致替换失效:
- 异步方法未等待:
CopyToAsync是异步方法,直接调用会在操作完成前执行后续代码,导致内容未写入原始流。 - 原始流获取错误:
OnResultExecuted中获取的Response.Body是之前替换的内存流,并非真正的原始响应流。 - 流操作顺序错误:先将
memStream.Position设为0再写入字节,会覆盖流起始位置,导致写入的内容位置异常。 - 未更新响应长度:修改内容后未同步更新
Response.ContentLength,可能导致客户端接收截断内容。
修复方案
改用异步生命周期方法,修正流操作逻辑,确保内容正确写入原始响应流:
public class MessageFilter : ActionFilterAttribute { private MemoryStream _responseBuffer; private Stream _originalResponseBody; public override void OnActionExecuting(ActionExecutingContext context) { // 提前保存原始响应流 _originalResponseBody = context.HttpContext.Response.Body; // 创建内存流缓存响应内容 _responseBuffer = new MemoryStream(); context.HttpContext.Response.Body = _responseBuffer; } public override async Task OnResultExecutedAsync(ResultExecutedContext context, CancellationToken cancellationToken) { try { // 重置缓存流位置,读取原始响应内容 _responseBuffer.Seek(0, SeekOrigin.Begin); using var sr = new StreamReader(_responseBuffer); var originalContent = await sr.ReadToEndAsync(cancellationToken); // 执行内容替换 var modifiedContent = originalContent .Replace("Warm", "REPLACED", StringComparison.InvariantCultureIgnoreCase) .Replace("Hot", "REPLACED", StringComparison.InvariantCultureIgnoreCase); // 转换为字节数组并写入原始流 var modifiedBytes = Encoding.UTF8.GetBytes(modifiedContent); // 更新响应长度,避免内容截断 context.HttpContext.Response.ContentLength = modifiedBytes.Length; // 写入并刷新原始流 await _originalResponseBody.WriteAsync(modifiedBytes, 0, modifiedBytes.Length, cancellationToken); await _originalResponseBody.FlushAsync(cancellationToken); } finally { // 恢复原始响应流,释放缓存流 context.HttpContext.Response.Body = _originalResponseBody; _responseBuffer.Dispose(); } await base.OnResultExecutedAsync(context, cancellationToken); } }
关键修改点
- 在
OnActionExecuting中提前保存原始响应流,避免后续获取错误。 - 使用
OnResultExecutedAsync异步方法,确保所有流操作等待完成。 - 简化流操作:直接将修改后的内容写入原始流,无需额外中转内存流。
- 更新
Response.ContentLength为修改后内容的字节长度,保证客户端完整接收。 - 用
finally块确保资源释放和原始流恢复,避免泄漏。
内容的提问来源于stack exchange,提问作者codingjoe
相关产品推荐
相关产品推荐

