Using Script and Style Bundles【翻译】

遇到个MVC4中的bundles的问题,问了别人,由于不熟悉MVC4,始终问不到点子上,所以自己就翻译了下资料,搞明白了这个VS显示正常IIS显示异常的小问题,申明我翻译的很烂,不过共享出来或许会帮到人。

Preparing the Example Application

For thischapter, we have created a new MVC project called ClientFeatures using theBasic template option. We are going to create a variation on the applicationthat we used in the previous chapter, so we started by creating a new classfile called Appointment.cs in the Models folder.  You can see the contents of this file inListing 24-1.

在这一章中,我们先创建一个新的名为ClientFeatures并使用Basic template 模板的MVC应用程序,我们将创建一个在前一章中已经使用过的变量,因此我们首先要在Models文件夹中创建一个名为Appointment.cs的文件。清单24-1中可以看到此文件的内容。

Listing 24-1. The Appointment Model Class 
 
We created a Home controller that operates on the Appointment model class, as shown in Listing 24-2. Listing 24-2. The Home Controller in the ClientFeatures Application 
我们创建一个HomeController操作这个Appointment 模型类,如清单24-2所示。
Using Script and Style Bundles【翻译】
using System;
using System.Web.Mvc;
using ClientFeatures.Models; namespace ClientFeatures.Controllers {
public class HomeController : Controller { public ViewResult MakeBooking() {
return View(new Appointment {
ClientName = "Adam",
Date = DateTime.Now.AddDays(2),
TermsAccepted = true
});
} [HttpPost]
public JsonResult MakeBooking(Appointment appt) {
// statements to store new Appointment in a
// repository would go here in a real project
return Json(appt, JsonRequestBehavior.AllowGet);
}
}
}
Using Script and Style Bundles【翻译】
There are two version of the MakeBooking method in this controller. The version with no parameters creates an Appointment object and passes it to the View method to render the default view. The HttpPost version of the MakeBooking method relies on the model binder to create an Appointment object and uses the Json method to encode the Appointment and send it back to the client in the JSON format. We are focused on the MVC Framework features that support client-side development in this chapter, so we have taken some shortcuts in the controller that wouldn’t be sensible or useful in a real project. Most importantly, we do not perform any kind of validation when we receive a HTTP POST request and just sent the details of the object created by the model binder back to the browser as JSON (with no support for HTML responses to POST requests). We want to make it as easy as possible to use the Ajax-enabled form element that we have defined in the /Views/Home/MakeBooking.cshtml file, which you can see in Listing 24-3. Our interest is in the script and link elements in the view and the interaction with the application is far less important. 
在这个Controller中有两个重载版本的的MakeBooking方法, 不带参数的版本创建一个Appointment对象并通过View()传递并渲染默认的视图。HttpPost版本的MakeBooking方法依赖于模型绑定器的创建AppoimtMent对象,并使用Json方法编码Appoientment并以JSON格式发送回客户端。本章我们的重点是支持客户端开发功能MVC框架特性,所以我们在Controller中的功能做了简化,当然这不是一个切实有用项目。最重要的是,我们并没有进行任何形式的验证,当我们收到一个HTTP POST请求,仅发送由模型绑定器创建的对象以JSON的形式返回给浏览器(不支持HTML响应的POST请求)我们希望尽可能容易地使用支持Ajax的表单元素,我们在/Views/Home/MakeBooking.cshtml文件,如清单24-3所示。 我们感兴趣的是视图中的脚本和link元素和应用程序交互的重要性。
Listing 24-3. The MakeBooking View 
Using Script and Style Bundles【翻译】
@model ClientFeatures.Models.Appointment 

@{
ViewBag.Title = "Make A Booking";
AjaxOptions ajaxOpts = new AjaxOptions {
OnSuccess = "processResponse"
};
}
<h4>Book an Appointment</h4> <link rel="stylesheet" href="~/Content/CustomStyles.css" />
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script> <script type="text/javascript">
function processResponse(appt) {
$('#successClientName').text(appt.ClientName);
$('#successDate').text(processDate(appt.Date));
switchViews();
} function processDate(dateString) {
return new Date(parseInt(dateString.substr(6,
dateString.length - 8))).toDateString();
} 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();
});
});
</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>
Using Script and Style Bundles【翻译】
In this view, there are two div elements. The first is shown to the user when the view is first rendered and contains an Ajax-enabled form. When the form is submitted, we respond to the server’s response to the Ajax request by hiding the form and revealing the other div element, which we use to display details of the appointment confirmation.  
We included a number of the standard JavaScript libraries from the Scripts folder and defined a local script element that contains some simple jQuery code that is specific to this view. We also added a link element that loads a CSS file from the /Content folder called CustomStyles.css, which you can see in Listing 24-4.  
在此视图中,有两个div元素。 第一个是在视图首次渲染时向用户呈现一个支持Ajax的表单。提交表单时,我们对Ajax请求的响应的服务器的响应通过隐藏表单元素显示其他的div元素,我们用于显示预约确认的详细信息。我们使用了Script文件夹一些标准的JavaScript库并定义了一些本地的脚本元素其中包含一些简单的jQuery的代码在特定View中。我们还添加了link元素从 /Content加载CustomStyles.css,如清单24-4:
Listing 24-4. The Contents of the CustomStyles.css File 
div.hidden { display: none;}
div.visible { display: block;}
We want to create a typical scenario for a complex view file without needing to create a complex application, which is why we have added a CSS file that only contains two styles and why we are using a bunch of jQuery libraries for a very simple view. The key idea is that there are lots of files to be managed. When you are writing real applications, you will be struck by just how many script and style files you have to deal with in your views. You can see how our example application works by starting the application and navigating to the /Home/MakeBooking URL. The form is pre-populated with data so that you can just click the Make Booking button to submit the form data to the server using Ajax. When the response is received, you will see a summary of the Appointment object that was created by the model binder from the form data, along with a button element that will return you to the form, as illustrated in Figure 24-1.
我们要创建一个复杂的视图文件的典型方案,而无需创建复杂的应用程序,所以我们添加了一个只包含两种风格的CSS文件和一个非常简单的使用了一堆jQuery库的。关键是在于有大量的文件需要管理。 当你写真实项目时,会遇到更多少的本和样式文件需要你的处理。你可以可以启动程序并导航到/Home/MakeBooking。预先填充表单数据,以便您可以点击预订按钮使用Ajax服务器提交表单数据。收到响应时,您将从表单数据的模型看到所创建的Apointment对象的以input元素返回表单摘要,如图24-1所示。

Using Script and Style Bundles【翻译】

Managing Scripts and Style Sheets 
The view that we created in Listing 24-3 is typical of the MVC projects that we see in the wild. There is a mix of libraries from the Scripts folder, CSS style sheets from the Content folder, local script elements, and of course, HTML and Razor markup.  Developers tend to write view files just as they would write HTML pages, which is fine but isn’t the most effective approach. As we will show you in the sections that follow, there are some hidden problems in our MakeBooking.cshtml view file. We can make a number of improvements in the way that we manage our scripts and style sheets. 
清单24-3中创建的是典型的MVC项目。它由来自Script文件夹的脚本库,来自Content文件夹的CSS样式表,本地js元素,当然,还有HTML元素和Razor标签的组合。
开发人员倾向于像写HTML页面一样写视图文件,这是很好的,但不是最有效的方法。正如我们将告诉你在后面的章节中,也有一些隐藏的问题我们MakeBooking.cshtml视图文件。我们可以做一些改进的方式,我们管理我们的脚本和样式表。
Profiling Script and Style Sheet Loading 
When considering any kind of optimization in any kind of project, you need to start by taking some measurements. We are all for efficient and optimized applications, but our experience is that people rush to optimize problems that don’t have much impact, and in doing so, make design decisions that cause problems later.  
剖析脚本和样式表的加载

在考虑任何类型的项目优化时,你首先需要采取了一些措施以实现高效、优化的应用程序,但我们的经验是,人们对于优化的问题并不是很重视,也正因此,作出的设计决策,导致以后出现问题。

For the problems that we are going to look at in this chapter, we are going to perform our measurements using the Internet Explorer 10 F12 tools (so called because you access them by pressing the F12 key). Load the application, navigate to the /Home/MakeBooking URL, and then press the F12 key. When the tools window opens, navigate to the Network tab and click the Start Capturing button. Reload the contents of the browser tab (right-click in the browser window and select Refresh), and you will see the results shown in Figure 24-2.  
针对这些问题,我们在本章中进行探讨,我们使用Internet Explorer10的F12工具(你可以通过按下F12键访问他们)。执行我们的测试,加载应用程序,导航到/Home/MakeBooking的网址,然后按F12键。当工具窗口打开时,导航到“网络”选项卡,并单击“开始捕获”按钮。刷新浏览器“选项卡的内容​​(右击在浏览器窗口,并选择刷新),你会看到如图24-2所示的结果。
Using Script and Style Bundles【翻译】
 
Figure 24-2. Profiling script and style sheet loading for the example application The F12 tools in IE10 allow you to profile the network requests that your application makes. (There are other tools available if you are not using Internet Explorer. Our favorite is Fiddler, which you can get for free from www.fiddler2.com). So that we can compare the optimizations that we make in this chapter, we will use the data shown in Figure 24-2 as our baseline. Here are the key figures: 
• The browser made nine requests for the /Home/MakeBooking URL. 
• There were two requests for CSS files. 
• There were six requests for JavaScript files. 
• A total of 2,978 bytes were sent from the browser to the server. 
• A total of 470,417 bytes were sent from the server to the browser 
在IE10的F12工具可让您分析应用程序的网络请求状况。 (当然有其他可用的工具,如果你不使用Internet Explorer。我们推荐的Fiddler,你可以去www.fiddler2.com下载)。这样我们我们在本章中就可以比较得出最优化的方案,我们使用图24-2中显示的数据作为的基准。
这里是关键的数据:
•浏览器作出请求的 /Home/ MakeBooking的网址。
•有两个CSS文件的请求。
•有6个JavaScript文件的请求。
•共2,978个字节从浏览器发送到服务器。
•共470,417字节从服务器发送到浏览器。
This is the worst-case profile for our application because we cleared the browser’s cache before we reloaded the view. We have done this because it allows us to easily create a measurable starting point, even though we know that real-world use would be improved by the browser caching files from previous requests. 
If we reload the /Home/MakeBooking URL without clearing the cache, then we get the following results: 
• The browser made nine requests for the /Home/MakeBooking URL. 
• There were two requests for CSS files. 
• There were six requests for JavaScript files. 
• A total of 2,722 bytes were sent from the browser to the server. 
• A total of 6,302 bytes were sent from the server to the browser. 
This is our best-case scenario, where all the requests for CSS and JavaScript files were able to be 
serviced using previously cached files. 
因为在我们加载视图之前,我们清除浏览器的缓存,对于应用程序这是最低效的配置方式。 尽管我们知道从以前的浏览器缓存文件的实际使用会改善请求,但我们还是这样做了,因为它使我们可以方便地创建一个可衡量的基点。 如果我们重新加载/Home/MakeBooking URL而不清除缓存,然后我们得到以下结果:
•浏览器做出请求的/Home/MakeBooking 的网址。
• 有两个CSS文件的请求。
•有6个JavaScript文件的请求。
•共2,722个字节从浏览器发送到服务器。
•共6,302个字节是从服务器发送到浏览器。
 这是最理想的情况,CSS和JavaScript文件的所有请求都能使用以前缓存的文件。
 Note In a real project, we would stop at this point and try to understand if we have a problem that needs to be solved. It may seem that 470K is a lot of bandwidth for a simple Web page, but context is everything. We might be developing an application for Intranet use where bandwidth is cheap and plentiful, and optimizations of any sort are outweighed by the cost of the developer, who could be working on more-important projects. Equally, we could be writing an application that operates over the Internet with high-value customers in countries with low-speed connections—in which case it is worth spending the time to optimize every aspect of the application. Our point is that you shouldn’t automatically assume that you have to squeeze every optimization into every application— there will often be better things you could be doing. (This is always the case if you are sneakily optimizing your application without telling anyone. Stealth optimization is a bad idea and will catch up with you eventually). 
   注意  在实际项目中,如果我们有问题需要解决,此时我们就会停止并尝试了解这里。这个看似简单的470K大小的Web页对于宽带来说微不足道。我们或许开发一个应用程序内部使用的廉价和充足的带宽任何一种优化的成本或许超过了开发人员的成本,他可以去做更重要的工作项目。同样,我们如果在低网速的国家情况下开发在互联网上运行的应用程序和高价值客户这种,我们就值得花时间去优化应用的各个方面。我们的观点是,你不应该认为你每天有更重要的的事情去做而不去挤些时间优化应用程序的各个方面。(如果你始终在不告诉任何人就优化应用,隐身优化是个坏主意,最终会自讨苦吃)。
If you look at the list of JavaScript files that are downloaded for the view, you will notice that we havere-created two very common problems. The first is that we have a mix of minified and regular JavaScript files. This isn’t a huge issue, but it does mean that we won’t have the ability to easily debug all the library code during development.  The second problem is that we have downloaded both the minified and regular versions of the jQuery library. This has happened because the layout is loading some JavaScript and CSS files as well, and our lack of coordination means that we are forcing the browser to download code that it already has. We see both of these problems often. We will show you the MVC Framework features that help you get script and CSS files under control. More broadly, we will also show you how to reduce the number of requests that the browser has to make to the server and the amount of data that has to be downloaded. 
如果你看看为视图下载的JavaScript文件的列表,你会发现我们已重现了两个非常常见的问题。
   首先是,我们有一个压缩的和常规的JavaScript文件组合。这不是一个大问题,但它确实意味着,我们将不会有能力在开发过程中轻松地调试所有的库代码。
   第二个问题是,我们已经下载了压缩版或常规版的jQuery库。由于布局将加载一些JavaScript和CSS文件,这种缺乏协调的发生协调意味着我们将强制浏览器下载已有的代码。
   我们经遇到这两个问题。 我们将向您展示MVC框架功能以帮助你如何控制脚本和CSS文件的获取。从更广泛的意义上说,我们也将向您展示如何减少浏览器的请求,控制服务器和下载的数据量。

Our first step will be to organize our JavaScript and CSS files into bundles, which allows us to treat them as a single unit.Bundles are defined in the /App_Start/BundleConfig.cs file. We have listed the default contents of this file, as created by Visual Studio, in Listing 24-5.

我们第一步组织JavaScript和CSS文件放入bundles中,可以将它们作为独立的单元。bundles的定义在/App_Start/BundleConfig.cs文件中。我们列出了通过Visual Studio创建的此文件的默认内容,如清单24-5。

using System.Web;
using System.Web.Optimization; namespace ClientFeatures {
public class BundleConfig {
public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js")); bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*")); bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css")); bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
}
}
}

The static RegisterBundles method is called from the Application_Start method in Global.asax when the MVC Framework application first starts. The RegisterBundles method takes a BundleCollection object, which we use to register new bundles of files through the Add method.

 Tip The classes that are used for creating bundles are contained in the System.Web.Optimization namespace and, as we write this, the MSDN API documentation for this namespace isn’t easy to find. You can navigate directly to http://msdn.microsoft.com/en-us/library/system.web.optimization.aspx if you want to learn more about the classes in this namespace.

在MVC框架应用程序第一次启动时将从 Global.asax 的Application_Start 方法中调用静态的RegisterBundles方法。 RegisterBundles的方法将BundleCollection的对象作为参数,我们可以用通过Add方法来注册新的bundles。

提示 这些类都包含在用于创建bundles的System.Web.Optimization命名空间中,之所以关注它是因为这个命名空间的MSDN API文档是不容易找到。您可以直接导航到http://msdn.microsoft.com/en-us/library/system.web.optimization.aspx如果您想了解更多关于这个命名空间中的类。

We can create bundles for script files and for style sheets and it is important that we keep these types of file separate because the MVC Framework optimizes the files differently. Styles are represented by the StyleBundle class and scripts are represented by the ScriptBundle class. When you create a new bundle, you create an instance of either StyleBundle or ScriptBundle, both of which take a single constructor argument that is the path that the bundle will be referenced by. The path is used as a URL for the browser to request the contents of the bundle, so it is important to use a scheme for your paths that won’t conflict with the routes your application supports. The safest way to do this is to start your paths with ~/bundles or ~/Content. (The importance of this will become apparent as we explain

how bundles work).
Once you have created the StyleBundle or ScriptBundle objects, you use the Include method to add details of the style sheets or script files that the bundle will contain. There are some nice features available for making your bundles flexible. To help us demonstrate this, we have edited and simplified the bundles our example application supports in the BundleConfig.cs file, as shown in Listing 24-6. We added a new bundle, edited one of the existing ones, and removed all of the ones that we do not need.
我们可以为脚本文件和样式表的创建bundles,而且重要的是,我们要保持这些类型的文件独立,因为MVC框架按照不同的文件进行优化。样式的由StyleBundle类,脚本由ScriptBundle类来控制。当你创建一个新的bundle的一个实例,这两者采取单一的构造函数的参数即bundle被引用的路径。这个路径被用于浏览器请求bundle的内容的Url,所以使用该方案时使您的应用程序请求路径与路由不会冲突非常重要。要做到这一点,最安全的方式是路径以〜/bundles或〜/Content开始。 (之后为大家讲解bundles是如何工作时,这一点的重要性将变得很明显)。
一旦你已经创建了StyleBundle或ScriptBundle的对象,您可以使用Include方法为这个bundle添加样式表或脚本文件的具体内容。有一些不错的功能,可灵活的用于bundles。为了我们演示这一点,我们对现有的BundleConfig.cs文件中的bundles进行了编辑及简化,如清单24-6所示。我们添加了一个新的bundle,编辑一个现有的,并删除了所有那些我们不需要的。

Listing -. Customizing the Bundle Configuration
using System.Web;
using System.Web.Optimization; namespace ClientFeatures {
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")); }
}
}

We started by modifying the StyleBundle with the ~/Content/css path. We want this bundle to include all the CSS files in our application, so we changed the argument passed to the Include method from ~/Content/site.css (which refers to a single file) to ~/Content/*.css. The asterisk (*) character is a wild card, which means that our bundle now refers to all of the CSS files in the /Content folder of our project.
This is an excellent way of ensuring that files in a directory are automatically included in a bundle and where the order in which the files are loaded isn’t important. The order in which the browser loads our CSS files isn’t important, so using a wildcard is just fine; but if you are relying on the CSS style precedence rules, then you need to list the files individually to ensure a specific order.
The addition to the BundleConfig.cs file is a ScriptBundle whose path we set to ~/bundles/clientfeaturesscripts. You will see the paths for both of these bundles again when we apply them in our application shortly. We used the Include method for this bundle to specify individual JavaScript files, separated by commas, because we only require some of the files in the Scripts folder and we care about the order in which the browser loads and executes the code.
Notice how we specified the jQuery library file:
我们开始通过修改StyleBundle的~/Content/css 路径。 我们希望这个bundle包含应用程序中的所有的CSS文件,所以我们将传递给Include方法参数~/Content/site.css(指单个文件)改成~/Content/*.css。 星号(*)字符是一个通配符,这意味着现在的Bundle引用我们的项目/Content文件夹中所有的CSS文件。确保目录中的文件会自动包含在bundle中的文件很好的方法,同时文件加载的顺序并不重要因为浏览器加载我们的CSS文件的顺序并不重要,所以使用通配符是非常好的方式;但如果程序依赖于CSS样式规则的优先级,则需要列出单独的文件,以确保特定的顺序。
除此之外BundleConfig.cs文件还有一组ScriptBundle,它的路径被设置成~/bundles/clientfeaturesscripts。他们很快会在我们的应用程序中出现,你会再见到这些bundle的路径。我们在bundle的Include方法指定由逗号分隔的单独的JavaScript文件,因为我们只需要Script文件夹中的一些文件,此时我们关心浏览器的加载并执行代码顺序。注意我们是如何指定jQuery库文件:

...
~/Scripts/jquery-{version}.js
...

The {version} part of the file name is pretty handy because it matches any version of the file specified and it uses the configuration of the application to select either the regular or minified version of the file. MVC 4 comes with version 1.7.1 of the jQuery library, which means that our bundle will include /Scripts/jquery-1.7.1.js during development and /Scripts/jquery-1.7.1.min.js when deployment.
 Tip The decision between the regular and minified version is driven by the compilation element in the Web.config file. The regular version is used when the debug attribute is set to true and the minified version is used when debug is false. We will switch our application from debug to deployment mode later in the chapter so you can see how it is done.
{version} 部分文件名是非常方便的,因为它匹配指定的文件的任何版本,这种的配置可以是应用程序选择常规或压缩的版本的文件。 MVC 4自带1.7.1版本的jQuery库,这意味着在开发阶段我们的Bundle将包括Scripts/jquery-1.7.1.js的部署时加载/ Scripts/jquery-1.7.1.min.js 。
提示 常规和压缩版本之间的切换都基于Web.config文件compilation元素。 当debug属性设置为true时使用正式版本, 当debug属性设置为false,使用压缩版本。我们将我们的应用程序切换调试到部署的模式,在本章的后面,你可以看到它是如何做的。

The benefit of using {version} is that you can update the libraries you use to new versions without having to redefine your bundles. The drawback is that the {version} token isn’t able to differentiate between two versions of the same library in the same directory. So, for example, if we were to add the jquery-1.7.2.js file in our Scripts folder, we would end up with both the 1.7.1 and 1.7.2 files being shipped to the client. Since this would undermine our optimization, we must ensure that only one version of the library is in the /Scripts folder.
使用{version}的好处是你可以更新你的的版本,而不必重新在bundle中定义。{version}标记的缺点是是无法区分在同一目录中两个版本的相同的库。因此,举例来说,如果我们要添加的Script文件夹中的query1.7.2.js文件,而最终的1.7.1和1.7.2的文件都被发送到客户端。因为这会破坏我们的优化,我们必须确保/ Scripts文件夹中只有一个版本库。

Tip The MVC Framework is smart enough to ignore the IntelliSense files when processing {version} in a bundle, but we always check what is being requested by the browser because it is so easy to include unwanted files. You will see how we do this shortly.

提示 MVC框架是聪明到在bundle中处理{version}忽略智能感知文件,但我们总是检查所请求的浏览器因为太容易了,包括不需要的文件。很快你将看到我们如何做到这一点。

Applying Bundles 

The first thing we need to do when applying bundles is prepare the view. Our first step is optional, but it will allow the MVC Framework to perform maximum optimization for our application. We have created a new /Scripts/Home folder and added a new JavaScript file called MakeBooking.js within it. This is the convention that we follow to keep our per-page JavaScript files organized by controller. You can see the contents of the MakeBooking.js file in Listing 24-7.

应用bundles,我们需要做的第一件事情是准备视图。我们的第一个步骤是可选的,但它可以让MVC框架进行最大限度地优化我们的应用程序。我们已经创建了一个新的/Scripts/Home文件夹,并添加了一个名为MakeBooking.jsJavaScript新文件。这是我们遵循的惯例,使我们的每个页面的JavaScript文件由控制器组织。清单24-7中,你可以看到MakeBooking.js文件的内容。

 Listing 24-7. The Contents of the MakeBooking.js File

Listing 24-7. The Contents of the MakeBooking.js File
function processResponse(appt) {
$('#successClientName').text(appt.ClientName);
$('#successDate').text(processDate(appt.Date));
switchViews();
}
function processDate(dateString) {
return new Date(parseInt(dateString.substr(6,
dateString.length - 8))).toDateString();
}
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();
});
});

This is the same code that we used previously—we have just moved it into a separate file. The next step is to change the /Views/Home/MakeBooking.cshtml view file to remove the link and script elements for which we have created a bundle, as shown in Listing 24-8. We only want the browser to request the files it needs; leaving those elements in place will lead to duplicate requests. The only script element that remains is the one that refers to the view-specific MakeBooking.js file we created in Listing 24-7

这是我们以前使用的相同代码-我们刚把它移动到单独的文件。我们只想让浏览器请求它需要的文件,下一步是修改/Views/Home/MakeBooking.cshtml视图文件以删除链接和脚本元素,留下这些元素会导致重复请求。我们创建了一个bundle,如清单24-8所示。我们在清单24-7中创建MakeBooking.js文件仍然是指特定视图唯一的脚本元素。

Listing -. Removing the Script and Link Elements from the MakeBooking.cshtml View File
@model ClientFeatures.Models.Appointment @{
ViewBag.Title = "Make A Booking";
AjaxOptions ajaxOpts = new AjaxOptions {
OnSuccess = "processResponse"
};
}
<h4>Book an Appointment</h4> <script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></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>

You can refer to bundles in view files, but we tend to create bundles only for content that is shared
across multiple views, and that means we apply bundles in the layout files. In Listing 24-9, you can see the
/Views/Shared/_Layout.cshtml file that Visual Studio added to the project.

您可以在视图文件引用bundels,但我们倾向于在多个视图共享的内容时才创建bundles,这意味着我们将bundles应用在布局文件。在清单24-9中,你可以看到有Visual Studio中添加到项目中中的/Views/Shared/_Layout.cshtml文件。

Listing -. The Layout Added to the Project by Visual Studio
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title> @Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
@RenderBody() @Scripts.Render("~/bundles/jquery") @RenderSection("scripts", required: false)
</body>
</html>

Bundles are added using the @Scripts.Render and @Styles.Render helper methods. You can see that the layout already contains three bundles, which we have marked in bold. The two calls to @Scripts.Render explain some of the initial profile data. The ~/bundles/jquery bundle is the reason that we ended up with two copies of the jQuery library being requested by the browser. The ~/bundles/modernizr bundle makes the browser request a library that we do not use in our simple application.
In fact, only the ~/Content/css bundle was any help to us because it loaded the /Content/Site.css file
in the original bundle definition and will now load all the CSS files in the /Content folder following the
change we made in Listing 24-9. To get the behavior we want, we have edited the _Layout.cshtml file to
use our newly defined bundles and remove the references that we do not want, as shown in Listing 24-10.

Bundles使用 @Scripts.Render和@Styles.Render辅助器方法进行添加。你可以看到的布局页已经包含三个bundle,我们以粗体标记。两个的调用了@Scripts.Rende解析一些初始配置文件数据。 使用~/bundles/jquery 的bundle的原因是我们最终使用jQuery库的两个副本请求浏览器。 ~/bundles/modernizr的bundle使浏览器请求一个库,而在简单的应用程序并不使用它。
事实上,只有~/Content/css 的bundle对我们有一些帮助,因为它装载了/Content/Site.css的文件在原生的bundle定义中,现在我们需要载入所有的CSS/ Content文件夹中的文件因而需要一些修改如清单24-9所示。为了得到我们想要的结果,我们需要修改_Layout.cshtml文件使用我们新定义的bundle,并删除我们不想引用,如清单24-10所示。

Listing 24-10. Ensuring the Right Bundles Are Using the _Layout.cshtml File
<!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>
@RenderBody()
@Scripts.Render("~/bundles/clientfeaturesscripts")
@RenderSection("scripts", required: false)
</body>
</html>

You can see the HTML that these helper methods generate by starting the application, navigating to the /Home/MakeBooking URL, and viewing the page source. Here is the output produced by the Styles.Render method for the ~/Content/css bundle:

你可以启动该应用,导航到 /Home/MakeBooking URL,查看通过这些帮助器方法生成的HTML,并查看网页源代码。下面 @Styles.Render("~/Content/css") 方法产生的:

...
<link href="/Content/CustomStyles.css" rel="stylesheet"/>
<link href="/Content/Site.css" rel="stylesheet"/>
...
And here is the output produced by the Scripts.Render method:
...
<script src="/Scripts/jquery-1.7.1.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>
...

Using the Scripts Section
We have one more thing to do. Our view-specific JavaScript contained in the /Scripts/Home/MakeBooking.js file depends on jQuery to set up the event handler for the button. This means that we have to make sure that the jQuery file is loaded before the MakeBooking.js file. If you look at the layout in Listing 24-10, you will see that the call to the RenderBody method is before the call to Scripts.Render, which means that the script element in the view appears before the script elements in the layout and our button code won’t work. (It will either fail quietly or report a JavaScript error, depending on the browser being used.)

使用Script分段
我们还有一件事要做。我们的视图--特定的脚本包含在/Scripts/Home/MakeBooking.js文件中并依赖jQuery来设置按钮的事件处理程序 这就意味着我们必须确保在加载MakeBooking.js文件之前加载jQuery文件 如果你看看清单24-10中的内容,您将看到RenderBody调用方法在调用 Scripts.Render方法之前。这意味着在我们的按钮代码出现在布局视图中的这些脚本之前,那么按钮将无法正常工作。。

We could fix this by moving the Scripts.Render call to the head element in the view; in fact, this is
what we usually do. However, we can also take advantage of the optional scripts section that is defined in
the _Layout.cshtml file and which you can see in Listing 24-10. In Listing 24-11, you can see how we have
updated the MakeBooking.cshtml view to use this section.
Listing 24-11. Using the Optional Scripts Section in the View

我们可以通过调用Scripts.Render将脚本在视方法图head元素中呈现;事实上,我们通常就这么做。不过,我们还可以利用_Layout.cshtml文件定义的可选的脚本在清单24-10中你可以看到。清单24-11中,可以看到使用了这个section更新了MakeBooking.cshtml视图

Listing 24-11. Using the Optional Scripts Section in the View
@model ClientFeatures.Models.Appointment @{
ViewBag.Title = "Make A Booking";
AjaxOptions ajaxOpts = new AjaxOptions {
OnSuccess = "processResponse"
};
}
<h4>Book an Appointment</h4>
@section scripts {
<script src="~/Scripts/Home/MakeBooking.js" type="text/javascript"></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>

The scripts section appears after the call to Scripts.Render in the layout, which means that our view-
specific script won’t be loaded until after jQuery, and our button element will work the way we intended.
This is an incredibly common mistake to make when using bundles, which is why we have demonstrated
it explicitly.

scripts section 出现在布局调用Scripts.Render方法之后,这意味着我们的视图加载jQuery库完成后才加载指定的脚本,我们的按钮元素将我们预期的方式工作。
是一种使在使用bundels时非常常见的错误,这就是为什么我们明确说明它的原因。

Profiling the Changes
We have defined our own bundles, removed the unwanted JavaScript references, and generally tidied up our use of scripts and style sheets. It is now time to profile our changes and get a handle on the difference. To do this, we cleared the browser cache and navigated to the /Home/MakeBooking URL, and monitored the requests the browser makes using the F12 tools. You can see the results in Figure 24-3.

变动的性能分析
我们已经定义我们自己的bundles,去掉了不必要的JavaScript引用,并且整理了通常我们使用的脚本和样式表。现在是时候来分析我们的更改并了解这些区别。要做到这一点,我们清除浏览器缓存导航到 /Home/MakeBooking的网址,使用F12工具并监测浏览器发出的请求。在图24-3中,你可以看到的结果。

Using Script and Style Bundles【翻译】

Here is the summary of the profile information:
• The browser made eight requests for the /Home/MakeBooking URL.
• There were two requests for CSS files.
• There were five requests for JavaScript files.
• A total of 2,638 bytes were sent from the browser to the server.
• A total of 326,549 bytes were sent from the server to the browser.
That’s not bad. We have shaved about 30 percent off the amount of data that is sent to the browser.
But we should get even greater benefits when we switch the application from its debugging to deployment
configuration, which we do by setting the debug attribute on the compilation element in the Web.config
file to false, as shown in Listing 24-12.
下面是优化的信息摘要:
•浏览器向/Home/MakeBooking URL发送了有8请求的。
•有2个CSS文件的请求。
•有5个JavaScript文件的请求。
•共2,638个字节从浏览器发送到服务器。
•共326,549字节,从服务器发送到浏览器。
这已经非常不错了。我们已干掉了被发送到浏览器的大约30%的的数据量。但是,我们还可以得到更多的好处,当我们通过将Web.config文件中compilation 元素的debug属性设置为false将应用程序从调试模式切换到部署模式。如清单24-12所示。

Listing 24-12. Disabling Debug Mode in the Web.config File
...
<system.web>
<httpRuntime targetFramework="4.5" />
<compilation debug="true" targetFramework="4.5" />
...

Once we have made this change, we restart the application, clear the browser cache, and profile the network requests again. You can see the results in Figure 24-4.

一旦我们做了这些改变,我们重新启动应用程序,清除浏览器缓存,并再次剖析网络请求。如图24-4中,你可以看到如下结果.

Using Script and Style Bundles【翻译】

Figure 24-4. Profiling with bundles in the deployment configuration Here is the summary of the profile information: 

• The browser made four requests for the /Home/MakeBooking URL. 
• There was one request for CSS. 
• There were two requests for JavaScript files. 
• A total of 1,372 bytes were sent from the browser to the server. 
• A total of 126,340 bytes were sent from the server to the browser.

 

图24-4。剖析bundles 在部署配置优化的信息摘要:

•浏览器向/Home/MakeBooking URL发送了有4请求的。
•对CSS有1个请求。
•有2个JavaScript文件的请求。
•共1,372个字节从浏览器发送到服务器。
共126,340字节,从服务器发送到浏览器。

You might be wondering why there are fewer requests for CSS and JavaScript files. The reason is that the MVC Framework concatenates and minifies the style sheets and JavaScript files in the deployment mode so that all the content in a bundle can be loaded in a single request. You can see how this works if you look at the HTML that the application renders. Here is the HTML that the Styles.Render method has produced:

你也许会奇怪,为什么会有更少的对CSS和JavaScript文件的请求。原因是MVC框架连接和minifies样式表和部署模式中的JavaScript文件,以便所有bundle中的内容可以加载在单个请求。如果你看一下应用程序呈现的HTML你可以看到这是如何工作的。这是Styles.Render方法产生的HTML样式:

...
<link href="/Content/css?v=6jdfBoUlZKSHjUZCe_rkkh4S8jotNCGFD09DYm7kBWE1"
rel="stylesheet"/>
...
And here is the HTML produced by the Scripts.Render method:
...
<script src="/bundles/clientfeaturesscripts?v=SOAJUpvGwNr0XsILBsLrEwEpdyIziIN9frqxIjgTyWE1">
</script>
...

These long URLs are used to request the contents of a bundle in a single blob of data. The MVC Framework minifies CSS data differently from JavaScript files, which is why we have to keep style sheets and scripts in different bundles.
The impact of the optimizations we have applied is significant. We have far fewer requests from the browser, which reduces the amount of data sent to the client. And we have less data sent in return; in fact, we have sent about 27 percent of the volume of data that we recorded when we profiled the application at the start of the chapter.
This is the point where we stop optimizing the requests. We could add our MakeBooking.js file to a bundle to eliminate another request and have the MVC Framework minify the code, but we have reached the point where our returnsare diminished and we start mixing up the content that is view-specific with the code that is in the layout, which we prefer not to do. If we had a more complex application, we might well create a second script bundle that contains more custom code, but we have a simple application and our final rule of optimization is to know where to stop. For this application, we have reached that point.

这些长url用于请求的内容包中的单个blob数据。 MVC框架不同的JavaScript文件minifies CSS数据,这就是为什么我们必须保持样式表和脚本在不同的bundle中。优化对应用的影响很大,来自浏览器的请求要少得多,减少了大量数据发送到客户端和回发送较少的数据;事实上,我们已发送的数据量只有我们在一章的开始分析的应用程序时的27%左右。这是优化的请求停止的时候。 我们可以加入我们的MakeBooking.js文件到另一个bundle,消除另一个请求,并有缩小MVC框架代码,但我们已经达到了我们的预期的压缩的效果,我们开始混合了具体的视图的内容和布局的代码,这是我们不喜欢做的内容。如果我们有一个更复杂的应用,我们很可能会创建第二个script bundle,包含更多的自定义代码,但有对于一个简单的应用程序,最后的优化准则是知道在哪里停止的。对于这种应用,我们已经达到了这一点。

上一篇:处理 input 上传图片,浏览器读取图片大小过程中遇到到的坑(兼容IE8\9)


下一篇:C#窗体 WinForm 文件操作