Bundling and Minification in ASP.NET MVC 4

The bundling and minification feature enables you to reduce the number of HTTP requests that a Web page needs to make by combining individual files into a single, bundled file for scripts and CSS. It can then reduce the overall size of those requests by minifying the contents of the bundle. Minifying can include activities like eliminating whitespace to shortening variable names to even collapsing CSS selectors based on their semantics. Bundles are declared and configured in code and are easily referenced in views via helper methods which can generate either a single link to the bundle or, when debugging, multiple links to the individual contents of the bundle.

I already have a blog post which explains the bundling and minification features in ASP.NET MVC 4 in following link: http://theshravan.net/bundling-and-minification-support-in-asp-net-mvc-4/

Blog post in above link was written at the time of ASP.NET MVC 4 Beta, final version (ASP.NET MVC 4 RTM) has some minor changes in bundling and minification API, In this blog post, I am going to explain those differences.

The bundling and minification API provides 2 different classes for creating bundles:

  1. System.Web.Optimization. ScriptBundle (For JavaScript Bundles)
  2. System.Web.Optimization. StyleBundle (For CSS Bundles)
namespace System.Web.Optimization
{
    public class ScriptBundle : Bundle
    {
        public ScriptBundle(string virtualPath) : this(virtualPath, (string)null)
        {
        }

        public ScriptBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath,
            new IBundleTransform[1] { (IBundleTransform) new JsMinify() })
        {
            this.ConcatenationToken = ";";
        }
    }
}
namespace System.Web.Optimization
{
    public class StyleBundle : Bundle
    {
        public StyleBundle(string virtualPath) : base(virtualPath, 
            new IBundleTransform[1] { (IBundleTransform) new CssMinify()})
        {
        }

        public StyleBundle(string virtualPath, string cdnPath) : base(virtualPath, cdnPath, 
            new IBundleTransform[1] { (IBundleTransform) new CssMinify() })
        {
        }
    }
}

If we look at code of ScriptBundle & StyleBundle both looks similar. ScriptBundle class constructor uses JsMinify class for JavaScript minification, StyleBundle class constructor uses CssMinify class for CSS minification. Both classes are inherited from same base class System.Web.Optimization.Bundle.

System.Web.Optimization.Bundle class provides three methods for creating bundles:

  1. public Bundle Include(params string[] virtualPaths)
  2. public Bundle IncludeDirectory(string directoryVirtualPath, string searchPattern)
  3. public Bundle IncludeDirectory(string directoryVirtualPath, string searchPattern, bool searchSubdirectories)

We can create bundles with single file or multiple individual files or all the files in a directory or some files  in directory by specify search pattern using wild cards or any combination .

How to create script bundles?

var jqueryBundle = new ScriptBundle("~/bundles/jquery");
jqueryBundle.Include("~/Scripts/jquery-1.7.1.js");
jqueryBundle.Include("~/Scripts/report.js");
BundleTable.Bundles.Add(jqueryBundle);

/* step 1 - create a script bundle
 * step 2 - add the required files using Include() method
 * step 3 - add the bundle to "BundleCollection" using BundleTable.Bundles.Add() method
 */
var jqueryBundle = new ScriptBundle("~/bundles/jquery");
jqueryBundle.Include("~/Scripts/jquery-{version}.js");
jqueryBundle.Include("~/Scripts/report.js");
BundleTable.Bundles.Add(jqueryBundle);
  1. The {version} wild card shown above is used to automatically create a jQuery bundle with the appropriate version of jQuery in your Scripts folder.
  2. For example today we are using version jquery-1.7.1.js in future we might upgrade to latest version (e.g: jquery-1.9.js). The {version} wild card allows you to use NuGet to update to a newer jQuery version without changing the preceding bundling code or jQuery references in your view pages.
  3. The {version} wild card automatically selects the full version for debug configurations and the ".min" version for release builds.
var jqueryValBundle = new ScriptBundle("~/bundles/jquery");
jqueryValBundle.Include("~/Scripts/jquery.unobtrusive*" , "~/Scripts/jquery.validate*");
BundleTable.Bundles.Add(jqueryValBundle);

Here is another wildcard character  “ * ” (asterisk) to Select Files, the virtual path specified in the Include() method and the search pattern in the IncludeDirectory() method can accept one "*" wildcard character as a prefix or suffix to in the last path segment. The search string is case insensitive. The IncludeDirectory() method has the option of searching subdirectories.

How to create CSS bundles?

var siteCssBundle = new StyleBundle("~/Content/css");
siteCssBundle.Include("~/Content/site.css");

var siteThemesCssBundle = new StyleBundle("~/Content/themes/base/css");
siteThemesCssBundle.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");

BundleTable.Bundles.Add(siteCssBundle);
BundleTable.Bundles.Add(siteThemesCssBundle);

How to add bundles to view pages?

We can add script bundles using @Scripts.Render() and CSS bundles using @Styles.Render() methods in view pages by providing the virtual path as parameter.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title - Bundling and Minification</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <meta name="viewport" content="width=device-width" />

        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/modernizr")

    </head>
    <body>
        <!--body html markup-->

        @Scripts.Render("~/bundles/jquery")

        @RenderSection("scripts", required: false)
    </body>
</html>

How to use CDN with Bundling & Minification?

BundleTable.Bundles.UseCdn = true; //enable CDN support

//add link to jquery on the ASP.NET CDN

var jqueryCdnPath = "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";
var jqueryBundle = new ScriptBundle("~/bundles/jquery", jqueryCdnPath).
                            Include("~/Scripts/jquery-{version}.js");
BundleTable.Bundles.Add(jqueryBundle);

In the code snippet above, jQuery will be requested from the asp.net CDN while in release mode and the debug version of jQuery will be fetched locally in debug mode. When using a CDN, you should have a fallback mechanism in case the CDN request fails. The following markup fragment from the end of the layout file shows script added to request jQuery should the CDN fail.

Note: Bundling & Minification is disabled in debug mode (where the compilation Element in the Web.config file is set to debug="true”),  so that It is easy to debug JavaScript in a development environment.

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

</system.web>

We can enable bundling & minification by changing <compilation debug="false" />.  Instead in Application_Start() method in Global.asax set  BundleTable.EnableOptimizations = true  to override the Web.config setting.

All the templates in ASP.NET MVC 4 (except empty template) include the code for bundling & minification for scripts & CSS files in templates.

//Global.asax
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        
        AuthConfig.RegisterAuth();
    }
}

All the bundling & minification code is moved to BundleConfig class under App_Start folder in project.

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 System.Web.Optimization namespace is implemented in System.Web.Optimization.dll. It leverages the WebGrease library (WebGrease.dll) for minification capabilities, which in turn uses Antlr3.Runtime.dll.

5 thoughts to “Bundling and Minification in ASP.NET MVC 4”

  1. Thanks for this. The part that confused me the most was that, if you include *.min.css or *.min.js, when you turn on debugging in the web.config, the bundling process will not include those files. So, some of my included files were sent down the wire, but others (the ones with *.min*) were not. With debugging off, they were all included. Once I changed my bundles to include only the .js versions (not the mins), it worked fine.

  2. Thahks for usefull article, but I did not understand one thing about the last screenshot. When we create bundle “`Content/themes/base/css”, we took each file in directory separately. What is the reason? Why did not used asterisk “`Content/themes/base/jquery.ui.*”?

Leave a Reply