tag net mvc bootstrap attribute asp asp.net asp.net-mvc-4 optimization bundle asp.net-optimization

asp.net - net - Runtime agrupamiento dinámico y minimización en MVC 4



select asp.net core (4)

Me preguntaba si alguien puede ayudarme a agrupar y minimizar el nuevo espacio de nombres de optimización incluido con MVC 4. Tengo una aplicación Multitenant en la que quiero decidir qué archivos js deben cargarse según la configuración por usuario. Un enfoque sería crear todos los paquetes por adelantado y cambiar la ruta de acceso virtual de resolviendo en base a la configuración del usuario, pero esa no es la manera correcta. También tengo css dinámico en una vista cshtml basada en la configuración de usuario, que me gustaría tener minimizado en tiempo de ejecución.

¿Alguna sugerencia? También veo muchas reacciones en otras preguntas para revisar Requestreduce, pero todas son del mismo usuario.

¿Cuál sería el mejor enfoque para manejar ambas situaciones?

¡Gracias por adelantado!


Actualización: No estoy seguro de si es importante pero estoy usando MVC 5.2.3 y Visual Studio 2015, la pregunta es un poco antigua.

Sin embargo, hice un agrupamiento dinámico que funciona en _viewStart.cshtml. Lo que hice fue crear una clase de ayuda que almacena paquetes en un diccionario de paquetes. Luego, al inicio de la aplicación, los saco del diccionario y los registro. E hice un "paquete inicializado" boolen estático para que los paquetes solo se agreguen al diccionario una vez.

Ejemplo de ayuda:

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; } }

Ejemplo _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. } }

Nota: Esto debe actualizarse para utilizar el bloqueo de subprocesos para evitar que 2 solicitudes ingresen el código del paquete antes de que salga la primera.

La forma en que funciona es que el inicio de la vista se ejecuta en la primera solicitud al sitio después de un reinicio del grupo de aplicaciones. Llama a RegisterBundle en el helper y pasa ScriptBundle o StyleBundle al diccionario en el orden en que se llama a RegisterBundles.

Cuando se llama a FinalizeBundles, puede especificar Verdadero que forzará las optimizaciones independientemente de la configuración de depuración web.config, o dejarlo en blanco o usar el constructor sin ese parámetro para que respete la configuración web.config. Pasar falso lo obligará a no usar optimización incluso si la depuración es verdadera. FinalizeBundles Registra los lotes en la tabla de lotes y establece _BundlesFinalized a true.

Una vez finalizado, un intento de llamar a RegisterBundle nuevamente lanzará una excepción, se congela en ese punto.

Esta configuración le permite agregar nuevos paquetes para ver el inicio y restablecer el grupo de aplicaciones para que tengan efecto. El objetivo original que tenía al escribir esto era porque estoy haciendo algo que otros usarán, así que quise que pudieran cambiar por completo la interfaz de usuario de front-end sin tener que reconstruir la fuente para cambiar los paquetes.


Consideramos que admitir paquetes dinámicos desde el principio, pero el problema fundamental con ese enfoque es que los escenarios de múltiples servidores (es decir, la nube) no funcionarán. Si todos los paquetes no están definidos de antemano, todas las solicitudes de paquetes que se envíen a un servidor diferente al que atendió la solicitud de la página obtendrán una respuesta 404 (ya que la definición del paquete solo existiría en el servidor que manejó la solicitud de la página). Como resultado, sugeriría crear todos los paquetes por adelantado, ese es el escenario de la línea principal. La configuración dinámica de paquetes también podría funcionar, pero ese no es un escenario totalmente compatible.


Escribí una función auxiliar para minimizar mi css y 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; }

uso

~ / views / Home / Test1.cshtml

~ / Vistas / Inicio / Test1.cshtml.css

~ / Vistas / Inicio / Test1.cshtml.js

en Test1.cshtml

@model object @{ // init }@{ }@section MainContent { {<div>@{ if ("work" != "fun") { {<hr/>} } }</div>} }@{ }@section Scripts {@{ {@Html.RenderScripts()} }@{ }@section Styles {@{ {@Html.RenderStyles()} }}

pero ofcoz, puse la mayoría de mis sripts, estilos en ~ / Scripts / .js, ~ / Content / .css

y registralos en Appp_Start


Un enfoque que puede tomar es construir el paquete dinámicamente cuando se inicia la aplicación. Entonces, si tus scripts se encuentran en ~/scripts , puedes hacerlo:

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);

Entonces tu marca puede verse así

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

Nota: Estoy usando el último paquete nuget para system.web.optimization (ahora Microsoft.AspNet.Web.Optimization) que se encuentra here . Scott Hanselman tiene una buena post respecto.