You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 18:15:00