我能在 ASP.NET MVC 中使用 [CompressFilter] 吗,而不破坏甜甜圈缓存?

12

我正在尝试将 [CompressFilter] 与甜甜圈缓存一起使用,但遇到了问题。

整个页面都被缓存了,而不仅仅是甜甜圈。我使用的 CompressFilter 源代码如下。我从 原始源代码 进行了更改,以使用 OnResultExecuted 而不是 OnActionExecuting(),因为我需要访问结果类型以避免缓存某些 ActionResult 子类。

查看实际的 MVC v1 源代码 OutputCacheAttribute,它似乎也在使用 OnResultExecuted(),但我认为这个事实并不直接导致冲突。

我对替换缓存的工作原理还不够了解,无法理解其表现方式背后的原因。我认为值得注意的是,这不会导致任何形式的显示损坏。它只是表现得好像没有甜甜圈一样!

看起来我将不得不使用某种 IIs “插件” 来处理缓存,这是我真的不想做的,但看起来我也需要甜甜圈缓存。

实际上,我现在更感兴趣的是知道它会产生这种效果的原因,但是如果有解决方案的话,那就太好了。

public class CompressFilter : ActionFilterAttribute
{
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;

        // dont encode images!
        if (filterContext.Result is ImageResult)
        {
            return;
        }

        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 GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

@jordan 谢谢!我只是希望我不要花一个小时才弄清楚那个愚蠢的[CompressFilter]是冲突的原因。我检查了所有可能导致甜甜圈缓存失败的东西,真希望这不是问题所在。 - Simon_Weaver
无论具体的过滤器实现如何,您需要仔细检查,确保在调用RenderAction<>时不会重复应用过滤器(基控制器已经有了过滤器)。从我的实现来看,我正在检查if (response.Filter is GZipStream || response.Filter is DeflateStream),如果为真,则跳过其余的过滤器。 - Tom Mayfield
2个回答

10

这是CompressFilter类的一个糟糕实现。

请阅读这篇文章:在C#中找到首选的接受编码

我写了一份自己的代码,它将根据上述文章遵守AcceptEncoding:

public class CompressFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;

        string[] supported = new string[] { "gzip", "deflate" };

        IEnumerable<string> preferredOrder = new AcceptList(request.Headers["Accept-Encoding"], supported);

        string preferred = preferredOrder.FirstOrDefault();

        HttpResponseBase response = filterContext.HttpContext.Response;

        switch (preferred)
        {
            case "gzip":
                response.AppendHeader("Content-Encoding", "gzip");
                response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                break;

            case "deflate":
                response.AppendHeader("Content-Encoding", "deflate");
                response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                break;

            case "identity":
            default:
                break;
        }
    }
}

public class AcceptList : IEnumerable<string>
{
    Regex parser = new Regex(@"(?<name>[^;,\r\n]+)(?:;q=(?<value>[\d.]+))?", RegexOptions.Compiled);

    IEnumerable<string> encodings;

    public AcceptList(string acceptHeaderValue, IEnumerable<string> supportedEncodings)
    {
        List<KeyValuePair<string, float>> accepts = new List<KeyValuePair<string, float>>();

        if (!string.IsNullOrEmpty(acceptHeaderValue))
        {
            MatchCollection matches = parser.Matches(acceptHeaderValue);

            var values = from Match v in matches
                         where v.Success
                         select new
                         {
                             Name = v.Groups["name"].Value,
                             Value = v.Groups["value"].Value
                         };

            foreach (var value in values)
            {
                if (value.Name == "*")
                {
                    foreach (string encoding in supportedEncodings)
                    {
                        if (!accepts.Where(a => a.Key.ToUpperInvariant() == encoding.ToUpperInvariant()).Any())
                        {
                            accepts.Add(new KeyValuePair<string, float>(encoding, 1.0f));
                        }
                    }

                    continue;
                }

                float desired = 1.0f;
                if (!string.IsNullOrEmpty(value.Value))
                {
                    float.TryParse(value.Value, out desired);
                }

                if (desired == 0.0f)
                {
                    continue;
                }

                accepts.Add(new KeyValuePair<string, float>(value.Name, desired));
            }
        }

        this.encodings = from a in accepts
                         where supportedEncodings.Where(se => se.ToUpperInvariant() == a.Key.ToUpperInvariant()).Any() || a.Key.ToUpperInvariant() == "IDENTITY"
                         orderby a.Value descending
                         select a.Key;
    }

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return this.encodings.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)this.encodings).GetEnumerator();
    }
}

感谢。acceptencoding 是唯一的区别吗?我现在没有时间比较它们。再次感谢。 - Simon_Weaver

2

我重写了OnResultExecuting方法。这个方法在呈现ActionResult之前被调用。在检查客户端是否接受压缩之前,我会先检查我要呈现的结果类型。如果它不是ViewResult,我就不会应用任何类型的压缩。

为了使其工作,你的操作必须明确调用View()或PartialView()方法。

下面是CompressOutputAttrtibute的代码:

public class CompressOutputAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext) 
    {
        var result = filterContext.Result;
        if (!(result is 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"))
        {        
            // we want to use gzip 1st
            response.AppendHeader("Content-encoding", "gzip");
            //Add DeflateStream to the pipeline in order to compress response on the fly 
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            //If client accepts deflate, we'll always return compressed content 
            response.AppendHeader("Content-encoding", "deflate");
            //Add DeflateStream to the pipeline in order to compress response on the fly 
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
}

在控制器内部:

[CompressOutput]
public class ArticleController : Controller

    public PartialViewResult MostPopular()
    {
        var viewModel = ArticleMostPopularViewModel();
        viewModel.Articles = CmsService.GetMostPopularArticles();
        return PartialView(viewModel);
    }

    public ViewResult Show(int id)
    {
        var viewModel = ArticleShowViewModel();
        viewModel.Article = CmsService.GetArticle(id);
        return View(viewModel);
    }
}

在使用OnResultExecuting时加一。我发现在OnActionExecuting中应用压缩会在出现错误时引起问题,因为Content-Encoding头被清除了,但是过滤器流没有被清除。 - Marnix van Valen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接