19

我想知道是否有人可以帮助我使用 MVC 4 附带的新优化命名空间进行捆绑和缩小。我有一个多租户应用程序,我想根据每个用户的设置来决定应该加载哪些 js 文件。一种方法是预先创建所有捆绑包并根据用户的设置更改 resolvebundleurl 的虚拟路径,但这感觉不是正确的方法。此外,我在基于用户设置的 cshtml 视图中有动态 css,我希望在运行时将其缩小。

有什么建议么?我还在其他问题中看到很多关于查看 Requestreduce 的反应,但它们都来自同一个用户。

处理这两种情况的最佳方法是什么?

提前致谢!

4

4 回答 4

12

您可以采用的一种方法是在应用程序启动时动态构建捆绑包。因此,如果您的脚本位于~/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 包。Scott Hanselman 有一篇关于它的好帖子。

于 2012-06-02T04:09:00.033 回答
9

我写了一个辅助函数来动态缩小我的 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,我把我的大部分 sripts、样式放在 ~/Scripts/ .js、~/Content/ .css

并在 Appp_Start 中注册它们

于 2014-07-26T06:57:25.100 回答
5

我们很早就考虑过支持动态捆绑包,但这种方法的根本问题是多服务器场景(即云)不起作用。如果未预先定义所有捆绑包,则任何发送到与提供页面请求的服务器不同的服务器的捆绑包请求都将获得 404 响应(因为捆绑包定义仅存在于处理页面请求的服务器上)。因此,我建议预先创建所有捆绑包,这是主线方案。捆绑包的动态配置也可以工作,但这不是完全支持的方案。

于 2012-06-15T23:22:15.083 回答
0

更新:不确定是否重要,但我使用的是 MVC 5.2.3 和 Visual Studio 2015,问题有点老了。

但是,我制作了在 _viewStart.cshtml 中工作的动态捆绑。我所做的是创建了一个帮助类,将捆绑包存储在捆绑包字典中。然后在应用程序启动时,我从字典中提取它们并注册它们。我制作了一个静态布尔“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'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,并按照调用 RegisterBundles 的顺序将 ScriptBundle 或 StyleBundle 传递给字典。

当 FinalizeBundles 被调用时,您可以指定 True ,无论 web.config 调试设置如何,都将强制优化,或者将其保留为 null 或使用没有该参数的构造函数以使其尊重 web.config 设置。传递 false 将强制它不使用优化,即使 debug 为 true。FinalizeBundles 在捆绑表中注册捆绑包并将 _BundlesFinalized 设置为 true。

一旦完成,再次调用 RegisterBundle 的尝试将引发异常,此时它被冻结。

此设置允许您添加新捆绑包以查看启动并重置应用程序池以使其生效。我写这篇文章的最初目标是因为我正在制作其他人会使用的东西,所以我希望他们能够完全更改前端 UI,而无需重建源代码来更改捆绑包。

于 2015-08-27T00:43:16.237 回答