Подтвердить что ты не робот

Динамическое связывание и минимизация времени выполнения в MVC 4

Мне было интересно, сможет ли кто-нибудь помочь мне связать и минимизировать использование нового пространства имен для оптимизации, поставляемого с MVC 4. У меня есть приложение Multitenant, в котором я хочу решить, какие js файлы должны быть загружены на основе настроек для каждого пользователя. Одним из подходов было бы создание всех пакетов вперед и изменение виртуального пути resolvebundleurl на основе настройки пользователя, но это не совсем правильно. Также у меня есть динамический css в представлении cshtml, основанный на пользовательских настройках, которые я хотел бы минимизировать во время выполнения.

Любые предложения? Я также вижу много реакций в других вопросах, чтобы проверить Requestreduce, но все они от одного и того же пользователя.

Каким будет лучший подход для обработки обеих ситуаций?

Спасибо заранее!

4b9b3361

Ответ 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")

Примечание. Я использую последний пакет nuget для system.web.optimization(теперь Microsoft.AspNet.Web.Optimization), расположенный здесь. Скотт Гензельман имеет хороший пост об этом.

Ответ 2

Я написал вспомогательную функцию для динамического сокращения моих 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()}
}}

но tocoz, я ставил большинство своих сценариев, стилей в ~/Scripts/.js, ~/Content/.css

и зарегистрируйте их в Appp_Start

Ответ 3

Мы рассмотрели возможность поддержки динамических пакетов на раннем этапе, но основная проблема с этим подходом - многосерверные сценарии (т.е. облако) не будут работать. Если все пакеты не определены заранее, любые запросы пакетов, которые отправляются на другой сервер, чем тот, который обслуживал запрос страницы, получат 404 ответ (поскольку определение пакета будет существовать только на сервере, который обрабатывал запрос страницы). В результате я бы предложил создать все пакеты впереди, что основной сценарий. Динамическая конфигурация пакетов также может работать, но это не полностью поддерживаемый сценарий.

Ответ 4

Обновление: не уверен, имеет значение, но я использую MVC 5.2.3 и Visual Studio 2015, вопрос немного старый.

Однако я сделал динамическое связывание, которое работает в _viewStart.cshtml. То, что я сделал, я сделал класс-помощник, который хранит связки в словаре пакетов. Затем в начале приложения я вытаскиваю их из словаря и регистрирую их. И я сделал статический boolen "bundlesInitialzed", так что связки только добавляют словаря один раз.

Пример помощника:

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 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 запроса вводили код пакета до того, как первый из них завершит.

Как это работает, запуск начинается с первого запроса на сайт после пула приложений reset. Он вызывает RegisterBundle в помощнике и передает ScriptBundle или StyleBundle в словарь в порядке, который вызывается RegisterBundles.

Когда вызывается FinalizeBundles, вы можете указать True, который будет принудительно оптимизировать независимо от настройки debug web.config или оставить его null или использовать конструктор без этого параметра, чтобы он уважал настройку web.config. Передача false заставит его использовать оптимизацию, даже если debug is true. FinalizeBundles Регистрирует пакеты в таблице пакетов и устанавливает _BundlesFinalized в true.

После финализации попытка вызвать RegisterBundle снова вызовет исключение, оно будет заморожено в этой точке.

Эта настройка позволяет вам добавлять новые пакеты для просмотра start и reset пула приложений, чтобы они вступили в силу. Первоначальная цель, которую я писала, состояла в том, что я делаю что-то, что другие будут использовать, поэтому я хотел, чтобы они могли полностью изменить интерфейс интерфейса пользователя, не перестраивая источник, чтобы изменить пакеты.