[ASP.NET MVC 小牛之路]17 - 捆綁(Bundle)

Liam Wang發表於2013-11-25

本文介紹 MVC 4 提供的一個新特性:捆綁(Bundle),一個在  View 和 Layout 中用於組織優化瀏覽器請求的 CSS 和 JavaScript 檔案的技術。

本文目錄

瞭解VS預設加入的指令碼庫

當我們建立一個基本模板的 MVC 工程時,VS在Scripts資料夾中預設加入了一些 JavaScript 指令碼庫。下面是這些指令碼庫的簡單介紹:

  • jquery-1.8.2.js,這個就不用解釋了。
  • jquery-ui-1.8.24.js在jQuery 基礎上的一套介面工具,包括了網頁上常見的很多外掛和動畫特效。
  • jquery.validate.js,用於驗證使用者在表單內input元素輸入的資料。
  • knockout-2.2.0.js是一個輕量級的UI類庫,通過應用MVVM模式使JavaScript前端UI簡單化,更多:http://knockoutjs.com/documentation/introduction.html
  • modernizr-2.6.2.js,一個開源的JS庫,它使得那些基於訪客瀏覽器的不同(指對新標準支援性的差異)而開發不同級別體驗的設計師的工作變得更為簡單。它使得設計師可以在支援HTML5和CSS3的瀏覽器中充分利用HTML5和CSS3的特性進行開發,同時又不會犧牲其他不支援這些新技術的瀏覽器的控制。更多:http://www.mhtml5.com/2011/03/676.html 。
  • jquery.unobtrusive-ajax.js,MVC 框架中使用 Unobtrusive Ajax 的庫,更多:[ASP.NET MVC 小牛之路]14 - Unobtrusive Ajax 。
  • jquery.validate.unobtrusive.js,基於 jquery.unobtrusive-ajax.js,更多:[ASP.NET MVC 小牛之路]15 - Model Binding 。

另外還有一個 _references.js 檔案,它的作用是通過下面這種方式放入該檔案中的JS檔案可以被VS智慧感知:

/// <reference path="jquery-1.8.2.js" />
/// <reference path="jquery-ui-1.8.24.js" />

相關小技巧:在VS中讓一個JS檔案智慧提示另一個JS檔案中的成員 。

在實際的專案中,我們可能遠遠不止引入上面這些指令碼檔案,MVC 4提供的“捆綁”新功能可以很方便地對引入的指令碼檔案進行管理。

準備工作

選擇基本模板建立一個MVC工程。和前一篇的示例差不多,建立一個名為 Appointment 的 Model,程式碼如下:

public class Appointment {
    [Required]
    public string ClientName { get; set; }

    [DataType(DataType.Date)]
    public DateTime Date { get; set; }

    public bool TermsAccepted { get; set; }
}
Appointment

新增一個 HomeController,程式碼如下:

public class HomeController : Controller {
    public ViewResult MakeBooking() {
        return View();
    }

    [HttpPost]
    public JsonResult MakeBooking(Appointment appt) {
        return Json(appt, JsonRequestBehavior.AllowGet);
    }
}
HomeController

給MakeBooking action新增一個 MakeBooking.cshtml 檢視,程式碼如下:

@model MvcApplication1.Models.Appointment
@{
    AjaxOptions ajaxOpts = new AjaxOptions {
        OnSuccess = "processResponse"
    };
}
<h4>Book an Appointment</h4>

<script src="~/Scripts/Home/MakeBooking.js"></script>

<div id="formDiv" class="visible">
    @using (Ajax.BeginForm(ajaxOpts)) {
        @Html.ValidationSummary(true)
        <p>@Html.ValidationMessageFor(m => m.ClientName)</p>
        <p>Your name: @Html.EditorFor(m => m.ClientName)</p>
        <p>@Html.ValidationMessageFor(m => m.Date)</p>
        <p>Appointment Date: @Html.EditorFor(m => m.Date)</p>
        <p>@Html.ValidationMessageFor(m => m.TermsAccepted)</p>
        <p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions</p>
        <input type="submit" value="Make Booking" />
    }
</div>
<div id="successDiv" class="hidden">
    <h4>Your appointment is confirmed</h4>
    <p>Your name is: <b id="successClientName"></b></p>
    <p>The date of your appointment is: <b id="successDate"></b></p>
    <button id="backButton">Back</button>
</div>

把該檢視需要的 JavaScript 程式碼放在一個單獨的檔案 MakeBooking.js 中,並將該JS檔案放在 /Scripts/Home 資料夾下,該JS檔案程式碼如下:

function processResponse(appt) { 
    $('#successClientName').text(appt.ClientName);
    $('#successDate').text(processDate(appt.Date));
    switchViews();
}
function processDate(dateString) {
    var date = new Date(parseInt(dateString.substr(6, dateString.length - 8)));
    return date.toLocaleDateString();
}
function switchViews() {
    var hidden = $('.hidden');
    var visible = $('.visible');
    hidden.removeClass("hidden").addClass("visible");
    visible.removeClass("visible").addClass("hidden");
}
$(document).ready(function () {
    $('#backButton').click(function (e) {
        switchViews();
    });
});
MakeBooking.js

注意,這裡需要對後臺通過Json方法返回的日期進行處理,Json 方法返回的日期格式是:/Date(1385308800000)/,所以把它呈現給客戶端時用 processDate JS方法處理了一下。

在 Content 資料夾下新增一個樣式檔案 CustomStyles.css,程式碼如下:

div.hidden { display: none;} 
div.visible { display: block;}

最後清理一下 _Layout.cshtml 檔案,如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderBody()
</body>
</html>
_Layout.cshtml

本文將關心的是 JS 和 CSS 檔案的引用,大家不用關心程式碼本身,此時只需Copy好程式碼,一會使用捆綁讓它執行起來。 

使用捆綁管理指令碼和樣式檔案

以前我們引入指令碼和樣式檔案的時候,都是一個個的引用,看起來一大坨,不小心還會弄錯先後次序,管理很是不便。而且很多指令碼庫有普通和 min 兩個版本,開發的時候我們引入普通版本以方便除錯,釋出的時候又換成min版本以減少網路頻寬,很是麻煩。為此,MVC 4 增加了一個新功能:“捆綁”,它的作用是把一類指令碼或樣式檔案捆綁在一起,在需要用的時候呼叫一句程式碼就行,極大地方便了指令碼和樣式檔案的管理;而且可以把指令碼的普通和 min 兩個版本都捆綁起來,MVC也會根據是否為Debug模式智慧地選擇指令碼檔案的版本。下面我們來看看這個捆綁功能的使用。

用捆綁方便之一是可以在 /App_Start/BundleConfig.cs 中通過註冊來統一管理指令碼和樣式檔案。我們可以開啟 BundleConfig.cs 檔案看看VS 已經預設對指令碼和樣式檔案的捆綁情況。為了演示如何使用捆綁,我們把VS預設的捆綁程式碼刪除,再增加我們需要的捆綁,如下所示:

public class BundleConfig {
    public static void RegisterBundles(BundleCollection bundles) {
        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/*.css"));

        bundles.Add(new ScriptBundle("~/bundles/clientfeaturesscripts").Include(
            "~/Scripts/jquery-{version}.js",
            "~/Scripts/jquery.validate.js",
            "~/Scripts/jquery.validate.unobtrusive.js",
            "~/Scripts/jquery.unobtrusive-ajax.js"));
    }
}

捆綁是通過 RegisterBundles 引數物件的 Add 方法新增。Add 方法的引數需要一個ScriptBundle 類 或 StyleBundle 類的例項物件, 指令碼檔案用的是 ScriptBundle 類,樣式檔案用的是 StyleBundle 類,它們的構造引數代表著捆綁在一起的檔案的引用。 Include 方法用於包含具體要捆綁的檔案。其中的 {version} 是檔案版本的佔位符,MVC會在相應的目錄下定位到最新的一個版本檔案。

使用捆綁方便之二是再也不用引入一大坨的檔案了,如下面的 _Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
</head>
<body>
    @Scripts.Render("~/bundles/clientfeaturesscripts")
    @RenderBody()
</body>
</html>

這裡通過 @Scripts.Render 和 @Styles.Render 兩個Helper方法新增捆綁。需要注意的是,MakeBooking.cshtml 檔案引入的 MakeBooking.js 是基於 jQuery的,所以 _Layout.cshtml的 @Scripts.Render("~/bundles/clientfeaturesscripts") 必須放在 @RenderBody() 之前。

使用指令碼 Section

上面我們提到在 _Layout.cshtml 中,@Scripts.Render("~/bundles/clientfeaturesscripts") 必須放在 @RenderBody() 之前,因為View引入的JS是基本jQuery的。有時候由於某種需求或個人的喜好要把 @RenderBody() 放在 @Scripts.Render("~/bundles/clientfeaturesscripts") 之前,如果這樣的話,MakeBooking.js 檔案就在jQuery庫之前引用了,顯然JS會報錯。

對於這種情況,我們可以用 [ASP.NET MVC 小牛之路]12 - Section、Partial View 和 Child Action 文章介紹的Section來解決這種JS引用次序的問題。如下,我們可以把 MakeBooking.cshtml 中引入JS的部分用section包起來:

...
<h4>Book an Appointment</h4> 
@section scripts { 
    <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></script> 
} 
...

然後我們就可以在 _Layout.cshtml 所有Render方法後面使用  @RenderSection("scripts", required: false) 方法引入MakeBooking.js 檔案,這樣就不用關心在 _Layout.cshtml 中的 @RenderBody() 和 @Scripts.Render("~/bundles/clientfeaturesscripts") 的先後次序了。如下所示:

<body> 
    @RenderBody() 
    @Scripts.Render("~/bundles/clientfeaturesscripts") 
    @RenderSection("scripts", required: false) 
</body> 

這樣保證了 MakeBooking.js 一定在jQuery庫檔案之後引用。

使用捆綁帶來的改變

捆綁除了可以方便地管理指令碼和樣式檔案,還可以給網路減少頻寬。

以下是在Debug模式下使用捆綁MVC生成引用部分的程式碼:

<link href="/Content/CustomStyles.css" rel="stylesheet"/>
<link href="/Content/Site.css" rel="stylesheet"/>
...
<script src="/Scripts/jquery-1.8.2.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
...
<script src="/Scripts/Home/MakeBooking.js"></script>

總共 7 個檔案,在Debug模式下使用捆綁和不使用捆綁沒什麼區別。

下面我們來比較一下在釋出模式下不使用捆綁和使用捆綁兩者使用頻寬的情況。

在 Web.config 中把調式模式關閉,如下:

...
<system.web>
    <httpRuntime targetFramework="4.5" />
    <compilation debug="false" targetFramework="4.5" />
...

我們先來看看不使用捆綁使用頻寬的情況,為此,我們 MakeBooking.cshtml 中的section部分 和 _Layout.cshtml 中的引用捆綁的部分註釋掉。並在 _Layout.cshtml 中引入上面 7 個檔案,如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @*@Styles.Render("~/Content/css")*@
    <link href="~/Content/CustomStyles.css" rel="stylesheet"/>    
    <link href="~/Content/Site.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.8.2.js"></script>
    <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
    <script src="~/Scripts/jquery.validate.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
    <script src="~/Scripts/Home/MakeBooking.js"></script>
</head>
<body>
    @RenderBody()
    @*@Scripts.Render("~/bundles/clientfeaturesscripts")
    @RenderSection("scripts", required: false)*@
</body>
</html>

執行程式,用IE F12 工具檢視請求伺服器資源的情況(注意要先清理快取),結果如下:

我們看到在不使用捆綁的情況下,客戶端接收的資料總大小為330.65 KB。

我們再來看看使用捆綁頻寬的使用情況。我們把之前在 MakeBooking.cshtml 和 _Layout.cshtml 中的註釋去掉,並把 _Layout.cshtml 引入 7 個檔案的程式碼刪除。

執行程式(注意清塗快取),結果如下:

我們看到,使用捆綁客戶端接收的資料總大小為 124.01 KB,和不使用捆綁相比少200多KB,即一半多,這是非常可觀的。

我們也注意到,使用捆綁請求的連結也少了。這是因為在釋出模式下,響應客戶端請求時,MVC整合並最小化了JavaScript檔案和樣式檔案,並使得一個捆綁中的內容在一個請求中載入。如果我們檢視Html程式碼,可以看到 Styles.Render 和 Scripts.Render 兩個方法生成了這樣的HTML引用程式碼:

<link href="/Content/css?v=6jdfBoUlZKSHjUZCe_rkkh4S8jotNCGFD09DYm7kBWE1" rel="stylesheet"/>
...
<script src="/bundles/clientfeaturesscripts?v=KyclumLmAXQGM1-wDTwVUS31lpYigmXXR8HfERBGk_I1"></script>
..
<script src="/Scripts/Home/MakeBooking.js"></script>

一個Styles.Render 或 Scripts.Render 方法生成了一個帶有v引數的URL,這個URL將使MVC把一整個捆綁的資料進行最小化處理並一次傳送到客戶端。

 


參考:《Pro ASP.NET MVC 4 4th Edition》

相關文章