I have an MVC .Net application that has actions that return report files, usually .xslx
:
byte[] data = GetReport();
return File(data,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"filename.xlsx");
This works great in testing and in all browsers, but when we put this on an SSL site it fails for IE6, 7 and 8 (all the proper browsers still work fine) with this unhelpful error:
This used to work in a legacy application (non-MVC) that this action replaces.
We can't tell our users to change anything locally - about 60% are still on IE6!
How can I fix this using MVC?
Further digging reveals that this is a fundamental failure in IE6-8. According to Eric Law's IE internals blog this happens because, during an SSL connection, IE treats the no-cache directive as an absolute rule. So, rather than not cache a copy, it considers no-cache to mean that it shouldn't be possible to save a copy to disk even when
Content-Disposition:attachment
and with an explicit prompt for a download location.
Obviously this is wrong, but while it's fixed in IE9 we're still stuck with all the IE6-8 users.
Using MVC's action filter attributes produces the following headers:
Cache-Control:no-cache, no-store, must-revalidate
Pragma:no-cache
Using Fiddler to change these on the fly we can verify the headers that need to be returned instead:
Cache-Control:no-store, no-cache, must-revalidate
Note the order of the Cache-Control
must have no-store
before no-cache
and that the Pragma
directive must be removed entirely.
This is a problem - we use MVC's action attributes extensively and I really don't want to rewrite them from scratch. Even if we could IIS throws an exception if you try to remove the Pragma
directive.
How do you make Microsoft's MVC and IIS return the no-cache directive that Microsoft's IE6-8 can handle under HTTPS? I do not want to allow private caching of the response (as per this similar question) or ignore the MVC built in methods with an override (as per my own answer, which is just my current best hack).
This error means you don't have permission to download this file from the server. To fix, go to the website where the file is hosted. Check if you need to sign in (or provide some other authentication). If you can't sign in, contact the website or server owner, or try finding the file on a different site.
To download files, Internet Explorer must create a cache or temporary file. In Internet Explorer 9 or a later version, if the file is delivered over HTTPS, and any response headers are set to prevent caching, and if the Do not save encrypted pages to disk option is set, a cache file is not created.
I've come up with a workaround, but it's a definite hack - this is a new cache attribute to replace the built-in [OutputCache]
one:
[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);
}
}
It's a workaround at best though - what I really want is to extend the existing [OutputCache]
and Response.Cache
structures so that they have the desired output suitable for legacy IEs.
I was having a similar approach in that I had a BaseController class
[OutputCache(Duration=0)]
public class BaseController : Controller
{
//snip snip: some utility stuff and shared endpoints among all my controllers
}
That caused the above mentioned problems in IE8. Applying the [IENoCacheAttribute]
as shown above didn't work however. The problem is that the instruction filterContext.HttpContext.Response.ClearHeaders()
removes all of my headers including eventual Content-Disposition
headers etc... causing the file download to not happen correctly.
My approach was therefore to overwrite the default OutputCacheAttribute.cs
in such a way that in case of IE it didn't apply any kind of caching headers, especially the problematic no-cache
ones.
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;
}
}
Here's the corresponding gist: https://gist.github.com/4633225
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With