HttpResponseBody manipulation or replacement in ActionFilterAttribute or IActionFilter

125 Views Asked by At

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.

2

There are 2 best solutions below

3
Mohammad Aghazadeh On BEST ANSWER

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);
    }
}
2
Dice On

It will work if you move originalBody reference to OnActionExecuting method:

using System.Text;
using Microsoft.AspNetCore.Mvc.Filters;

namespace WebApplicationHttpMessageHandler.Controllers;

public class MessageFilter : ActionFilterAttribute
{
    private MemoryStream newBody;
    private Stream originalBody;
    
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        newBody = new MemoryStream();
        originalBody = context.HttpContext.Response.Body;
        context.HttpContext.Response.Body = newBody;
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        //modify original response
        newBody.Seek(0, SeekOrigin.Begin);
        using StreamReader sr = new StreamReader(newBody);
        string actionResult = sr.ReadToEnd();
        string fake = actionResult.Replace("Warm", "REPLACED", StringComparison.InvariantCultureIgnoreCase).Replace("Hot", "REPLACED", StringComparison.InvariantCultureIgnoreCase);
        byte[] data = GetStreamWithGetBytes(fake, Encoding.UTF8);

        //write modified response to original response
        using MemoryStream memStream = new MemoryStream();
        memStream.Write(data, 0, data.Length);
        memStream.Position = 0;
        memStream.CopyToAsync(originalBody).Wait();
        context.HttpContext.Response.Body = originalBody;
    }

    public static byte[] GetStreamWithGetBytes(string sampleString, Encoding? encoding = null)
    {
        encoding ??= Encoding.UTF8;
        var byteArray = encoding.GetBytes(sampleString);
        return byteArray;
    }
}