Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bundling and Minification (ASP.NET 4.0)

0.00/5 (No votes)
28 Jul 2014 1  
Implementing Bundling and Minification in a manageable way

Introduction 

I know it's a little late to talk about ASP.NET 4.0, but I think its really important to put this idea of bundling and minification. I hope everyone today knows the importance of minimizing page resources and increasing the page performance. This article does not talk about why Bundling and Minification is important but talks about how well we can implement that in ASP.NET 4.0.

Bundling and Minification is really good and important for the site performance. We need to find a way to make it so easy to manage so that we would not require to do the Bundling and minification manually. Once defined, our code should take care of these process. 

The code sample that I have provided in this article is 1 year old (with few modifications). I implemented this way of managing the Bundling and Minification in one of the projects and thought I should share this idea.  

Steps to implement Bundling and Minification  

Following are the base points to make Bundling and Minification manageable:

  1. Adding Microsoft ASP.NET Web Optimization Framework: This framework is used to create minified version along with the specified bundles of the files. Download and add file reference to your project from here.
  2. Create an XML file to define bundling of the files. You can define two bundles to each page, one is for the common files bundle which is required on each page. Second will be the bundle for page specific files. Page specific files may include page JavaScript and all the controls JavaScript that are added to the page.
  3. Once the bundles are defined in XML file, you can add the bundling code in global.asax page. This code will take care of creating Bundles and Minification of the files.
  4. Adding bundle reference to a page. If the debug mode is false, then only the Bundle file should get added to the page. Otherwise, each individual file with normal version of the file (not minified) should get added to the page. This makes developer convenient to debug the JavaScript code.

See it in action

Let's implement all steps one by one:

1.   Adding Microsoft ASP.NET Web Optimization Framework

Go to http://www.nuget.org/packages/microsoft.aspnet.web.optimization/

This will instruct you for how to add a reference of System.Web.Optimization.dll to your project. This class take cares the actual Bundling and Minifications.

2.   Create an XML to add bundling definitions as follows:

<Bundling>
  <Js Name="~/AdminJS.js">
    <Path>~/scripts/jquery-1.9.0.min.js</Path>
    <Path>~/scripts/AppCommon.js</Path>
    <Path>~/scripts/jqGrid/jquery.jqGrid.min.js</Path>
    <Path>~/Scripts/jqGrid/grid.locale-en.js</Path>
  </Js>
  <Css Name="~/AdminCss.css">
    <Path>~/Styles/Site.css</Path>
  </Css>
</Bundling>  

XML above defined main Bundle and its child files. In the same way, we can use for creating CSS Bundling.

You may have to define Js, Css and Bundling classes before adding further code. This will allow you to serialize the XML for reading Bundling file details from XML file. Here are those classes:

[Serializable]
public class Js
{
    [XmlAttribute]
    public string Name { get; set; }
    [XmlElement]
    public string[] Path { get; set; }
}
public class Css
{
    [XmlAttribute]
    public string Name { get; set; }
    [XmlElement]
    public string[] Path { get; set; }
}
[Serializable]
public class Bundling
{
    [XmlElement]
    public Js[] Js { get; set; }
    [XmlElement]
    public Css[] Css { get; set; }
} 

3.   Register Bundling information

We have defined the JavaScript files and their Bundle names. Let's see how we can read the Bundle information and add it to memory.

public void AddBundling()
{
    string filePath = String.Empty;
    string bundleName = String.Empty;
    try
    {
        StreamReader reader = new StreamReader(HttpContext.Current.Server.MapPath
              ("~/Settings/FileBundling.xml"));

        XmlSerializer serializer = new XmlSerializer(typeof(Bundling));

        Bundling bundlingInfo = (Bundling)serializer.Deserialize(reader);

        reader.Close();
        reader.Dispose();

        #region Bundling of the css files.

        Bundle cssBundle;

        foreach (Css css in bundlingInfo.Css)
        {
            cssBundle = new Bundle(css.Name);
            foreach (string cssFile in css.Path)
            {
                cssBundle.Include(cssFile);
            }
            BundleTable.Bundles.Add(cssBundle);
        }

        #endregion

        #region Bundling of the java script files.

        Bundle jsBundle;

        foreach (Js js in bundlingInfo.Js)
        {
            bundleName = js.Name;
            jsBundle = new Bundle(js.Name);

            foreach (string jsFile in js.Path)
            {
                filePath = jsFile;
                jsBundle.Include(jsFile);
            }

            BundleTable.Bundles.Add(jsBundle);
        }

        #endregion
    }
    catch (Exception ex)
    {
        throw new Exception("There is a problem while creating Bundle", ex);
    }
} 

The above code adds the Bundles to the application memory. To allow adding these Bundles adding/creating, we will prefer to call those on Application Start event. Here is the code:

void Application_Start(object sender, EventArgs e)
{
    ApplicationSettingsHelper appSettings = new ApplicationSettingsHelper();
    appSettings.AddBundling();
} 

Till this point, we have created the Bundle definition in XML file. According to the Bundle definitions, we added the Bundle to the memory on Application Start event.

Here is the generic code that allows us to add the script file reference to page:

public static void AddScriptFromBundle(string bundleName, Page varThis, string scriptKey)
{
    StringBuilder strScript = new StringBuilder();
    if (HttpContext.Current.IsDebuggingEnabled)
    {
        StreamReader reader = new StreamReader(HttpContext.Current.Server.MapPath
                 ("~/Settings/FileBundling.xml"));
        XmlSerializer serializer = new XmlSerializer(typeof(Bundling));
        Bundling bundlingInfo = (Bundling)serializer.Deserialize(reader);
        reader.Close();
        reader.Dispose();
        foreach (Js js in bundlingInfo.Js)
        {
            if (js.Name.Trim('~') == bundleName)
            {
                foreach (string jsFile in js.Path)
                {
                    strScript.Append(String.Format("<script src=\"{0}?v=" + 
                           ConfigurationManager.AppSettings["ScriptVersion"] + 
                           "\" type=\"text/javascript\">
					</script>", jsFile.Trim('~')));
                }
                break;
            }
        }
    }
    else
    {
        strScript.Append(String.Format("<script src=\"{0}?v=" + 
          ConfigurationManager.AppSettings["ScriptVersion"] + 
				"\" type=\"text/javascript\"></script>", 
          bundleName));
    }
    varThis.ClientScript.RegisterStartupScript(varThis.GetType(), scriptKey, strScript.ToString());
} 

Now why is this code required to add just a one JavaScript reference to the page?

The following points provide the answer:

  • We want to add a script reference at the end of the page not in between the page or user control.
  • Bundle should only get added to the page if the debug mode is false. If the debug mode is true, we want to add every individual JavaScript file to the page. This will allow us to get the clean and normal JavaScript file which we can debug easily.
  • We want to add a versioning to the Bundles of the individual files that are getting added to the page. 

4. Adding Bundle references to the page 

Now, we have all Javascript Bundles ready to add to the page. There are number of ways to add these references to the page. All have approaches have own advantages and disadvantages.

Approach1

Add a simple Javascript Bundle reference to a aspx page. Like we add any javascript reference, we can add Bundle file reference to the page directly.

  <script language="javascript" type="text/javascript" src="/AdminJS.js"></script>

Problem with approach is, it will not add and versioning information for the Bundle.

Approach 2

We can add reference to the page using Scripts.Render method. This methods solves two problems. One is to add Bundle reference to the page and secondly adds versioning information to the page. These versioning information will be managed by the Scripts.Render itself. So every time you change the javascript, Bundle will refresh itself along with the verioning information. It will be application for recycling the app pool. 

  <%:System.Web.Optimization.Scripts.Render("~/AdminJS.js")%>

Ploblem with approach 2 is, it will add reference to the page where we add this line. Prefereable approach will be, add files to the bottom of the page instead of inbetween. 

Approach 3

Below is the approach to inject javascript reference to the page through code. This is a big advantage when you want to bind Javascript reference to the user control and not with the aspx page. So if you add/move user control, your controls Javascript file will move along with the user control. 

There is one more line Page.ClientScript.IsClientScriptIncludeRegistered. This line makes sure, if you add User Control twice to page, Javascrpt will not get add twice but just just once. 

    #region Constants

    private const string JAVA_SCRIPT_CLASS_KEY = "AdminHomePage";
    private const string CONTROL_KEY = "ControlKey";
    private const string SCRIPT_PATH = "~/AdminJS.js";

    #endregion

    protected void Page_Load(object sender, EventArgs e)
    {
        RegisterClientClass();
    }

    /// <summary>
    /// Register javascript class file. Also create a javascript class object.
    /// </summary>
    private void RegisterClientClass()
    {
        if (!Page.ClientScript.IsClientScriptIncludeRegistered(CONTROL_KEY))
        {
            Page.ClientScript.RegisterStartupScript(typeof(Page), CONTROL_KEY, Scripts.Render(SCRIPT_PATH).ToHtmlString());
        }

        Dictionary<string, string> jsControls = new Dictionary<string, string>();
        jsControls.Add(divBannerContainer.ID, divBannerContainer.ClientID);

        WebHelper.RegisterClientScriptClass(this.ClientID, this.Page, JAVA_SCRIPT_CLASS_KEY, jsControls);
    }

Arroach 3 will be the best way to add reference to the page but there is one disadvantage comes with that approach. if I add a custom Javasript to the page it should have version on App Pool recycle and on change in file. But there are files like jQuery plugins, these do not get frequently change over the time. So I would not prefere to add new version information every time I Recycle App Pool. To avoid this problem we can add one custom code to maintain version information from web.config file as mentioned in below approach. 

Approach 4

Below is code for replacement of the Page.ClientScript.IsClientScriptIncludeRegistered method. AddScriptFromBundle method manages versioning information from web.config file.

​  <% ApplicationSettingsHelper.AddScriptFromBundle("/AdminJS.js", this, "DefaultPage"); %> 

Based on the requirements, make your own choice on which approach to implement. 

Points of Interest

Bundling and Minification is always good for the page performance. The above concept really helps to keep us away from worrying about the Bundling and Minification of the JavaScript files.

Also, it is easy to manage the XML to maintain the Bundles for the pages.

I hope this code and concept will help to improve the framework design for projects. I know there is always a scope for improvement and writing good code. Any comments and suggestions will be appreciated.  

Thanks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here