Search code examples
asp.netasp.net-mvc-3compressionaction-filter

FilterAttributes conflicting Compress, RegexFilter


I have a controller action like so

[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Put)]
[InsertScript(Order = 1)]
[Compress(Order = 2)]
public ViewResult Service(Page page) { //omitted }

The InsertScript attribute applied a Response.Filter

response.Filter = new RegexResponseFilter(response.Filter, scriptInsertRegex, replacementString);

The regex response filter is based on a blog post here. Note I am not trying to remove whitespace I'm trying to insert a scrip tag but that is irrelevant.

The write method of the RegexResponseFilter is as so. The rest is the same as the blogpost.

public override void Write(byte[] buffer, int offset, int count)
{
    // capture the data and convert to string
    var data = new byte[count];
    Buffer.BlockCopy(buffer, offset, data, 0, count);

    // filter the string
    var s = Encoding.Default.GetString(buffer);
    s = regex.Replace(s, replacement);

    // write the data to stream 
    var outdata = Encoding.Default.GetBytes(s);
    response.Write(outdata, 0, outdata.GetLength(0));
}

This works great in isolation. But when I applied a compression attribute (fairly standard one seen in any number of places on the internets), the thing broke with FF4 giving the error message.

The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.

I smelled a red herring and removed the InsertScript attribute and it works. So the two are conflicting in some way. Here is the compression attribute for completeness.

public class CompressAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        //get request and response
        var request = filterContext.HttpContext.Request;
        var response = filterContext.HttpContext.Response;

        //get encoding
        var encoding = request.Headers["Accept-Encoding"];
        if (string.IsNullOrWhiteSpace(encoding))
            return;

        encoding = encoding.ToUpperInvariant();


        if (encoding.Contains("DEFLATE") || encoding == "*")
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
        else if (encoding.Contains("GZIP"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }

        base.OnActionExecuting(filterContext);
    }
}

It's probably something obvious but this looks like it should work. The insert should get applied before the compression takes place.

Many thanks


Solution

  • Try flipping the order of inclusion:

    [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Put)]
    [Compress(Order = 1)]
    [InsertScript(Order = 2)]
    public ViewResult Service(Page page) { //omitted }
    

    Another possibility is to combine multiple filters like this and then use a single action filter attribute:

    response.Filter = new CompressStream(new InsertScript(response.Filter));
    

    For this to work you need to write a custom CompressStream class the same way you did with the InsertScript and then have a single action filter attribute which will combine the two streams.