BundleCollectionにMVC4のキャッシュされたスクリプトバンドルを強制的にフラッシュする方法


85

...または、心配するのをやめて、Microsoftの完全に文書化されていないAPIに対してコードを書くことをどのように学んだか。公式System.Web.Optimizationリリースの実際のドキュメントはありますか?'cuz確かに何も見つかりません、XMLドキュメントはありません、そしてすべてのブログ投稿は実質的に異なるRCAPIを参照しています。Anyhoo ..

私はjavascriptの依存関係を自動的に解決するコードをいくつか書いていて、それらの依存関係からその場でバンドルを作成しています。アプリケーションを再起動せずにスクリプトを編集したり、バンドルに影響する変更を加えたりした場合を除いて、すべてがうまく機能します。変更は反映されません。そこで、開発で使用するために依存関係のキャッシュを無効にするオプションを追加しました。

ただし、バンドルコレクションが変更された場合でも、明らかBundleTablesにURLをキャッシュします。たとえば、自分のコードでバンドルを再作成する場合は、次のようにします。

// remove an existing bundle
BundleTable.Bundles.Remove(BundleTable.Bundles.GetBundleFor(bundleAlias));

// recreate it.
var bundle = new ScriptBundle(bundleAlias);

// dependencies is a collection of objects representing scripts, 
// this creates a new bundle from that list. 

foreach (var item in dependencies)
{
    bundle.Include(item.Path);
}

// add the new bundle to the collection

BundleTable.Bundles.Add(bundle);

// bundleAlias is the same alias used previously to create the bundle,
// like "~/mybundle1" 

var bundleUrl = BundleTable.Bundles.ResolveBundleUrl(bundleAlias);

// returns something like "/mybundle1?v=hzBkDmqVAC8R_Nme4OYZ5qoq5fLBIhAGguKa28lYLfQ1"

同じエイリアスでバンドルを削除して再作成しても、まったく何も起こりません。bundleUrl返されるのResolveBundleUrlは、バンドルを削除して再作成する前と同じです。「同じ」とは、バンドルの新しいコンテンツを反映するためにコンテンツハッシュが変更されていないことを意味します。

編集...実際には、それよりもはるかに悪いです。バンドル自体は何とか外のキャッシュされるBundlesコレクション。ブラウザがスクリプトをキャッシュしないように独自のランダムハッシュを生成するだけの場合、ASP.NETは古いスクリプトを返します。したがって、どうやら、からバンドルを削除BundleTable.Bundlesしても実際には何も起こりません。

この問題を回避するためにエイリアスを変更するだけで済みます。これは開発には問題ありませんが、ページの読み込みごとにエイリアスを非推奨にするか、BundleCollectionのサイズが大きくなるため、このアイデアは好きではありません。すべてのページが読み込まれます。これを実稼働環境でオンのままにすると、災害になります。

したがって、スクリプトが提供されると、実際のBundleTables.Bundlesオブジェクトとは関係なくキャッシュされるようです。したがって、URLを再利用する場合、参照するバンドルを削除してから再利用しても、そのURLはキャッシュ内にあるもので応答し、Bundlesオブジェクトを変更してもキャッシュはフラッシュされません。つまり、新しいアイテム(またはむしろ、別の名前の新しいアイテムが使用されることはありません。

動作は奇妙に思えます...コレクションから何かを削除すると、キャッシュから削除されるはずです。しかし、そうではありません。このキャッシュをフラッシュしBundleCollection、そのバンドルが最初にアクセスされたときにキャッシュされたものではなく、現在のコンテンツを使用する方法が必要です。

私がこれをどのように行うかについて何か考えはありますか?

ResetAll目的がわからないこの方法がありますが、とにかく物事を壊すだけなので、そうではありません。


ここでも同じ問題があります。私はなんとか私の問題を解決できたと思います。それがあなたのために働くかどうか試してみてください。完全に同意する。System.Web.Optimizationのドキュメントはごみであり、インターネットで見つけることができるすべてのサンプルは古くなっています。
leftyX 2012

2
MSの信頼への期待についての痛烈なコメントと組み合わせた、上部の優れた参照のための+1。そして、私が答えたい質問をするためにも。
Raif 2013年

回答:


33

ドキュメントに苦労しているとのことですが、残念ながらこの機能はまだ非常に速く変化しており、ドキュメントの生成には多少の遅れがあり、ほとんどすぐに古くなる可能性があります。 リックのブログ投稿は最新のものであり、その間に現在の情報を広めるために、ここでも質問に答えようとしました。現在、公式のcodeplexサイトをセットアップ中です。このサイトには、常に最新のドキュメントがあります。

次に、バンドルをキャッシュからフラッシュする方法に関する特定の問題について説明します。

  1. 要求されたバンドルURLから生成されたキーを使用して、バンドルされた応答をASP.NETキャッシュ内に格納しContext.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]ます。つまり、このバンドルの生成に使用されたすべてのファイルとディレクトリに対するキャッシュの依存関係も設定します。したがって、基になるファイルまたはディレクトリのいずれかが変更されると、キャッシュエントリがフラッシュされます。

  2. リクエストごとのBundleTable / BundleCollectionのライブ更新は実際にはサポートされていません。完全にサポートされているシナリオは、アプリの起動時にバンドルが構成されていることです(これにより、Webファームのシナリオですべてが正しく機能します。そうしないと、間違ったサーバーに送信された場合、一部のバンドル要求が404になります)。コード例を見ると、特定のリクエストでバンドルコレクションを動的に変更しようとしていると思いますか?すべてが正しくセットアップされていることを保証するために、あらゆる種類のバンドル管理/再構成には、appdomainのリセットを伴う必要があります。

したがって、アプリドメインをリサイクルせずにバンドル定義を変更することは避けてください。バンドル内の実際のファイルは自由に変更できます。これらのファイルは自動的に検出され、バンドルURLの新しいハッシュコードが生成されます。


2
あなたの直接の知識をここに持ってきてくれてありがとう!はい-バンドルコレクションを動的に変更しようとしています。バンドルは、別のスクリプトで記述された一連の依存関係(つまり、それ自体、必ずしもバンドルの一部ではない)に基づいて構築されます。これが、この問題が発生している理由です。バンドル内のスクリプトを変更すると強制的にフラッシュされるので、それを行うことができます-手動のフラッシュメソッドを追加する可能性はありますか?これは重要ではありません-これは開発中の便宜のためです-しかし、誤って製品で使用された場合に問題を引き起こす可能性のあるコードを作成することは嫌いです。
Jamie Treworgy 2012

また、Webファームの問題について詳しく説明できますか?アプリケーションの起動後に新しいバンドルを追加すると、それが作成されたサーバーでのみ使用可能になりますか?それとも既存のバンドルを変更しようとしますか?これは、依存関係の実行時の解決を行う必要があるため、私がやろうとしていることにとっては少し厄介です。
Jamie Treworgy 2012

確かに、明示的なキャッシュフラッシュと同等のメソッドを追加できます。これはすでに内部にあります。Webファームの問題に関しては、基本的に2つのWebサーバーAとBがあり、リクエストはバンドルを追加するAに送信され、応答が送信されます。クライアントはバンドルのコンテンツをフェッチしますが、リクエストはバンドルを登録しなかったサーバーB、そしてあなたの404があります。–
Hao Kung

1
キャッシュの更新は遅延します。バンドルが初めて使用されるとき(通常はバンドルへの参照をレンダリングすることにより)、キャッシュに追加されます。リクエストの処理を開始する前にすべてのウェブサーバーでバンドルをセットアップする同等のアプリ開始フックがある場合は、それで問題ありません。
ハオクン2012

2
私の知る限り、これは機能しません。つまり、構成ファイルを変更しても、ここに記載されているようにサーバーキャッシュがクリアされません。変更を加えるには、物をリサイクルする必要があります。その公式文書が実際にどこにあるか知っている人はいますか?
philw 2013

21

同様の問題があります。
私のクラスでBundleConfigは、を使用した場合の効果を確認しようとしていましたBundleTable.EnableOptimizations = true

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        BundleTable.EnableOptimizations = true;

        bundles.Add(...);
    }
}

すべてが正常に機能していました。
ある時点で、デバッグを行っていて、プロパティをfalseに設定していました。
jqueryのバンドル(最初のバンドル)が解決およびロードされないように見えたため、何が起こっているのか理解するのに苦労しました(/bundles/jquery?v=)。

いくつかの宣誓の後、私は思う(?!)私は物事を整理することができました。追加bundles.Clear()してみてbundles.ResetAll()、登録の開始時に、物事が再び機能し始めるはずです。

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Clear();
        bundles.ResetAll();

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

EnableOptimizationsプロパティを変更した場合にのみ、これら2つのメソッドを実行する必要があることに気付きました。

更新:

掘り深い私はことを発見したBundleTable.Bundles.ResolveBundleUrl@Scripts.Urlのバンドルパスを解決するために問題を持っているように見えます。

簡単にするために、いくつかの画像を追加しました。

画像1

最適化をオフにして、いくつかのスクリプトをバンドルしました。

画像2

同じバンドルが本体に含まれています。

画像3

@Scripts.Url バンドルの「最適化された」パスを教えてくれます @Scripts.Render適切な生成します。
同じことがBundleTable.Bundles.ResolveBundleUrl。でも起こります。

Visual Studio 2010 + MVC 4 + Framework .Net4.0を使用しています。


うーん...問題は、バンドルテーブルを実際にクリアしたくないということです。バンドルテーブルには、さまざまなページからの(さまざまな依存関係のセットから作成された)他の多くのテーブルが含まれるためです。しかし、これは実際には開発環境での作業のためだけのものなので、キャッシュをフラッシュする場合は、内容をコピーしてクリアし、再度追加することができると思います。恐ろしく非効率的ですが、それが機能する場合は、開発者にとっては十分です。
Jamie Treworgy 2012

同意しますが、それが私が持っていた唯一の選択肢です。私は午後中ずっと、何が問題だったのかを理解しようとして過ごしました。
leftyX 2012

2
私はそれを試しましたが、それでもキャッシュをフラッシュしていません!! 私はそれをクリアし、キャッシュをリセットする必要があるときに起動時とインラインの両方でfalseにResetAll設定しようとしましたがEnableOptimizations、何も起こりません。ああ。
Jamie Treworgy 2012

開発者がこれらのオブジェクトのメソッドに関するワンライナーでさえ簡単なブログ投稿を開始できれば、きっと素晴らしいでしょう:)
Jamie Treworgy 2012

6
したがって、これらのメソッドの機能を説明するために、Scripts.UrlはBundleTable.Bundles.ResolveBundleUrlの単なるエイリアスであり、バンドル以外のURLも解決するため、バンドルについて知っている汎用URLリゾルバーです。Scripts.Renderは、EnableOptimizationsフラグを使用して、バンドルへの参照をレンダリングするか、バンドルを構成するコンポーネントをレンダリングするかを決定します。
ハオクン2012

8

Webファームのシナリオのためにこれを行わないというHaoKungの推奨事項を念頭に置いて、これを実行したいシナリオはたくさんあると思います。解決策は次のとおりです。

BundleTable.Bundles.ResetAll(); //or something more specific if neccesary
var bundle = new Bundle("~/bundles/your-bundle-virtual-path");
//add your includes here or load them in from a config file

//this is where the magic happens
var context = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundle.Path);
bundle.UpdateCache(context, bundle.GenerateBundleResponse(context));

BundleTable.Bundles.Add(bundle);

上記のコードはいつでも呼び出すことができ、バンドルが更新されます。これは、EnableOptimizationsがtrueまたはfalseの場合の両方で機能します。つまり、デバッグシナリオまたはライブシナリオで正しいマークアップがスローされます。

@Scripts.Render("~/bundles/your-bundle-virtual-path")

キャッシングについて少し説明しているここをさらに読んでくださいGenerateBundleResponse
Zac

4

また、再構築せずにバンドルを更新する際に問題が発生しました。理解しておくべき重要なことは次のとおりです。

  • ファイルパスが変更されても、バンドルは更新されません。
  • バンドルの仮想パスが変更された場合、バンドルは更新されます。
  • ディスク上のファイルが変更された場合、バンドルは更新されます。

したがって、動的バンドルを実行している場合は、バンドルの仮想パスがファイルパスに基づくようにするコードを記述できます。ファイルパスをハッシュし、そのハッシュをバンドルの仮想パスの最後に追加することをお勧めします。このように、ファイルパスが変更されると、仮想パスも変更され、バンドルが更新されます。

これが私が問題を解決したコードです:

    public static IHtmlString RenderStyleBundle(string bundlePath, string[] filePaths)
    {
        // Add a hash of the files onto the path to ensure that the filepaths have not changed.
        bundlePath = string.Format("{0}{1}", bundlePath, GetBundleHashForFiles(filePaths));

        var bundleIsRegistered = BundleTable
            .Bundles
            .GetRegisteredBundles()
            .Where(bundle => bundle.Path == bundlePath)
            .Any();

        if(!bundleIsRegistered)
        {
            var bundle = new StyleBundle(bundlePath);
            bundle.Include(filePaths);
            BundleTable.Bundles.Add(bundle);
        }

        return Styles.Render(bundlePath);
    }

    static string GetBundleHashForFiles(IEnumerable<string> filePaths)
    {
        // Create a unique hash for this set of files
        var aggregatedPaths = filePaths.Aggregate((pathString, next) => pathString + next);
        var Md5 = MD5.Create();
        var encodedPaths = Encoding.UTF8.GetBytes(aggregatedPaths);
        var hash = Md5.ComputeHash(encodedPaths);
        var bundlePath = hash.Aggregate(string.Empty, (hashString, next) => string.Format("{0}{1:x2}", hashString, next));
        return bundlePath;
    }

を繰り返し使用する際に、画家のアルゴリズムAggregate固有のSchlemielについて誰かが考えないリスクがあるため、文字列の連結は一般的に避けることをお勧めし+ます。代わりに、を実行してくださいstring.Join("", filePaths)。非常に大きな入力の場合でも、これにはその問題はありません。
ErikE 2016

3

StyleBundleまたはScriptBundle)から派生し、コンストラクターにインクルードを追加せずにオーバーライドしてみましたか?

public override IEnumerable<System.IO.FileInfo> EnumerateFiles(BundleContext context)

私はこれを動的スタイルシートに対して行い、EnumerateFilesはリクエストごとに呼び出されます。それはおそらく最高の解決策ではありませんが、機能します。


0

デッドスレッドを復活させて申し訳ありませんが、Umbracoサイトでバンドルキャッシングで同様の問題が発生しました。ユーザーがバックエンドできれいなバージョンを変更したときに、スタイルシート/スクリプトを自動的に縮小したいと思いました。

私がすでに持っていたコードは(スタイルシートのonSavedメソッドに)ありました:

 BundleTable.Bundles.Add(new StyleBundle("~/bundles/styles.min.css").Include(
                           "~/css/main.css"
                        ));

および(onApplicationStarted):

BundleTable.EnableOptimizations = true;

何を試しても、「〜/ bundles /styles.min.css」ファイルは変更されていないようです。私のページの先頭で、私はもともと次のようにスタイルシートにロードしていました:

<link rel="stylesheet" href="~/bundles/styles.min.css" />

ただし、これを次のように変更することで機能するようになりました。

@Styles.Render("~/bundles/styles.min.css")

Styles.Renderメソッドは、ファイル名の最後にクエリ文字列を取り込みます。これは、上記のHaoで説明されているキャッシュキーであると推測しています。

私にとって、それはそれと同じくらい簡単でした。これが何時間もこれをグーグルしていて、数年前の投稿しか見つけられなかった私のような他の誰かに役立つことを願っています!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.