9

我有一个 MVC .Net 应用程序,它具有返回报告文件的操作,通常.xslx

byte[] data = GetReport();
return File(data, 
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 
    "filename.xlsx");

这在测试和所有浏览器中都非常有效,但是当我们将它放在 SSL 站点上时,它在 IE6、7 和 8(所有正确的浏览器仍然可以正常工作)上失败,并出现这个无益的错误:

无法从服务器下载文件名。 无法打开此 Internet 站点。 请求的站点不可用或找不到。 请稍后再试。

这曾经在此操作替换的遗留应用程序(非 MVC)中工作。

我们不能告诉我们的用户在本地进行任何更改——大约 60% 的用户仍在使用 IE6!

如何使用 MVC 解决此问题?

更新

进一步挖掘表明,这是 IE6-8 中的根本性失败。根据Eric Law 的 IE 内部博客,这是因为在 SSL 连接期间,IE 将 no-cache 指令视为绝对规则。因此,与其不缓存副本,它认为无缓存意味着即使在 Content-Disposition:attachment明确提示下载位置时也不应该将副本保存到磁盘。

显然这是错误的,但是虽然它在 IE9 中得到了修复,但我们仍然坚持所有 IE6-8 用户。

使用 MVC 的操作过滤器属性会生成以下标头:

Cache-Control:no-cache, no-store, must-revalidate
Pragma:no-cache

使用 Fiddler 动态更改这些,我们可以验证需要返回的标头:

Cache-Control:no-store, no-cache, must-revalidate

请注意Cache-Control must have no-store before的顺序,并且 必须完全删除no-cachePragma指令。

这是一个问题——我们广泛使用 MVC 的动作属性,我真的不想从头开始重写它们。即使我们可以在您尝试删除Pragma指令时 IIS 抛出异常。

如何让微软的 MVC 和 IIS 返回微软的 IE6-8 在 HTTPS 下可以处理的 no-cache 指令?我不想允许私有缓存响应(根据这个类似的问题)或忽略带有覆盖的 MVC 内置方法(根据我自己的答案,这只是我目前最好的 hack)。

4

2 回答 2

8

我想出了一个解决方法,但它是一个明确的黑客 - 这是一个新的缓存属性来替换内置的[OutputCache]

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class IENoCacheAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsSecureConnection &&
            string.Equals(filterContext.HttpContext.Request.Browser.Browser, "IE", StringComparison.OrdinalIgnoreCase) &&
            filterContext.HttpContext.Request.Browser.MajorVersion < 9)
        {
            filterContext.HttpContext.Response.ClearHeaders();
            filterContext.HttpContext.Response.AddHeader("cache-control", "no-store, no-cache, must-revalidate");
        }
        else
        {
            filterContext.HttpContext.Response.Cache.SetNoStore();
            filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }

        base.OnResultExecuting(filterContext);
    }
}

不过,这充其量只是一种解决方法——我真正想要的是扩展现有的[OutputCache]Response.Cache结构,以便它们具有适合传统 IE 的所需输出。

于 2012-10-29T13:00:33.853 回答
2

我有一个类似的方法,因为我有一个 BaseController 类

[OutputCache(Duration=0)]
public class BaseController : Controller
{
    //snip snip: some utility stuff and shared endpoints among all my controllers
}

这导致了IE8中的上述问题。但是,应用[IENoCacheAttribute]如上所示的方法不起作用。问题是该指令filterContext.HttpContext.Response.ClearHeaders()删除了我的所有标题,包括最终Content-Disposition标题等......导致文件下载无法正确进行。

因此,我的方法是以这样一种方式覆盖默认值OutputCacheAttribute.cs,即在 IE 的情况下,它不会应用任何类型的缓存标头,尤其是有问题no-cache的标头。

public class EnhancedOutputCacheAttribute : OutputCacheAttribute
{

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {

        if (!IsFileResultAndOldIE(filterContext))
            base.OnActionExecuted(filterContext);
        else
        {
            //try the best to avoid any kind of caching
            filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.Private);
            filterContext.HttpContext.Response.Cache.SetMaxAge(new TimeSpan(0));
            filterContext.HttpContext.Response.Cache.SetExpires(DateTime.Now.AddMinutes(-5D));
        }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!IsFileResultAndOldIE(filterContext))
            base.OnActionExecuting(filterContext);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (!IsFileResultAndOldIE(filterContext))
            base.OnResultExecuted(filterContext);
    }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (!IsFileResultAndOldIE(filterContext))
            base.OnResultExecuting(filterContext);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="filterContext"></param>
    /// <returns><c>true</c> for FileResults and if the browser is < IE9</returns>
    private bool IsFileResultAndOldIE(dynamic filterContext)
    {
        return filterContext.Result is FileResult &&
               filterContext.HttpContext.Request.IsSecureConnection &&
               string.Equals(filterContext.HttpContext.Request.Browser.Browser, "IE", StringComparison.OrdinalIgnoreCase) &&
               filterContext.HttpContext.Request.Browser.MajorVersion < 9;
    }

}

这是相应的要点:https ://gist.github.com/4633225

于 2013-01-25T10:12:53.703 回答