Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 3 compression filter causing garbled output

So, I have a custom attribute called CompressAttribute which is set up as a global filter in global.asax. It uses reflection to examine the return type of the current action method and if it is "ViewResult" it compresses the output using either GZip or Deflate. It works just fine except if a page throws a 500 Server Error. If an error is encountered, instead of displaying the .NET error page, I get a bunch of this:

��������`I�%&/m�{J�J��t��

Apparently it's attempting to encode the 500 Server Error page which is causing problems. What's the best way to handle this?

Here's the filter code:

public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            MethodInfo actionMethodInfo = Common.GetActionMethodInfo(filterContext);
            if (GetReturnType(actionMethodInfo).ToLower() != "viewresult") return;

            HttpRequestBase request = filterContext.HttpContext.Request;

            string acceptEncoding = request.Headers["Accept-Encoding"];

            if (string.IsNullOrEmpty(acceptEncoding)) return;

            acceptEncoding = acceptEncoding.ToUpperInvariant();

            HttpResponseBase response = filterContext.HttpContext.Response;

            if (acceptEncoding.Contains("GZIP"))
            {
                response.AppendHeader("Content-encoding", "gzip");
                response.Filter = new WebCompressionStream(response.Filter, CompressionType.GZip);
            }
            else if (acceptEncoding.Contains("DEFLATE"))
            {
                response.AppendHeader("Content-encoding", "deflate");
                response.Filter = new WebCompressionStream(response.Filter, CompressionType.Deflate);
            }
        }
like image 761
Scott Avatar asked Dec 28 '10 18:12

Scott


4 Answers

Ok so I was able to resolve this by clearing the Response.Filter property in the Application_Error event:

public void Application_Error(object sender, EventArgs e)
{
    Response.Filter.Dispose();
}

Wondering if there's a more correct way to do it though...

like image 58
Scott Avatar answered Nov 17 '22 11:11

Scott


You can also solve this by attaching to OnResultExecuting instead of OnActionExecuting. This gives a few advantages

  1. You can discover the action result without resorting to reflection.
  2. OnResultExecuting won't execute in exceptional cases (MVC will invoke OnException but not OnResultExecuting)

Something like this:

public sealed class MyAttribute  : ActionFilterAttribute
{
    /// <summary>
    /// Called by MVC just before the result (typically a view) is executing.
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var result = filterContext.Result;
        if (result is ViewResultBase)
        {
            var response = filterContext.HttpContext.Response;

            // Check your request parameters and attach filter.
        }
    }
like image 27
IanBru Avatar answered Nov 17 '22 09:11

IanBru


The accepted answer won't work if anything has been already written to the output.

Instead of disposing the filter you can make sure the headers are being persisted in place:

 protected void Application_PreSendRequestHeaders()
{
    // ensure that if GZip/Deflate Encoding is applied that headers are set
    // also works when error occurs if filters are still active
    HttpResponse response = HttpContext.Current.Response;
    if (response.Filter is GZipStream && response.Headers["Content-encoding"] != "gzip")
        response.AppendHeader("Content-encoding", "gzip");
    else if (response.Filter is DeflateStream && response.Headers["Content-encoding"] != "deflate")
        response.AppendHeader("Content-encoding", "deflate");
}
like image 24
Adaptabi Avatar answered Nov 17 '22 10:11

Adaptabi


I had this same problem in asp.net mvc 1.0 browsing for a page that had a RenderAction inside (from the futures assembly). Apparently the problem was that the response was being encoded twice. I had to create an action filter for this child actions so that a flag is set in the DataTokens collection of the RouteData. Then I had to modify the compress filter so that it returned in case the flag was set. I also had to deal with the execution order of the filters. Maybe this can help, verify if the compress filter is being called more than one time when an error page is raised.

like image 1
uvita Avatar answered Nov 17 '22 11:11

uvita