Advanced Bundling Feature in MVC4

This article explains the advanced bundling feature in MVC4.

This article continues from my previous article that described the bundling features basics and showed how it helps us to reduce the page processing time, if you haven't looked at it then kindly go through the following URL.

Bundling in MVC4

Well apart from the features discussed in my previous article there are a few more new features that I would like to be introduced as the advanced features of bundling in MVC4. The following is the list.

1. bundles.UseCDN property:

This property is used to read the contents of the script, images, CSS from online, for which you provide the path. CDN is an abbreviation for “Content Delivery Network”. When you set the value of this property to “true” then you provide the online CDN path for that script, images, CSS as the second parameter for the ScriptBundle / StyleBundle class. The problem with this approach is that it may often fail because of incorrect CDN path/url, or due to some network failure. So when you provide the CDN path then you need to provide the failure mechanism like how to handle an exception inside your catch block. In the same manner you need to check whether the CDN failed or not, if it failed then you need to manually load the scripts, images and CSS files from your application. The following is the syntax of using the CDN property when bundling:

  1. #region UseCDN  
  2. //1. For Using CDN property  
  3.   
  4.  bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/*.css"));  
  5. // Content Delivery Network  
  6.  bundles.UseCdn = true;  
  7.  BundleTable.EnableOptimizations = true;  
  8.  var jqueryPath = "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js";  
  9.   
  10.  bundles.Add(new ScriptBundle("~/bundles/jquery", jqueryPath).Include(  
  11.              "~/Scripts/jquery-{version}.js",  
  12.              "~/Scripts/jquery.unobtrusive*",  
  13.              "~/Scripts/jquery.validate*"  
  14.  ));  
  15.   
  16. #endregion  
In the preceding scenario of using the CDN property, the script images and CSS file will be requested from the CDN path/url when in release/deployment mode and in the case of development mode the file would be requested from the local application. As we pointed out earlier we need to have a failure mechanism if in case the CDN fails. So for this we can implement this mechanism inside the _Layout.cshtml file (where we will render this script, images and CSS). The following would be the code:

“_Layout.cshtml”
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width" />  
  6.     <title>@ViewBag.Title</title>  
  7.     @Styles.Render("~/Content/css")  
  8.     @*@Scripts.Render("~/bundles/modernizr")*@  
  9. </head>  
  10. <body>  
  11.     @RenderBody()  
  12.     @Scripts.Render("~/bundles/jquery")  
  13.   
  14.     <script type="text/javascript">  
  15.         //checking for the jquery is loaded or not. You can also cheche the same by using the below commented line  
  16.         //if(typeof($)=='function') && typeof(jQuery)=='function')  
  17.         if (typeof jQuery == undefined) {  
  18.             //creating the script element  
  19.             var e = document.createElement("script");  
  20.             //setting the src attribute of the script to load dynamically  
  21.             e.src = '@Url.Content("~/Scripts/jquery-1.7.1.min.js")';  
  22.             //settting the type of the script  
  23.             e.type = 'text/javascript';  
  24.             //appending the newly created script element in the header section of the html page  
  25.             document.getElementByTagName("head")[0].appendChild(e);  
  26.         }  
  27.     </script>  
  28.     @RenderSection("scripts", required: false)  
  29. </body>  
  30. </html>  
Just try running the application, once the page is loaded press the F12 key to open the “F12 developer tools” in Google Chrome then go to the Network Tab and again refresh the page by pressing the F5 key. You'll find that since you haven't set the debug attribute to “false” it will load all the script and CSS files from the local application only. Now let's try to force the MVC application to use the CDN path by setting the “EnableOptimizations” property of the BundleTable class inside the BundleConfig.cs file or by setting the compilation debug attribute value to ”false” in the web.config file. Just write the following line beneath your bundle.UseCdn=”true” line.
  1. BundleTable.EnableOptimizations = true;  
Or set the Compilation debug attribute in the web.config file to “false”.
  1. <compilation debug="false" targetFramework="4.0"/>  
And again run the application. This time you'll find that the file has been downloaded from the CDN path. Here is the snapshot for it.

downloaded from the CDN path

NOTE: 
  1. From the preceding screenshot you can find that the jQuery file is been downloaded from the CDN path provided by us.
  2. From the preceding screenshot you can also find the single blob CSS file that is returned by the MVC, if you open that file by just clicking it, you'll find that the MyStyle.css file (classes) is first added and then the Site.css file (classes) are added. That's because we are implementing the virtual parth “~/Content/css with the following code in the BundleConfig.cs file:
    1. bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/*.css"));  
    Since the wildcard character “*” is used, so the default ordering of the CSS files would be made alphabetically done by the MVC. As opposed to this if you provide MVC with the order of the files, then it will render those files in the same order that you provided to the MVC as in the case of jQuery script files. For for example inside the BundleConfig.cs file if you provide the StyleBundle Configuration as below:
    1. bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/Site.css").Include("~/Content/MyStyle.css"));  
    Then in the preceding scenario MVC will load first the Site.css and then the MyStyle.css file.
2. Using IBundleOrderer

This interface allows us to order the Bundles in our own way, in other words it takes the Bundle order in the way we've added them in the BundleConfig.cs file. If in your application you are not using any Custom class that implements the IBundleOrderer interface then in that case the DefaultBundleOrderer class is used to bundle your scripts and .css files. Like in the case of the Controller the DefaultControllerFacotry class is used to process the user request, if your application doesn't have any CustomControllerFactory defined inside your application. For more information about the ControllerFactory you may use the following link.

CustomControllerFactory in MVC4

In the case of the DefaultBundleOrderer class the way it works is a little bit different for css/script/image files. For example if in case your application requires some sort of ordering of CSS/Scripts files and for such your created a bundle like the following one.
  1. //2. For demonstrating the IBundleOrderer interface  
  2.               
  3.             Bundle styleBundle = new StyleBundle("~/Content/css")  
  4.                             .Include("~/Content/Site.css")  
  5.                              .Include("~/Content/MyStyle.css");  
  6.             bundles.Add(styleBundle);  
  7.   
  8.             var scriptBundle = new Bundle("~/bundles/jquery").Include("~/Scripts/jquery-{version}.js")  
  9.                 .Include("~/Scripts/jquery.unobtrusive*")  
  10.                 .Include("~/Scripts/jquery.validate*")  
  11.                 .Include("~/Scripts/Home/Index.js");  
Try running the application, you may find that some of the dependent files are loaded earlier then the parent/ required files on whom they are dependent. For example look at the following screenshot. You can find that the jquery.validate.unobtrusive.js file is loaded before the file jquery.validate.js file. That may create problems for the normal functioning of the application.

dependent files

The reason for the preceding download of script files, is that it may happen because the DefaultBundleOrderer might not consider the order of the Script/css files that you've provided using the Include function and it tries to render the script files, by making use the FileSetOrderList property of the BundleCollection class. This property is of type BundleFileSetOrdering class that comes with the default set of ordering for scripts and CSS files defined within the method “AddDefaultFileOrderings”. The following is the snapshot for it from my solution.

AddDefaultFileOrderings

These are 7 sets of the default FileSetOrderList. You can expand the node and check it.

FileSetOrderList

Or else click the following link:

Ordering of Files within a bundle - What are the known libraries?

It may often happen that the dependent script files are loaded earlier than the main script files that may cause chaotic results leading to script failure errors or an undesired look and feel of your view/page. To overcome this issue we can have a custom class that can implement the “IBundleOrderer” interface where you can specify the file orders and the MVC application will render those files in that order only. This interface lies inside the “System.Web.Optimization” namespace. To demonstrate this I'm creating a new folder inside my application with the name “Utility” and within this folder I'm creating a new class with the name “MyCustomBundleOrderer” that implements the “IBundleOrderer” interface.

NOTE: In case your application is using the CSS files with the name “reset.css” and “normalize.css”, then as per the DefaultBundleOrderer it will add the “reset.css” file first and then the “normalize.css” file”. For this you can have a look at the default ordering of CSS and script file managed by the DefaultBundleOrderer class.

Ordering of Files within a bundle - What are the known libraries?

If you want to override the processing by the DefaultBundleOrderer class then you need to implement the IBundleOrderer interface.

The folllowing is the code snippet for it:
  1. public class MyCustomBundleOrderer : IBundleOrderer  
  2. {  
  3.     public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)  
  4.     {  
  5.         //you can write your own custom logic here like the one following.  

  6.         string[] highPriorityCssOrScripts = { "Site.css""MyStyle.css""jquery-1.7.1.js""jquery-ui- 1.8.20.js""jquery.validate.js""jquery.unobtrusive-ajax.js""jquery.validate.unobtrusive.js" };  
  7.   
  8.         List<FileInfo> lstOrderedFiles = new List<FileInfo>();  
  9.  
  10.         //retrieving all the HighPriority Script files   
  11.         foreach (string strCssOrScriptName in highPriorityCssOrScripts)  
  12.         {  
  13.             FileInfo fileObj = files.FirstOrDefault(delegate(FileInfo fObj)  
  14.             {  
  15.                  return fObj.Name.Equals(strCssOrScriptName, StringComparison.InvariantCultureIgnoreCase);  
  16.             });  
  17.             if (fileObj != null)  
  18.             {  
  19.                 lstOrderedFiles.Add(fileObj);  
  20.             }  
  21.          }  
  22.   
  23.         //Adding the remaining LowScript Files to the List  
  24.         foreach (FileInfo lowFileInfo in files)  
  25.         {  
  26.             if (!lstOrderedFiles.Contains(lowFileInfo))  
  27.             {  
  28.                 lstOrderedFiles.Add(lowFileInfo);  
  29.              }  
  30.         }  
  31.   
  32.         return lstOrderedFiles;  
  33.     }  
  34. }  
And update the BundleConfig.cs file to use the MyCustomBundleOrderer interface. The following is the code snippet for that.
  1. #region IBundleOrderer  
  2.             //2. For using the IBundleOrderer interface  
  3.   
  4.             var styleBundle = new Bundle("~/Content/css");  
  5.             styleBundle.Orderer = new MyCustomBundleOrderer();  
  6.             styleBundle.Include("~/Content/Site.css");  
  7.             styleBundle.Include("~/Content/MyStyle.css");  
  8.             bundles.Add(styleBundle);  
  9.   
  10.             var scriptBundle = new Bundle("~/bundles/jquery");  
  11.             //setting the IBundleOrderer by our custom BundleOrderer Classs  
  12.             scriptBundle.Orderer = new MyCustomBundleOrderer();  
  13.             scriptBundle.Include("~/Scripts/jquery-{version}.js")  
  14.             .Include("~/Scripts/jquery.unobtrusive*")  
  15.             .Include("~/Scripts/jquery.validate*");  
  16.             //add the scriptBundle object to the bundles Collection          
  17.             bundles.Add(scriptBundle);  
  18.   
  19.             #endregion   
Also update the _Layout.cshtml file. Here is the code snippet for that.
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width" />  
  6.     <title>@ViewBag.Title</title>  
  7.     @Styles.Render("~/Content/css")  
  8. @*    @Styles.Render("~/Content/MyStyle")*@  
  9.   
  10.    @* <link rel="Stylesheet" type="text/css" href="@Styles.Url("~/Content/css")" />*@  
  11.       
  12. </head>  
  13. <body>  
  14.     @RenderBody()  
  15.     
  16.   @*  <script type="text/javascript" src="@Scripts.Url("~/bundles/jquery")"></script>*@  
  17.   
  18.    @Scripts.Render("~/bundles/jquery")  
  19.   
  20.    @*  
  21.    <script type="text/javascript">  
  22.         //checking for the jquery is loaded or not. You can also cheche the same by using the below commented line  
  23.         //if(typeof($)=='function') && typeof(jQuery)=='function')  
  24.         if (typeof jQuery == undefined) {  
  25.             //creating the script element  
  26.             var e = document.createElement("script");  
  27.             //setting the src attribute of the script to load dynamically  
  28.             e.src = '@Url.Content("~/Scripts/jquery-1.7.1.min.js")';  
  29.             //settting the type of the script  
  30.             e.type = 'text/javascript';  
  31.             //appending the newly created script element in the header section of the html page  
  32.             document.getElementByTagName("head")[0].appendChild(e);  
  33.         }  
  34.     </script>*@  
  35.     @RenderSection("scripts", required: false)  
  36. </body>  
  37. </html>  
Now try to run your application and go to the network tab. The following would be the snapshot of yours.

network tab

NOTE: If for rendering the css/script file you use the way like the following one described in for example. Then you'll get a single blob of CSS or script file that will load all the CSS and script files in a single file for CSS and script separately. For example: 
  1. <script type="text/javascript" src="@Scripts.Url("~/bundles/jquery")"></script>  
The preceding script will print the snapshot.

script

3. Using BundleFileSetOrdering: You can also use this class for maintaining the order of the Script files. The following is code that you need to implement for using the BundleFileSetOrdering class for sorting the script or CSS files. This could be implemented in the following two ways:
  1. Implementing directly inside the BundleConfig.cs file

    The following is the code snippet:
    1. #region BundleFileSetOrdering  
    2.   
    3.             bundles.FileSetOrderList.Clear();  
    4.   
    5.             BundleFileSetOrdering cssOrScriptFileSetOrdering = new BundleFileSetOrdering("script");  
    6.             cssOrScriptFileSetOrdering.Files.Add("~/Content/reset.css");  
    7.             cssOrScriptFileSetOrdering.Files.Add("~/Content/normalize.css");  
    8.             cssOrScriptFileSetOrdering.Files.Add("~/Content/Site.css");  
    9.             cssOrScriptFileSetOrdering.Files.Add("~/Content/MyStyle.css");  
    10.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery-{version}.js");  
    11.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery.unobtrusive*");  
    12.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery.validate*");  
    13.   
    14.   
    15.             bundles.FileSetOrderList.Add(cssOrScriptFileSetOrdering);  
    16.   
    17.             var styleBundle = new Bundle("~/Content/css");  
    18.             styleBundle.Include("~/Content/*.css");  
    19.             bundles.Add(styleBundle);  
    20.   
    21.          
    22.             var scriptBundle = new Bundle("~/bundles/jquery");  
    23.             scriptBundle.Include("~/Scripts/*.js");  
    24.             //add the scriptBundle object to the bundles Collection          
    25.             bundles.Add(scriptBundle);  
    26.   
    27.           #endregion  
  2. Create a function with the name AddDefaultFileOrderings that is basically a static function of the BundleCollection class. The following would be the code snippet.
    1. public static void RegisterBundles(BundleCollection bundles)  
    2. {  
    3. #region BundleFileSetOrdering  
    4.   
    5.             bundles.FileSetOrderList.Clear();  
    6.   
    7.             var styleBundle = new Bundle("~/Content/css");  
    8.             styleBundle.Include("~/Content/*.css");  
    9.             //bundles.FileSetOrderList.Add(cssFileSetOrdering);  
    10.             bundles.Add(styleBundle);  
    11.   
    12.            AddDefaultFileOrderings(bundles.FileSetOrderList);  
    13.   
    14.             var scriptBundle = new Bundle("~/bundles/jquery");  
    15.             scriptBundle.Include("~/Scripts/*.js");  
    16.   
    17.             //add the scriptBundle object to the bundles Collection          
    18.             bundles.Add(scriptBundle);  
    19.  
    20.  
    21.             #endregion  
    22. }  
    23.   
    24. public static void AddDefaultFileOrderings(IList<BundleFileSetOrdering> list)  
    25.         {  
    26.             BundleFileSetOrdering cssOrScriptFileSetOrdering = new BundleFileSetOrdering("script");  
    27.             cssOrScriptFileSetOrdering.Files.Add("~/Content/reset.css");  
    28.             cssOrScriptFileSetOrdering.Files.Add("~/Content/normalize.css");  
    29.             cssOrScriptFileSetOrdering.Files.Add("~/Content/Site.css");  
    30.             cssOrScriptFileSetOrdering.Files.Add("~/Content/MyStyle.css");  
    31.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery-{version}.js");  
    32.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery.unobtrusive*");  
    33.             cssOrScriptFileSetOrdering.Files.Add("~/Scripts/jquery.validate*");  
    34.             list.Add(cssOrScriptFileSetOrdering);  
    35.         }  
    Updated _Layout.cshtml file.
    1. <!DOCTYPE html>  
    2. <html>  
    3. <head>  
    4.     <meta charset="utf-8" />  
    5.     <meta name="viewport" content="width=device-width" />  
    6.     <title>@ViewBag.Title</title>  
    7.     @Styles.Render("~/Content/css")  
    8.    @* <link rel="Stylesheet" type="text/css" href="@Styles.Url("~/Content/css")" />*@  
    9.     @*@Scripts.Render("~/bundles/modernizr")*@  
    10. </head>  
    11. <body>  
    12.     @RenderBody()  
    13.   @*  <script type="text/javascript" src="@Scripts.Url("~/bundles/jquery")"></script>*@  
    14.    @Scripts.Render("~/bundles/jquery")  
    15.    @* <script type="text/javascript">  
    16.         //checking for the jquery is loaded or not. You can also cheche the same by using the below commented line  
    17.         //if(typeof($)=='function') && typeof(jQuery)=='function')  
    18.         if (typeof jQuery == undefined) {  
    19.             //creating the script element  
    20.             var e = document.createElement("script");  
    21.             //setting the src attribute of the script to load dynamically  
    22.             e.src = '@Url.Content("~/Scripts/jquery-1.7.1.min.js")';  
    23.             //settting the type of the script  
    24.             e.type = 'text/javascript';  
    25.             //appending the newly created script element in the header section of the html page  
    26.             document.getElementByTagName("head")[0].appendChild(e);  
    27.         }  
    28.     </script>*@  
    29.     @RenderSection("scripts", required: false)  
    30. </body>  
    31. </html>  
4. ResetAll(): You can reset all the ordering of bundling using the ResetAll() function of the BundleCollection class. This will remove any custom order that has been applied to the BundleCollection class. The only thing that you need to write is the following line of code in the BundleConfig.cs file.