Showing posts with label MVC. Show all posts
Showing posts with label MVC. Show all posts

2/10/2013

ASP.NET MVC 4 Highlights: Bundling and Minification

By: John V. Petersen


In the first installment of this series, I explored a few of the new features in ASP.NET MVC 4, including the new default project templates, mobile templates, and display modes. Since that article, ASP.NET MVC 4 has been released to beta. For brevity’s sake, when I refer to MVC the design pattern, I’m referring to the ASP.NET implementation of the pattern. In this installment, I’m going to focus on one of MVC’s most useful features: integrated JavaScript and CSS bundling and minification.
One of the most important considerations in any Web application is the size of the content rendered to the browser. Bundling and minification handle two important tasks. First, all of the disparate JavaScript and CSS files are combined into one or more files. Second, the JavaScript and CSS code is minified by means of removing all of the carriage returns and line feeds as well as verbose variable names in favor of shorter (less verbose) alternatives.
With bundling, there are fewer resources that need to be rendered to the browser. With minification, the size of such resources is much smaller, often by as much as 70% or more!
These are not new concepts. Tools like JSMin and YUI have been around for several years. You have always been able to incorporate these bundling and minification solutions. What’s new are the integrated features baked into the MVC framework that handle these bundling and minification tasks out of the box.
Bundling and Minification “Out of the Box”
Figure 1 illustrates what you get out of the box for bundling and minification. Like all ASP.NET applications, everything starts in the Global.asax Application_Start() event handling code.
Click for a larger version of this image.

Figure 1: These code windows represent the out-of-the-box bundling and minification implementation in Global.asax and _Layout.cshtml.

In that code, there’s a call to:
BundleTable.Bundles.RegisterTemplateBundles();
The code for this method is burned into the System.Web.Optimization.dll. Figure 2 illustrates what that code looks like.
Click for a larger version of this image.

Figure 2: This code represents the out-of-the-box implementation code for RegisterTemplateBundles in System.Web.Optimization.

The code is straightforward in that bundles are created and files are added to the bundles. There are two ways to add files. One way is to refer to the file specifically. The second way is to specify a directory along with a file pattern search string. In the second method, the code can selectively traverse subdirectories to add content.
Each method has their respective pros and cons. When you specify the files, you know exactly what is getting into your bundled and minified files. This method requires more code. When you add directories, you don’t have to write as much code, but the possibility exists for unwanted code in the bundled and minified file. Choosing between adding files, directories or some combinatin thereof is a matter personal preference.
To use this functionality, there are a few concepts you must be familiar with:
  • Bundle Class: Encapsulates one or more files to be bundled and minified
  • AddFile() Method: Adds a specifically named file to the bundle
  • AddDirectory() Method: Adds files that match a file search string within the specified directory
  • Transform Property: An instance of IBundlerTransform, the object in this property performs the minification task
  • Path Property: The string that identifies the bundle within the Bundles Member in BundleTables
As you will see in a moment, as is custom in MVC, if you don’t like the default out of the box functionality in MVC, you can substitute your own functionality. First, take a look at what the default functionality gives you. Figure 3 illustrates how the disparate javaScript (JS) and Cascading Style Sheet (CSS) files are rendered to the browser.
Click for a larger version of this image.

Figure 3: The network view lists the bundled and minified CSS and JS files (with a query string parameter that is explained in the next section).

Note the file paths:
/Content/css/Content/themes/base/css/Scripts/js
These are the paths specified when the bundles were created. Figure 4 illustrates how the minified JavaScript appears.
Click for a larger version of this image.

Figure 4: This is the minified and bundled JavaScript content as rendered to the browser.

Why Is a Query String Parameter Added to the JavaScript and CSS Files?
Every time the page is refreshed, the server-side code is executed, causing the _Layout.cshtml Razor Template to evaluate. You always want the client to recognize the latest JavaScript and CSS content. If the file were always named the same, the browser may reference its cache. This behavior can always be configured at the browser level. However, that is not usually a feasible solution because it requires each computer to be specifically configured. If you have the chance to control behaviors that are essential to your applications at the server level, take it! With the addition of the query string, the client will interpret this to be a new file and therefore, rely on its cache.
You might be thinking that the call to ResolveBundleUrl is required for the bundling and minification to work. It’s not. To illustrate, I’ll replace the code for /Content/css with the following:
<link href="~/Content/css" rel="stylesheet" type="text/css" />
Figure 5 illustrates that bundling and minification still takes place. The only difference is the file name. Without the call to ResolveBundleUrl, the query string parameter is not added.
Click for a larger version of this image.

Figure 5: If the ResolveBundleUrl call is omitted, a query string parameter will not be added.

Essentially, that’s it! That’s what we get for free. But what about debugging? I don’t want my code minified in those cases. Am I stuck?
Of course not! In MVC, with almost no exception, you can override the default out-of-the-box behavior for your own behavior. In the next section, you will see how to create your own custom bundler and minification class.

Creating a Custom Bundler
If there are limitations to how the bundling and minification feature is implemented in the beta, these two would be at the top of the list:
  • The code to create the bundles is burned in a dll.
  • There is no way to make the default functionality sensitive to debug vs. release modes.
While bundling is something you probably always want, minification isn’t. While debugging, you need the unminified code to be rendered. Out of the box, using the default bundler, you cannot conditionally bundle. Let’s solve that problem now. The custom bundler code in Listing 1 solves the problem.
What’s the Second Bool Variable in AddFile()?
The second Bool Variable is the throwIfNotExist parameter. If the specified file does not exist, you can elect to have an exception thrown. In the default beta implementation, this parameter is set to false.
If you don’t want to be explicit with your files, the code could be simplified to what is illustrated in Listing 2.
The AddDirectory() method has four parameters:
  • directoryVirtualPath: the root directory used to search for files to bundle and minify
  • searchPattern: specifies the pattern to limit which files are included in the bundle
  • searchSubDirectories: if true, the process recursively searches all contained subdirectories under the directoryVirtualPath
  • throwIfNotExist: if true, the process throws an exception if the specified directoryVirtualPath does not exist
In this simplified code, every js file under /Scripts and every CSS file under /Content is included.
In both cases, a compiler directive is added to specify the transformer used to drive the minification process. Out of the box, there is a class called NoTransform. As the name implies, this class does not minify. You need such a class because the bundler instance requires a transformer:
var bundle = new Bundle("~/Scripts/js", jstransformer);
The bundler does not care what the transformer does. As long as it gets an instance that conforms to IBundleTransformer, the Bundle instance will be happy. Listing 3 shows what the NoTransform class is.
I Like the YUI Minifier; Can I Use That?
The nice thing about the way bundling and minification was implemented in ASP.NET MVC is that you don’t have to give up using utilities that you are currently using. The implementation works very much like the way IDependencyResolver works. In Version 3, an inversion of control container adapter was added to abstract away the details of any specific IoC container from the framework. The bundling and minification process illustrated here works very much the same way. The process begins with creating a custom instance of IbundlerTransform, as shown in Listing 4.
To take advantage of the YUICompressor Transform Class, the code in Listing 2 that loads the proper transformer must be changed to the following:
bool isDebug;
#if DEBUG isDebug = true; #endif
if (isDebug) { jstransformer = new NoTransform("text/javascript"); csstransformer = new NoTransform("text/css"); } else { jstransformer = new YUITransform(contentType.javascript); csstransformer = new YUITransform(contentType.css); }
I changed the debug-checking process slightly, because I am gradually moving to a solution that is testable. The next step involves creating a custom abstraction over the base Bundle Class that begins to abstract away the details of which files to add. I’ll leave that exercise to you to explore.
File Ordering within a Bundle
By default, JavaScript files are first ordered alphabetically within a bundle. Then, the files are restacked around known libraries. For example, jQuery - related files occur first in the bundle. Within the jQuery group, the files are sorted alphabetically. For CSS files, the files are first sorted alphabetically. Then, if the files reset.css or normalize.css exist, that content appears at the top of the bundled CSS file. Figure 6 illustrates this behavior in the bundled CSS file. Like everything else, this behavior is completely customizable. The Bundle Class has an Orderer property that conforms to the IBundleOrderer interface.
Click for a larger version of this image.

Figure 6: The Bundle.Orderer Property controls how files are ordered within the bundle.

Conclusion
With each release, the ASP.NET MVC Framework gets better and better. Bundling and minification is an essential process that needs to be in every production Web app that relies on significant JavaScript and CSS resources. Out of the box, the functionality is pretty good. There are, however, some missing pieces. Fortunately, the process was designed with customization and extensibility in mind. With a little effort, it was easy to toggle minification based on whether or not the application was being run under debug mode.
One final point, this article was based on beta software. The usual disclaimer applies - this functionality may change when the product is released to manufacturing (RTM).
John V. Petersen
&

SPONSORED SIDEBAR: Learn ASP.NET MVC in a Day!

ASP.NET is one of the world’s most popular Web development environments. We can help you with all ASP.NET projects as well as related technologies, such as HTML (4 and 5), JavaScript, jQuery, CSS, AJAX, services, and many more. (Learn more from www.codemag.com/consulting.)
That’s why CODE Training is offering a full day of training in ASP.NET MVC from CODE Consultants, experts in Web development. CODE Training and EPS Software will be holding an intensive one-day lecture, June 5, 2012, on ASP.NET MVC specifically designed for developers of business applications. Learn how, when and why to use ASP.NET MVC for the best result in your projects. Only $399!
Visit www.codemag.com/training to find out a little bit more about the class, or send an e-mail to info@codemag.com for more information.


Listing 1: Custom bundler class
using System;using System.Linq;using System.Web.Optimization;
namespace MVC4BundleUI{ public class MyBundler { public static void init() { IBundleTransform jstransformer; IBundleTransform csstransformer;
#if DEBUGjstransformer = new NoTransform("text/javascript"); csstransformer = new NoTransform("text/css"); #else jstransformer = new JsMinify(); csstransformer = new CssMinify(); #endif
var bundle = new Bundle("~/Scripts/js", jstransformer);
bundle.AddFile("~/Scripts/jquery-1.6.2.js", true); bundle.AddFile("~/Scripts/jquery-ui-1.8.11.js", true); bundle.AddFile("~/Scripts/jquery.validate.unobtrusive.js", true); bundle.AddFile("~/Scripts/jquery.unobtrusive-ajax.js", true); bundle.AddFile("~/Scripts/jquery.validate.js", true); bundle.AddFile("~/Scripts/modernizr-2.0.6-development-only.js", true); bundle.AddFile("~/Scripts/AjaxLogin.js", true); bundle.AddFile("~/Scripts/knockout-2.0.0.debug.js", true);
BundleTable.Bundles.Add(bundle);
bundle = new Bundle("~/Content/css", csstransformer);
bundle.AddFile("~/Content/site.css", true);
BundleTable.Bundles.Add(bundle);
bundle = new Bundle("~/Content/themes/base/css", csstransformer);
bundle.AddFile("~/Content/themes/base/jquery.ui.core.css", true); bundle.AddFile("~/Content/themes/base/jquery.ui.resizable.css", true); bundle.AddFile("~/Content/themes/base/jquery.ui.selectable.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.accordion.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.autocomplete.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.autocomplete.css", true); bundle.AddFile("~/Content/themes/base/jquery.ui.dialog.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.slider.css", true); bundle.AddFile("~/Content/themes/base/jquery.ui.tabs.css", true); bundle.AddFile("~/Content/themes/base/jquery.ui.datepicker.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.progressbar.css",true); bundle.AddFile("~/Content/themes/base/jquery.ui.theme.css", true);
BundleTable.Bundles.Add(bundle); } }
}

Listing 2: Simplied custom bundler code using the AddDirectory() method
public class MyBundler { public static void init() { IBundleTransform jstransformer; IBundleTransform csstransformer;
#if DEBUGjstransformer = new NoTransform("text/javascript"); csstransformer = new NoTransform("text/css"); #else jstransformer = new JsMinify(); csstransformer = new CssMinify(); #endif
var bundle = new Bundle("~/Scripts/js", jstransformer); bundle.AddDirectory("~/Scripts/","*.js",true, true); BundleTable.Bundles.Add(bundle);
bundle = new Bundle("~/Content/css", csstransformer); bundle.AddDirectory("~/Content/", "*.css", true, true);
BundleTable.Bundles.Add(bundle); } }

Listing 3: NoTransform transformation Class
public class NoTransform : IBundleTransform { readonly string _contentType;
public NoTransform(string contentType) { this._contentType = contentType; }
public void Process(BundleContext context, BundleResponse response) { response.ContentType = this._contentType; } }

Listing 4: YUI Compressor transformation class
using System.IO;using System.Web.Optimization;using Yahoo.Yui.Compressor;
namespace Bundler.Utilities{ public enum contentType { javascript, css }
public class YUITransform : IBundleTransform { readonly string _contentType = string.Empty;
public YUITransform(contentType contentType) { if (contentType == contentType.css) { this._contentType = "text/css"; } else { this._contentType = "text/javascript"; } }
public void Process(BundleContext context, BundleResponse bundle) { bundle.ContentType = this._contentType;
string content = string.Empty;
foreach (FileInfo file in bundle.Files) {
using (StreamReader fileReader = new StreamReader(file.FullName)) { content += fileReader.ReadToEnd(); fileReader.Close(); }
}
bundle.Content = Compress(content); }
string Compress(string content) { if (_contentType == "text/javascript") { return JavaScriptCompressor.Compress(content); } else { return CssCompressor.Compress(content, CssCompressionType.StockYuiCompressor); } } }}

9/08/2012

ASP.NET MVC – PartialView with Ajax

In this post I will briefly discuss how to use a PartialView either as a Web Form’s “UserControl” or as an“UpdatePanel”. You can click on the links to navigate accordingly, or just read on. UPDATE: If you’d rather have a “real world” example, you can read this post: http://evolpin.wordpress.com/2011/04/26/asp-net-mvc-partial-view-and-ajax-real-world-example/.
I’ve wanted to use a PartialView for quite some time now. A PartialView provides two functionalities which aren’t necessarily overlapping although have much in common. In comparison with Web Forms, a PartialView is a kind of a UserControl, but it also provides a kind of UpdatePanel.
What’s great about a PartialView is that it’s very straight forward – you simply place your HTML and/or JavaScript in a separate View file, probably placed in your Shared views folder so it can be used easily across views. Then you can either use it as a control, by placing Html.Partial(…) calls on your View as desired, or you can render it in run-time as a response to an Ajax call.
UserControl like implementation:
Let’s go quickly over the first usage, that of a “UserControl”: we right-click on the Shared folder to add a new View, give it a name, and select the “Create as a partial view” checkbox:
Our solution explorer looks as follows (Index.cshtml will be using the newly created MyPartialView.cshtml):
MyPartialView contains a simple dummy html place holder which will display a “My View” header; whereas Index.cshtml in this example will now include 2 references to MyPartialView:
1: <table border='1'> 
2:     <tr>
3:         <td>
4:             @Html.Partial("MyPartialView")
5:         </td>
6:         <td>
7:             @Html.Partial("MyPartialView")
8:         </td>
9:     </tr>
10: </table>
That’s about it, and this is how it looks:
One more thing worth mentioning, is that the ViewData dictionary which can be populated in the Controller, is available both to the View and to it’s Partial Views.
Ajax and html injection – “UpdatePanel”:
As I mentioned earlier, Partial Views can also provide a functionality resembling Web Form’s UpdatePanel. Those familiar with UpdatePanel usually either appreciate the (relatively) ease of use, or despise it for being so heavy and inefficient. Almost an Ajax “wannabe”. Personally, I think that UpdatePanel has its place in Ajax side by side with PageMethods/WebMethods. I consider it the better alternative for rendering a “mass” of html onto the client, especially when paging or sorting grids. In MVC, a PartialView could perform this exact functionality, and could prove to be the better solution for rendering plenty of html to the client.
Basically there are 4 steps to achieve this:
  1. Create a PartialView.
  2. Create a place holder html control.
  3. Use jQuery’s load method to fetch the partial view from the server and inject it into the place holder.
  4. Create a server side Action in a Controller that will return the partial view.
Here’s a quick example how this can be done. We’ll use the same partial view created earlier (i.e. MyPartialView.cshtml). So now we have to prepare a place holder div, and use jQuery to load and inject the response:
1: <a href='javascript:getView();'>Get Partial View</a>
2: <script type="text/javascript">
3:     function getView() {
4:         $('#divResult').load("@Url.Action("GetView" , "Home" )");
5:     }
6: </script>
7: <div id='divResult'>
8: </div>
  • Line 1 is a simple anchor which will invoke the JavaScript containing jQuery’s load.
  • Line 4 is the simple one-line code which performs an ajax call to the server’s GetView action in the Home Controller (will be done shortly), and injects the result.
  • Line 7-8 is where the result will be injected to.
And now for the server side – pretty self-explanatory:
1: public ActionResult GetView()
2: {
3:    return PartialView("MyPartialView");
4: }
When we run the sample, prior to the Ajax call this looks like this:
After clicking the link, jQuery’s load method performs the Ajax call as expected, and the partial view’s HTML is injected into the place holder. All this can be viewed in the picture below:
  1. The load method performs a GET operation to the server’s GetView.
  2. Response is returned with html.
  3. jQuery injects the result in the place holder div.
  4. The result is rendered in the browser.
That’s it! No doubt this is real simple to achieve. From here on, the possibilities are quite remarkable. You see, it turns out that when jQuery injects the code, it is also able to inject JavaScript. This means that you can actually render not only html, but JavaScript code as well. Naturally you can argue if this is a good thing or not, but it just gives you a hint on how extensible this can be.
As usual, credits are in order. This great post summarizes different jQuery Ajax approaches with ASP.NET MVC.
BTW: If you ask yourself why you should use jQuery instead of Microsoft Client libraries, I guess you should relate to Microsoft’s statements about “throwing its weight behind jQuery”. You can read about this in Stephen Walther’s blog. Stephen’s conclusion says it all: “Our plan is to focus on jQuery as the primary technology for building client-side Ajax applications moving forward. We want to adapt Microsoft technologies to work great with jQuery and we want to contribute features to jQuery that will make the web better for everyone. We are very excited to be working with the jQuery core team.” Although MVC 3 comes with Microsoft Ajax client scripts, it also includes jQuery and I guess that we’ll see more and more of jQuery in Microsoft’s VS templates.