【问题标题】:IE6-8 unable to download file from HTTPS siteIE6-8 无法从 HTTPS 站点下载文件
【发布时间】:2012-10-18 15:10:42
【问题描述】:

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

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

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

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

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

如何使用 MVC 解决此问题?

更新

进一步挖掘表明这是 IE6-8 中的根本故障。根据Eric Law's IE internals blog,这是因为在 SSL 连接期间,IE 将 no-cache 指令视为绝对规则。因此,与其不缓存副本,它认为 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的顺序必须no-store之前no-cache并且Pragma指令必须是完全删除。

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

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

【问题讨论】:

  • @tpeczek 查看更新 - 如果可能的话,我不打算覆盖 MVC 的操作过滤器,但我会检查答案。
  • @tpeczek 这个问题的答案似乎是使用Cache-Control:private,这意味着如果用户在同一个会话中下载同一个文件两次,他们将从他们的本地浏览器缓存而不是最新数据。这不是一个修复。我需要告诉浏览器不要以 IE 可以理解的格式缓存(但允许下载)
  • 让我们明确一下这个问题 - 我想知道如何让 MVC 输出 IE6-8 理解的缓存指令,不是使缓存私有可以有一个类似的效果。我有类似的症状,但Calin's question 对我没用。

标签: asp.net-mvc internet-explorer https mime-types


【解决方案1】:

我想出了一个解决方法,但这是一个明确的 hack - 这是一个新的缓存属性来替换内置的 [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 的所需输出。

【讨论】:

    【解决方案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

    【讨论】:

    • 非常有用(和 +1)。不幸的是,IE 速度如此之慢,一次只发出两个请求,我需要它比其他浏览器缓存更多。
    • 我已经修改了这里的解决方案来检查 FileContentResult: bool result = (filterContext.Result is FileResult || filterContext.Result is FileContentResult)
    • @stevieg:您不必明确检查 FileContentResult,因为它派生自 FileResult。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-27
    • 2015-04-02
    • 1970-01-01
    • 2019-05-19
    相关资源
    最近更新 更多