web-dev-qa-db-ja.com

MVC4でのランタイムの動的なバンドルと縮小

MVC 4に付属の新しい最適化名前空間を使用して、バンドルとミニファイを手伝ってくれる人がいるかどうか疑問に思いました。ユーザーごとの設定に基づいて、どのjsファイルをロードするかを決定したいマルチテナントアプリケーションがあります。 1つのアプローチは、すべてのバンドルを事前に作成し、ユーザーの設定に基づいてresolvebundleurlの仮想パスを変更することですが、それは実際には正しい方法ではないと感じています。また、ユーザー設定に基づいたcshtmlビューに動的cssがあります。これは、実行時に縮小したいと思います。

助言がありますか? Requestreduceをチェックするための他の質問にも多くの反応が見られますが、それらはすべて同じユーザーからのものです。

両方の状況を処理するための最良のアプローチは何でしょうか?

前もって感謝します!

18
Cyril Mestrom

実行できるアプローチの1つは、アプリケーションの起動時にバンドルを動的に構築することです。したがって、スクリプトが_~/scripts_にある場合は、次のことができます。

_Bundle bundle = new Bundle("~/scripts/js", new JsMinify());

if (includeJquery == true) {     
  bundle.IncludeDirectory("~/scripts", "jquery-*");
  bundle.IncludeDirectory("~/scripts", "jquery-ui*");
} 

if (includeAwesomenes == true) {
  bundle.IncludeDirectory("~/scripts", "awesomeness.js");
}

BundleTable.Bundles.Add(bundle);
_

次に、マークアップは次のようになります

@Scripts.Render("~/Scripts/Libs/js")

注:私はsystem.web.optimization(現在はMicrosoft.AspNet.Web.Optimization)の最新のnugetパッケージを使用しています ここ 。スコット・ハンゼルマンはそれについて良い 投稿 を持っています。

12
JaySilk84

私は自分のCSSとJSを動的に縮小するヘルパー関数を書きました

    public static IHtmlString RenderStyles(this HtmlHelper helper, params string[] additionalPaths)
    {
        var page = helper.ViewDataContainer as WebPageExecutingBase;
        if (page != null && page.VirtualPath.StartsWith("~/"))
        {
            var virtualPath = "~/bundles" + page.VirtualPath.Substring(1);
            if (BundleTable.Bundles.GetBundleFor(virtualPath) == null)
            {
                var defaultPath = page.VirtualPath + ".css";
                BundleTable.Bundles.Add(new StyleBundle(virtualPath).Include(defaultPath).Include(additionalPaths));
            }
            return MvcHtmlString.Create(@"<link href=""" + HttpUtility.HtmlAttributeEncode(BundleTable.Bundles.ResolveBundleUrl(virtualPath)) + @""" rel=""stylesheet""/>");
        }
        return MvcHtmlString.Empty;
    }

    public static IHtmlString RenderScripts(this HtmlHelper helper, params string[] additionalPaths)
    {
        var page = helper.ViewDataContainer as WebPageExecutingBase;
        if (page != null && page.VirtualPath.StartsWith("~/"))
        {
            var virtualPath = "~/bundles" + page.VirtualPath.Substring(1);
            if (BundleTable.Bundles.GetBundleFor(virtualPath) == null)
            {
                var defaultPath = page.VirtualPath + ".js";
                BundleTable.Bundles.Add(new ScriptBundle(virtualPath).Include(defaultPath).Include(additionalPaths));
            }
            return MvcHtmlString.Create(@"<script src=""" + HttpUtility.HtmlAttributeEncode(BundleTable.Bundles.ResolveBundleUrl(virtualPath)) + @"""></script>");
        }
        return MvcHtmlString.Empty;
    }

使用法

〜/ views/Home/Test1.cshtml

〜/ Views/Home/Test1.cshtml.css

〜/ Views/Home/Test1.cshtml.js

test1.cshtmlで

@model object
@{
   // init
}@{

}@section MainContent {
  {<div>@{
     if ("work" != "fun")
     {
        {<hr/>}
     }
  }</div>}
}@{

}@section Scripts {@{
  {@Html.RenderScripts()}
}@{

}@section Styles {@{
  {@Html.RenderStyles()}
}}

しかし、ofcoz、私は私のスクリプト、スタイルのほとんどを〜/ Scripts/。js、〜/ Content /。cssに入れました

appp_Startに登録します

8
Steven Chong

動的バンドルのサポートを早い段階で検討しましたが、そのアプローチの基本的な問題は、マルチサーバーシナリオ(つまりクラウド)が機能しないことです。すべてのバンドルが事前に定義されていない場合、ページ要求を処理したサーバーとは異なるサーバーに送信されるバンドル要求は404応答を受け取ります(バンドル定義はページ要求を処理したサーバーにのみ存在するため)。結果として、すべてのバンドルを事前に作成することをお勧めします。これがメインラインのシナリオです。バンドルの動的構成も機能する可能性がありますが、それは完全にサポートされているシナリオではありません。

5
Hao Kung

更新:それが重要かどうかはわかりませんが、MVC5.2.3とVisualStudio 2015を使用していますが、質問は少し古いです。

ただし、_viewStart.cshtmlで機能する動的バンドルを作成しました。私がしたことは、バンドルの辞書にバンドルを格納するヘルパークラスを作成したことです。次に、アプリの起動時に辞書からそれらを引き出して登録します。そして、静的なboolen「bundlesInitialzed」を作成して、バンドルが辞書に1回だけ追加されるようにしました。

ヘルパーの例:

public static class KBApplicationCore: .....
{
    private static Dictionary<string, Bundle> _bundleDictionary = new Dictionary<string, Bundle>();
    public static bool BundlesFinalized { get { return _BundlesFinalized; } }
    /// <summary>
    /// Add a bundle to the bundle dictionary
    /// </summary>
    /// <param name="bundle"></param>
    /// <returns></returns>
    public static bool RegisterBundle(Bundle bundle)
    {
        if (bundle == null)
            throw new ArgumentNullException("bundle");
        if (_BundlesFinalized)
            throw new InvalidOperationException("The bundles have been finalized and frozen, you can only finalize the bundles once as an app pool recycle is needed to change the bundles afterwards!");
        if (_bundleDictionary.ContainsKey(bundle.Path))
            return false;
        _bundleDictionary.Add(bundle.Path, bundle);
        return true;
    }
    /// <summary>
    /// Finalize the bundles, which commits them to the BundleTable.Bundles collection, respects the web.config's debug setting for optimizations
    /// </summary>
    public static void FinalizeBundles()
    {
        FinalizeBundles(null);
    }
    /// <summary>
    /// Finalize the bundles, which commits them to the BundleTable.Bundles collection
    /// </summary>
    /// <param name="forceMinimize">Null = Respect web.config debug setting, True force minification regardless of web.config, False force no minification regardless of web.config</param>
    public static void FinalizeBundles(bool? forceMinimize)
    {
        var bundles = BundleTable.Bundles;
        foreach (var bundle in _bundleDictionary.Values)
        {
            bundles.Add(bundle);
        }
        if (forceMinimize != null)
            BundleTable.EnableOptimizations = forceMinimize.Value;
        _BundlesFinalized = true;
    }        
}

例_ViewStart.cshtml

@{

    var bundles = BundleTable.Bundles;
    var baseUrl = string.Concat("~/App_Plugins/", KBApplicationCore.PackageManifest.FolderName, "/");
    //Maybe there is a better way to do this, the goal is to make the bundle configurable without having to recompile the code
    if (!KBApplicationCore.BundlesFinalized)
    {
        //Note, you need to reset the application pool in order for any changes here to be reloaded as the BundlesFinalized property is a static field that will only reset to false when the app restarts.
        Bundle mainScripts = new ScriptBundle("~/bundles/scripts/main.js");
        mainScripts.Include(new string[] {
            baseUrl + "Assets/lib/jquery/jquery.js",
            baseUrl + "Assets/lib/jquery/plugins/jqcloud/jqcloud.js",
            baseUrl + "Assets/lib/bootstrap/js/bootstrap.js",            
            baseUrl + "Assets/lib/bootstrap/plugins/treeview/bootstrap-treeview.js",   
            baseUrl + "Assets/lib/angular/angular.js",
            baseUrl + "Assets/lib/ckEditor/ckEditor.js"      
        });
        KBApplicationCore.RegisterBundle(mainScripts);

        Bundle appScripts = new ScriptBundle("~/bundles/scripts/app.js");
        appScripts.Include(new string[] {
            baseUrl + "Assets/app/app.js",
            baseUrl + "Assets/app/services/*.js",
            baseUrl + "Assets/app/directives/*.js",
            baseUrl + "Assets/app/controllers/*.js"
        });
        KBApplicationCore.RegisterBundle(appScripts);

        Bundle mainStyles = new StyleBundle("~/bundles/styles/main.css");
        mainStyles.Include(new string[] {
           baseUrl + "Assets/lib/bootstrap/build/less/bootstrap.less",
           baseUrl + "Assets/lib/bootstrap/plugins/treeview/bootstrap-treeview.css",   
           baseUrl + "Assets/lib/ckeditor/contents.css",
           baseUrl + "Assets/lib/font-awesome/less/font-awesome.less",
           baseUrl + "Assets/styles/tlckb.less"
        });
        mainStyles.Transforms.Add(new BundleTransformer.Core.Transformers.CssTransformer());
        mainStyles.Transforms.Add(new CssMinify());
        mainStyles.Orderer = new BundleTransformer.Core.Orderers.NullOrderer();
        KBApplicationCore.RegisterBundle(mainStyles);


        KBApplicationCore.FinalizeBundles(true); //true = Force Optimizations, false = Force non Optmizations, null = respect web.config which is the same as calling the parameterless constructor.
    }
}

注:これは、スレッドロックを使用して、最初のリクエストが終了する前に2つのリクエストがバンドルコードに入らないように更新する必要があります。

これが機能する方法は、アプリプールのリセット後、サイトへの最初のリクエストでビューの開始が実行されることです。ヘルパーでRegisterBundleを呼び出し、RegisterBundleが呼び出された順序でScriptBundleまたはStyleBundleをディクショナリに渡します。

FinalizeBundlesが呼び出されると、trueを指定して、web.configデバッグ設定に関係なく最適化を強制するか、nullのままにするか、そのパラメーターなしでコンストラクターを使用してweb.config設定を尊重することができます。 falseを渡すと、デバッグがtrueであっても、最適化を使用しないように強制されます。 FinalizeBundlesバンドルをbundlesテーブルに登録し、の_BundlesFinalizedをtrueに設定します。

確定すると、RegisterBundleを再度呼び出そうとすると例外がスローされ、その時点でフリーズします。

この設定により、新しいバンドルを追加して開始を表示し、アプリプールをリセットしてそれらを有効にすることができます。私がこれを書いた当初の目標は、他の人が使用するものを作成しているため、バンドルを変更するためにソースを再構築することなく、フロントエンドUIを完全に変更できるようにしたかったからです。

0
Ryan Mann