您好,登錄后才能下訂單哦!
由于默認的 ASP.NET MVC 模板使用了 Bundle 技術,大家開始接受并喜歡上這種技術。Bundle 技術通過 Micorosoft.AspNet.Web.Optimization 包實現,如果在 ASP.NET WebForm 項目中引入這個包及其依賴包,在 ASP.NET WebForm 項目中使用 Bundle 技術也非常容易。
關于在 WebForm 中使用 Bundle 技術的簡短說明
通過 NuGet 很容易在 WebForm 項目中引入
Microsoft.AspNet.Web.Optimization
包及其依賴包。不過在 MVC 項目的 Razor 頁面中可以使用類似下面的語句引入資源@Scripts.Render("...")而在
*.aspx
頁面中則需要通過<%= %>
來引入了:<%@ Import Namespace="System.Web.Optimization" %> // ... <%= Scripts.Render("...") %>備注 有些資料中是使用的
<%: %>
,我實在沒有發現它和<%= %>
有啥區別,但至少我在《ASP.NET Reference》的《Code Render Blocks》一節找到了<%= %>
,卻暫時沒在官方文檔里找到<%: %>
然后,我在一個使用了 EasyUI 的項目中使用了 Bundle 技術。才開始一切正常,至到第一個 Release 版本測試的那一天,“血案”發生了——
由于一個腳本錯誤,EasyUI 沒有生效。最終原因是 Bunlde 在 Release 版中將 EasyUI 的腳本壓縮了——當然,定位到這個原因還是經歷了一翻周折,這就不細說了。
這個解決方案理論上只需要在配置里加一句話就行:
BundleTable.EnableOptimizations = false;
但問題在于,這樣一來,為了一個 EasyUI,就放棄了所有腳本的壓縮,而僅僅只是合并,效果折半,只能當作萬不得已的備選。
先看看原本的 Bundle 配置(已簡化)
public static void Register(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/libs") .Include("~/scripts/jquery-{version}.js") .Include("~/scripts/jquery.eaysui-{versoin}.js") .Include("~/scripts/locale/easyui-lang-zh_CN.js") .IncludeDirectory("~/scripts/app", "*.js", true) ); }
這段配置先引入了 jquery,再引入了 easyui,最后引入了一些為當前項目寫的公共腳本。為了實現解決方案二,必須要改成分三個 Bundle 引入,同時還得想辦法阻止壓縮其中一個 Bundle。
要分段,簡單
public static void Register(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/jquery") .Include("~/scripts/jquery-{version}.js") ); bundles.Add(new ScriptBundle("~/easyui") .Include("~/scripts/jquery.eaysui-{versoin}.js") .Include("~/scripts/locale/easyui-lang-zh_CN.js") ); bundles.Add(new ScriptBundle("~/libs") .IncludeDirectory("~/scripts/app", "*.js", true) ); }
但為了阻止壓縮,查了文檔,也搜索了不少資料都沒找到解決辦法,所以只好看源碼分析了,請出 JetBrains dotPeek。分析代碼之后得出結論,只需要去掉默認的 Transform 就行
// bundles.Add(new ScriptBundle("~/easyui") // .Include("~/scripts/jquery.eaysui-{versoin}.js") // .Include("~/scripts/locale/easyui-lang-zh_CN.js") // ); Bundle easyuiBundle = new ScriptBundle("~/easyui") .Include("~/scripts/jquery.eaysui-{versoin}.js") .Include("~/scripts/locale/easyui-lang-zh_CN.js") ); easyuiBundle.Transforms.Clear(); bundles.Add(easyuiBundle);
關鍵代碼的分析說明
首先從 ScriptBunlde 入手
public class ScriptBundle: Bundle { public ScriptBundle(string virtualPath) : this(virtualPath, (string) null) {} public ScriptBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath, (IBundleTransform) new JsMinify() ) { this.ConcatenationToken = ";" + Environment.NewLine; } }
可以看出,ScriptBunlde 的構建最終是通過其基類 Bunlde 中帶 IBunldeTransform 參數的那一個來構造的。再看 Bunlde 的關鍵代碼
public class Bunlde public IList<IBundleTransform> Transforms { get { return this._transforms; } } public Bundle( string virtualPath, string cdnPath, params IBundleTransform[] transforms ) { // ... foreach(IBundleTransform bundleTransform in transforms) { this._transforms.Add(bundleTransform); } } }
容易理解,ScriptBunlde 構建的時候往 Transforms 中添加了一默認的 Transform——JsMinify,從名字就可以看出來,這是用來壓縮腳本的。而 IBundleTransform 只有一個接口方法
public interface IBundleTransform { void Process(BundleContext context, BundleResponse response); }
看樣子它是在處理 BundleResponse。而 BundleResponse 中定義有文本類型的 Content 和 ContentType 屬性,以及一個 IEnumerable<BundleFile> Files。
為什么是 Files 而不是 File 呢,我猜 Content 中包含的是一個 Bundle 中所有文件的內容,而不是某一個文件的內容。要驗證也很容易,自己實現個 IBundleTransform 試下就行了
Bundle b = new ScriptBundle("~/test") .Include(...) .Include(...); b.Transforms.Clear();b.Transforms.Add(new MyTransform()) // MyTransform 可以自由發揮,我其實啥都沒寫,只是在 Process 里打了個斷點,檢查了 response 的屬性值而已
實驗證明在 BundleResponse 傳入 Transforms 之前,其 Content 就已經有所有引入文件的內容了。
方案二解決了方案一不能解決的問題,但同時也帶來了新問題。原來只需要一句話就能引入所有腳本
@Scripts.Render("~/libs")
而現在需要 3 句話
@Scripts.Render("~/jquery") @Scripts.Render("~/easyui") @Scripts.Render("~/libs")
鑒于方案二帶來的新問題,試想,如果有一個東西,能把 3 個 Bundle 對象組合起來,變成一個 Bundle 對象,豈不是就解決了?
于是,我發明了 Bundle 的 Bundle,不妨就叫 BundleBundle 吧。
public class BundleBundle : Bundle{ readonly List<Bundle> bundles = new List<Bundle>(); public BundleBundle(string virtualPath) : base(virtualPath) { } public BundleBundle Include(Bundle bundle) { bundles.Add(bundle); return this; } // 在引入 Bundle 對象時申明清空 Transforms,這幾乎就是為 EasyUI 準備的 public BundleBundle Include(Bundle bundle, bool isClearTransform) { if (isClearTransform) { bundle.Transforms.Clear(); } bundles.Add(bundle); return this; } public override BundleResponse GenerateBundleResponse(BundleContext context) { List<BundleFile> allFiles = new List<BundleFile>(); StringBuilder content = new StringBuilder(); string contentType = null; foreach (Bundle b in bundles) { var r = b.GenerateBundleResponse(context); content.Append(r.Content); // 考慮到 BundleBundle 可能用于 CSS,所以這里進行一次判斷, // 只在 ScriptBundle 后面加分號(兼容 ASI 風格腳本) // 這里可能會出現在已有分號的代碼后面加分號的情況, // 考慮到只會浪費 1 個字節,忍了 if (b is ScriptBundle) { content.Append(';'); } content.AppendLine(); allFiles.AddRange(r.Files); if (contentType == null) { contentType = r.ContentType; } } var response = new BundleResponse(content.ToString(), allFiles); response.ContentType = contentType; return response; } }
使用 BundleBundle 也簡單,就像這樣
bundles.Add(new BundleBundle("~/libs") .Include(new ScriptBundle("~/bundle/jquery") .Include("~/scripts/jquery-{version}.js") ) .Include( new ScriptBundle("~/bundle/easyui") .Include("~/scripts/jquery.easyui-{version}.js") .Include("~/scripts/locale/easyui-lang-zh_CN.js") ) .Include(new ScriptBundle("~/bundle/app") .IncludeDirectory("~/scripts/app", "*.js", true) ) );
然后
@Scripts.Render("~/libs")
注意,每個子 Bundle 都有名字,但這些名字不能直接給 @Scripts.Render() 使用,因為它們并沒有直接加入 BundleTable.Bundles 中。但名字是必須的,而且不能是 null,不信就試試。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。