9

レポートファイルを返すアクションを持つ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 の 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 は例外をスローします。

Microsoft の MVC と IIS に、Microsoft の IE6-8 が HTTPS で処理できる no-cache ディレクティブを返すようにするにはどうすればよいですか? 応答のプライベート キャッシュを許可したり (この同様の質問に従って)、MVC の組み込みメソッドをオーバーライドして無視したりしたくありません(私自身の回答によると、これは私の現在の最高のハックです)。

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 に答える