Search code examples
c#asp.net-web-apiaction-filteractionfilterattribute

HttpResponseBody manipulation or replacement in ActionFilterAttribute or IActionFilter


As simple as it is I am trying to replace my context.HttpContext.Response.Body with another stream.

I have come so far that I can hijack the stream but replacing it doesnt work. Now I am using the weatherforecast web-api template for this example.

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;

    }

As you can se I am replacing 'Warm' or 'Hot' with REPLACED

Replacing works fine, however assigning the changed data and reading it into a stream and back in context.HttpContext.Response.Body doesn't seem to work.

Please what am I missing?

I am not looking for a middleware solution. I want to decorate our controllers with my custom action filter. So it has to be implemented with a actionfilterattribute or IActionFilter.

Here is the entire code:

public class MessageFilter : ActionFilterAttribute
{
    private MemoryStream responseBody;

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        //context.HttpContext.Request.EnableBuffering();
        this.responseBody = new MemoryStream();
        //hijack the real stream with our own memory stream
        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;
    }
}

The controller class:

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();
    }
}

I am really stuck here.


Solution

  • change MessageFilter as follows:

    using Microsoft.AspNetCore.Mvc.Filters;
    using System.Text.Json;
    using Microsoft.AspNetCore.Mvc;
    
    public class MessageFilter : ActionFilterAttribute
    {
        public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            if (context.Result is not ObjectResult result)
            {
                await base.OnResultExecutionAsync(context, next);
                return;
            }
    
            var data = JsonSerializer.Serialize(result.Value);
            var fake = data.Replace("Warm", "REPLACED", StringComparison.InvariantCultureIgnoreCase).Replace("Hot", "REPLACED", StringComparison.InvariantCultureIgnoreCase);
            context.HttpContext.Response.ContentType = "application/json";
            await context.HttpContext.Response.WriteAsync(fake);
        }
    }