Optimising ASP.NET Web Pages Sites - Bundling And Minification

ASP.NET 4.5 saw the introduction of new features for improving the performance of web sites. Delivered within a new library called System.Web.Optimization, bundling and minification enables you to combine multiple JavaScript or CSS files into one bundle, and to minify them thereby reducing the number of HTTP requests that browsers have to make, reducing the size of the files, and improving performance of the site overall, especially over slower (read 3G) networks.

If you create your Web Pages site using Visual Studio Express 2012 or Visual Studio 2012, System.Web.Optimization.dll is included as part of the template, which is an extended version of the WebMatrix Starter Site template. And then you have to unpick all the clutter that you don't want. You should not remove Antlr3.Runtime.dll or WebGrease.dll, as these are required components for the Bundling and Minification system.

If you create your site using WebMatrix, none of the templates include System.Web.Optimization.dll. You have to get it from Nuget. You can do that using the Nuget Gallery feature, although that was playing up when I tried, so I used the Package Adminstration Manager. Just to add a layer of confusion, you need to install a package called Microsoft.AspNet.Web.Optimization. Not only that, but the package is only available outside of the default WebMatrix packages, so you need to set the Source to Default (All).

Once you have installed it, you are able to get working with the new features.

Minification takes place automatically. The process reduces white space in .js and .css file, removes any comments and in the case of .js files, will also shorten variable names. Significant file size savings can be achieved. Bundling, on the other hand, needs to be managed programmatically.

A Bundle is a collection of files that will be combined into one file. Basically, their contents are minified, and then appended to eachother. Obviously, especially in the case of .js files, it's important to ensure that files are added to bundles in the correct order. Bundles are stored in a BundleTable object as a BundleCollection. The best place to create a BundleCollection is in an _AppStart.cshtml file:

@using System.Web.Optimization;
@{
    var bundles = BundleTable.Bundles;


}

Notice that System.Web.Optimization is referenced via a using directive.

You create bundles based on their content. Javascript bundles are created using the ScriptBundle type. Style sheet bundles are created using the StyleBundle type. For example, here's how to create a ScriptBundle and add it to the BundleTable:

var jquery = new ScriptBundle("~/bundles/jquery");
jquery.Include("~/Scripts/jquery-1.8.1.min.js");
bundles.Add(jquery);

The new ScriptBundle object is given a virtual path which can be anything you like. It effectively acts as a name by which the bundle can be identified. It does not have to match an existing path in the web site folder structure. In fact, something as simple as "~/jquery" will do. Once the bundle has been instantiated, you specify the actual files that should be included by using the Include() method. The virtual path you provide here must relate to the actual file location otherwise the file will not be found. In the example above, the file has been specified by name, but there are other ways to specifiy the contents of a bundle as you will see soon. Multiple files can be specified by passing their virtual location in to the Include method as a comma-separated list or an array of strings. Finally, the bundle is added to the BundleTable's BundleCollection using the BundleCollection's Add method.

StyleBundle objects are created in exactly the same way:

bundles.Add(new StyleBundle("~/bundles/css").Include(
                "~/Styles/site.css"
            ));

This example illustrates method chaining to produce a more concise syntax.

The following shows a couple of files being added to a bundle, using different methods to define the files:

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

The {version} wildcard allows you to update to newer versions of jQuery without having to change the code the references the file. At the moment, I have 1.8.1 in my site. If 1.8.2 comes out, I just replace the file. 1.8.2 will be picked up automatically by the bundling mechanism. The wildcard does more than that. It picks up the full version during debug, and any minified version if the site it set to release mode. Debug and release will be discussed a bit later.

The second file features use of the * wildcard character. Any file matching the specified pattern will be included, with multiple files added in alphabetical order. Only one wildcard can be used per virtual path. In the ecxample above, the wildcard is used as a suffix. It can also be used as a prefix to the file extension:

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                "~/Scripts/*.js"
           ));

This will result in all files with a .js extension being included in the one bundle in alphabetical order. Often, multiple fileswill have dependencies on others so the order in which files are referenced is important and needs managing. For that reason, you are unlikely to use * wildcard matching like that very often.

The IncludeDirectory method offers another blunt instrument for specifying multiple files:

bundles.Add(new StyleBundle("~/allStyles").IncludeDirectory("~/Styles", "*.css")); 

This results in all .css files in the Styles folder being bundled in alphabetical order so doesn't differ from the previous wildcard example. However, the IncludeDirectory method offers an overloaded version that allows you to include subfolders in the search for matching files:

bundles.Add(new StyleBundle("~/allStyles").IncludeDirectory("~/Styles", "*.css", true));   

Having created bundles, you need a way to consume them in your pages. There are two classes to manage this: Scripts and Styles. Both have a static Render method which takes a comma-separated listof virtual paths, or an array of them. These paths are the identifiers you gave your bundles. Here is an example of a busier _AppStart.cshtml file:

@using System.Web.Optimization;
@{
    var bundles = BundleTable.Bundles;
    
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js",
                    "~/Scripts/jquery.validate*",
                    "~/Scripts/jquery.unobtrusive*"
                ));

    bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
                    "~/Scripts/knockout-{version}.js",
                    "~/Scripts/knockout.mapping-latest"
                ));

    bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/modernizr-*"
                ));

    bundles.Add(new StyleBundle("~/bundles/css").Include(
                    "~/Styles/site.css"
                ));

    bundles.Add(new StyleBundle("~/bundles/jquery-css").Include(
                    "~/Styles/jquery.ui.core.css",
                    "~/Styles/jquery.ui.resizable.css",
                    "~/Styles/jquery.ui.selectable.css",
                    "~/Styles/jquery.ui.accordion.css",
                    "~/Styles/jquery.ui.autocomplete.css",
                    "~/Styles/jquery.ui.button.css",
                    "~/Styles/jquery.ui.dialog.css",
                    "~/Styles/jquery.ui.slider.css",
                    "~/Styles/jquery.ui.tabs.css",
                    "~/Styles/jquery.ui.datepicker.css",
                    "~/Styles/jquery.ui.progressbar.css",
                    "~/Styles/jquery.ui.theme.css"
                ));           
}

Three ScriptBundles have been created along with two StyleBundles. To render them, you include a using directive for System.Web.Optimization and then use their respective Render method:

@using System.Web.Optimization;
@{
    
}

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>My Site's Title</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        @Scripts.Render("~/bundles/jquery", "~/bundles/knockout", "~/bundles/modernizr")
        @Styles.Render("~/bundles/css", "~/bundles/jquery-css")
    </head>
    <body>
        
    </body>
</html>

At the moment, if you run the page, the files are all referenced correctly:

However, if you look at the jquery file that has been included, you see that it is the full development version. Equally, the debug version of knockout has been included. None of the files have been minified. This is because, by default, Web Pages applications run in debug mode as dictated by the compilation setting in the web.config file. You need to set debug = false to get minification to work. This also helps to improve the performance of your deployed site:

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.0" />
  </system.web>
</configuration>

Having made that change, if you re-run the site, the bundles are transformed, minified and referenced by the value you provideed as their virtual paths:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>My Site's Title</title>
        <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <script src="/bundles/jquery?v=MIr0sK49sxIK-epXF1SVcUd13V3eqzcL7nEICnt3vnc1"></script>
<script src="/bundles/knockout?v=pfuk13BYMQiDE7BiWZNzr6dcinflTsFL5KnKHpmJ4nY1"></script>
<script src="/bundles/modernizr?v=Vd40cG5fYxxjdknf_y9ilK-zi7pnjL35tk9IAsOQgQc1"></script>

        <link href="/bundles/css?v=qqSzEfGVYWAqpZj10F9l3OmsaavLPt8zTTTTuSNgO9s1" rel="stylesheet"/>
<link href="/bundles/jquery-css?v=ps9Ga9601PrzNA2SK3sQXlYmNW3igUv5FOdOPWptyus1" rel="stylesheet"/>

    </head>
    <body>
        
    </body>
</html>

Bundling sets the content expiration for each bundle to 12 months so that they are cached by the browser. The querystring value that is generated for each bundle does not change unless the content of the bundle changes. At that point, a new querystring value is generated which results in the browser downloading the revised bundle immediately.

Bundling and minification are considered advanced topics, which is why the library wasn't included as part of the WebMatrix Razor templates. However, the library is very easy to add to your WebMatrix site and to use. A lot of Javascript libraries come with minified versions, so the minification system doesn't provide a lot of additional assistance there, but it certainly produces benefits on your own script and style files.

 

Date Posted: Saturday, September 15, 2012 5:05 PM
Last Updated: Thursday, September 20, 2012 3:38 PM
Posted by: Mikesdotnetting
Total Views to date: 39013

5 Comments

Wednesday, December 5, 2012 3:26 PM - Kenneth

Why does no one ever mention the difference in approaches of using a BundleConfig.cs file (where your resources are hard-coded) vs using the bundle.config xml file? Is there some particular pro or con for using BundleConfig.cs? It seems like a more dynamic approach to use an xml file that you could modify without redeploying if needed.

Tuesday, February 26, 2013 10:30 AM - Naveen

Do you know if there is a way to get this work for ASP.NET 3.5 Web Forms application?

Sunday, September 14, 2014 2:03 PM - Michael

Hi Mike,

Great article that's exactly what is missing in the asp.net community content. The page about bundling still says that it's not supported in webmatrix, but that doesn't mean that it's impossible to do it at all. Thanks!

Michael

Tuesday, November 11, 2014 1:53 PM - swapnil

I did bundling as per above code. I need to performed the minification for some css files. Kindly provide the minification steps .

Tuesday, November 11, 2014 8:06 PM - Mike

@swapnil

Read the article above. It includes CSS files.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Allen Michaels 12/17/2014 4:37 PM
In response to Cascading DropDownLists with jQuery and ASP.NET
Fantastic thank you so much!...

Emily 12/17/2014 12:36 PM
In response to Parameterized IN clauses with ADO.NET and LINQ
Thanks, very helpful!!!! Can i use this for multiple in's ????? IN (.....) and IN(...) and IN...

sss 12/16/2014 3:06 PM
In response to Solving the Operation Must Use An Updateable Query error
good...

Gjuro 12/15/2014 10:30 PM
In response to Examining the Edit Methods and Edit View
You have one fromr (and it should be from, I suppose). :-)...

Gjuro 12/15/2014 10:27 PM
In response to Adding Search
Hi, thnx for all this C#->VB translations. Yet, the following code block is (slightly) in error it a...

Scot 12/14/2014 1:39 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Thanks,Mike I found solution....

Gjuro 12/13/2014 10:52 PM
In response to Accessing Your Model's Data from a Controller
The article mentions "Creating an Entity Framework Data Model for an ASP.NET MVC Application" (at is...

Samuel 12/13/2014 8:40 AM
In response to Displaying The First n Characters Of Text
I have failed to use the extension because it throws an error that it doesn't recognise the chop be...

Ignas 12/12/2014 5:11 PM
In response to Cleaner Conditional HTML Attributes In Razor Web Pages
Any suggestions for Html Helper elements with HtmlAttributes, when you need to conditionally set it...

Gautam 12/11/2014 8:50 PM
In response to Validation In Razor Web Pages 2
Hi Mike Is this required for V3, non html helper input...