web-dev-qa-db-ja.com

MVC4 StyleBundle:デバッグモードでキャッシュ無効化クエリ文字列を追加できますか?

MVCアプリケーションがあり、次のようにCSSファイルをレンダリングするためにStyleBundleクラスを使用しています。

bundles.Add(new StyleBundle("~/bundles/css").Include("~/Content/*.css"));

私の問題は、Debugモードでは、CSSのURLが個別にレンダリングされ、これらのURLを積極的にキャッシュするWebプロキシがあることです。 Releaseモードでは、各リリースのキャッシュを無効にするために、クエリ文字列が最終URLに追加されることを知っています。

StyleBundleを設定してDebugモードでランダムなクエリ文字列を追加し、次の出力を生成してキャッシュの問題を回避することはできますか?

<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>
47
growse

これを行うには、カスタムIBundleTransformクラスを作成できます。次に、ファイルの内容のハッシュを使用してv = [filehash]パラメータを追加する例を示します。

public class FileHashVersionBundleTransform: IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        foreach(var file in response.Files)
        {
            using(FileStream fs = File.OpenRead(HostingEnvironment.MapPath(file.IncludedVirtualPath)))
            {
                //get hash of file contents
                byte[] fileHash = new SHA256Managed().ComputeHash(fs);

                //encode file hash as a query string param
                string version = HttpServerUtility.UrlTokenEncode(fileHash);
                file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", version);
            }                
        }
    }
}

次に、バンドルのTransformsコレクションに追加してクラスを登録できます。

new StyleBundle("...").Transforms.Add(new FileHashVersionBundleTransform());

バージョン番号は、ファイルの内容が変更された場合にのみ変更されます。

45
bingles

一意の文字列が必要です。ハッシュである必要はありません。ファイルのLastModified日付を使用して、そこからティックを取得します。 @Toddが指摘したように、ファイルのオープンと読み取りにはコストがかかります。ティックは、ファイルが変更されると変化する一意の番号を出力するのに十分です。

internal static class BundleExtensions
{
    public static Bundle WithLastModifiedToken(this Bundle sb)
    {
        sb.Transforms.Add(new LastModifiedBundleTransform());
        return sb;
    }
    public class LastModifiedBundleTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            foreach (var file in response.Files)
            {
                var lastWrite = File.GetLastWriteTime(HostingEnvironment.MapPath(file.IncludedVirtualPath)).Ticks.ToString();
                file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", lastWrite);
            }
        }
    }
}

そしてそれを使用する方法:

bundles.Add(new StyleBundle("~/bundles/css")
    .Include("~/Content/*.css")
    .WithLastModifiedToken());

そしてこれはMVCが書くものです:

<link href="bundles/css/site.css?v=635983900813469054" rel="stylesheet"/>

スクリプトバンドルでも正常に動作します。

41
H Dog

このライブラリは、デバッグモードでバンドルファイルにキャッシュ無効化ハッシュを追加できるほか、他のいくつかのキャッシュ無効化アイテムを追加できます。 https://github.com/kemmis/System.Web.Optimization.HashCache

BundlesCollection内のすべてのバンドルにHashCacheを適用できます

すべてのバンドルがコレクションに追加された後、BundlesCollectionインスタンスでApplyHashCache()拡張メソッドを実行します

BundleTable.Bundles.ApplyHashCache();

または、HashCacheを単一のバンドルに適用できます

HashCacheTransformのインスタンスを作成し、HashCacheを適用するバンドルインスタンスに追加します。

var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());
13
Rafe

私は同じ問題を抱えていましたが、アップグレード後にクライアントブラウザのキャッシュバージョンに問題がありました。私の解決策は、次のようにクエリ文字列にバージョン番号を追加する独自のレンダラーで@Styles.Render("~/Content/css")の呼び出しをラップすることです。

    public static IHtmlString RenderCacheSafe(string path)
    {
        var html = Styles.Render(path);
        var version = VersionHelper.GetVersion();
        var stringContent = html.ToString();

        // The version should be inserted just before the closing quotation mark of the href attribute.
        var versionedHtml = stringContent.Replace("\" rel=", string.Format("?v={0}\" rel=", version));
        return new HtmlString(versionedHtml);
    }

そして、ビューでは次のようにします:

@RenderHelpers.RenderCacheSafe("~/Content/css")
8
Johan Gov

現在はありませんが、すぐに追加される予定です(現在1.1安定版リリースの予定です。この問題はここで追跡できます: Codeplex

2
Hao Kung

これはスクリプト用に書かれているが、スタイルにも機能することに注意してください(これらのキーワードを変更するだけです)

@ヨハンの答えに基づいて構築:

public static IHtmlString RenderBundle(this HtmlHelper htmlHelper, string path)
{
    var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
    var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
    var html = System.Web.Optimization.Scripts.Render(path).ToString();
    foreach (var item in bundle.EnumerateFiles(context))
    {
        if (!html.Contains(item.Name))
            continue;

        html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
    }

    return new HtmlString(html);
}

public static IHtmlString RenderStylesBundle(this HtmlHelper htmlHelper, string path)
{
    var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
    var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
    var html = System.Web.Optimization.Styles.Render(path).ToString();
    foreach (var item in bundle.EnumerateFiles(context))
    {
        if (!html.Contains(item.Name))
            continue;

        html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
    }

    return new HtmlString(html);
}

使用法:

@Html.RenderBundle("...")
@Html.RenderStylesBundle("...")

交換

@Scripts.Render("...")
@Styles.Render("...")

利点:

  • System.Web.Optimizationsのv1.0.0.0で動作します
  • バンドル内の複数のファイルで動作します
  • グループではなく、各ファイルのハッシュではなくファイル変更日を取得します

また、Bundlerを迅速に回避する必要がある場合:

public static MvcHtmlString ResolveUrl(this HtmlHelper htmlHelper, string url)
{
    var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
    var resolvedUrl = urlHelper.Content(url);

    if (resolvedUrl.ToLower().EndsWith(".js") || resolvedUrl.ToLower().EndsWith(".css"))
    {
        var localPath = HostingEnvironment.MapPath(resolvedUrl);
        var fileInfo = new FileInfo(localPath);
        resolvedUrl += "?" + fileInfo.LastWriteTimeUtc.ToString("yyyyMMddHHmmss");
    }

    return MvcHtmlString.Create(resolvedUrl);
}

使用法:

<script type="text/javascript" src="@Html.ResolveUrl("~/Scripts/jquery-1.9.1.min.js")"></script>

交換:

<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"></script>

(他の多くの代替ルックアップも置き換えます)

1
Todd