How to use bundling and minification in ASP .NET

What you will learn?

ASP .NET framework provides a native and powerful scripts optimization engine that is very easy to use. In this blog post, I will go through how you can handle your website scripts in an efficient way using bundle and minification techniques. After going through this step-by-step guide - you know how to optimize website scripts properly.

Let's jump in!

Installing the Web.Optimization package

First, make sure the project includes Microsoft.AspNet.Web.Optimization package.

If the package is missing - just install the library using NuGet command:

Install-Package Microsoft.AspNet.Web.Optimization

Preparing BundleConfig inside App_Start folder

Let's create BundleConfig.cs file under App_Start folder (if the folder doesn't exist just create it manually).

Then create the RegisterBundles method with BuncleCollection parameter inside the BundleConfig class. It is a good place to organize your scripts and group them into bundles.

public class BundleConfig
{
   public static void RegisterBundles(BundleCollection bundles)
   {
      //create bundles and add them to BundleCollection
   }
}

Creating bundles

You can create any number of bundles depending on your needs/modules (for instance home page bundles can be different than blog page bundles). It is worth experimenting and checking in practice - what combination of scripts/bundles will be most effective.

In the example below, we see two separate bundles - one for javascript files (ScriptBundle) and the second for CSS (StyleBundle). Each bundle should include relevant scripts.

Once bundles are ready, they should be added to BundleCollection:

public class BundleConfig
{
   public static void RegisterBundles(BundleCollection bundles)
   {
      ScriptBundle scriptBundle = new ScriptBundle("~/bundles/master/js");

      scriptBundle
         .Include("~/assets/js/jquery.js")
         .Include("~/assets/js/plugins.js")
         .Include("~/assets/js/functions.js");

      bundles.Add(scriptBundle);

      StyleBundle styleBundle = new StyleBundle("~/bundles/master/css");

      styleBundle
         .Include("~/assets/css/plugins.css")
         .Include("~/assets/css/style.css")
         .Include("~/assets/css/custom.css");

      bundles.Add(styleBundle);

      BundleTable.EnableOptimizations = true;
   }
}

Forcing optimization

In the next step, you should disable debug mode and force optimization as follows:

<system.web>
     <compilation debug="false" />
</system.web>
BundleTable.EnableOptimizations = true

Bundling and modification won't work without these settings.

Registering the bundling engine

Bundles should be registered when starting the application, so go to Global.asax.cs file and find Application_Start method. If it doesn't exist, just create it end execute 'BundleConfig.RegisterBundle(BundleTable.Bundles)' inside as follows:

protected void Application_Start(Object sender, EventArgs e)  
{  
    BundleConfig.RegisterBundle(BundleTable.Bundles);  
}

Bundling and minification in Razor Views:

How to use bundles in Razor Views?

First of all, make sure ~/View/Web.config file contains the new namespace 

<add namespace="System.Web.Optimization"/>

Then, include the namespace in your Razor View, so you could use the @Scripts and @Styles helpers:

@using System.Web.Optimization;

Finally, you can render the bundles in the View using helpers:

@Scripts.Render("~/bundles/master/js")
@Styles.Render("~/bundles/master/css")

Your master page can look like this:

<!DOCTYPE html>
<html lang="en-US">
<head>
    @Styles.Render("~/bundles/master/css")
</head>
<body>
    @RenderBody()

    @Scripts.Render("~/bundles/master/js")
</body>
</html>

Bonus tip: Resolving browser cache issues

It often happens that after making some scripts changes and new code release, we have unexpected feedback from the QA team/Client:

"It's not working" or "I can't see the changes".

Sounds familiar? 😀

Most of the time it's caused by the browser's caching mechanism.

After a quick website source code review, you may see the following output:

<link href="/bundles/master/css" rel="stylesheet"/>

How to avoid caching issues?

There is a good old trick to make sure the browser will work with the appropriate versions of scripts/bundles.

It’s really easy to force the browser cache to use the latest CSS/JS bundles by appending the query string parameter at the end of each bundle.

Each bundle should have a unique URL after each build.

After googling here and there I found a great code snippet here on StackOverflow.

All credit goes to author Ranjeet patil (thanks)!

internal static class BundleExtensions
    {
        public static Bundle WithLastModifiedToken(this Bundle sb)
        {
            sb.Transforms.Add(new LastModifiedBundleTransform());

            return sb;
        }

        public class LastModifiedBundleTransform : IBundleTransform
        {
            public void Process(BundleContext context, BundleResponse response)
            {
                foreach (var file in response.Files)
                {
                    var lastWrite = File.GetLastWriteTime(HostingEnvironment.MapPath(file.IncludedVirtualPath)).Ticks.ToString();
                    file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", lastWrite);
                }
            }
        }
    }

Using this clean and awesome CSharp extension method - we get an elegant and effective code inside BundleConfig.cs:

public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            ScriptBundle scriptBundle = new ScriptBundle("~/bundles/master/js");

            scriptBundle
                .Include("~/assets/js/jquery.js")
                .Include("~/assets/js/plugins.js")
                .Include("~/assets/js/functions.js");

            bundles.Add(scriptBundle.WithLastModifiedToken());

            StyleBundle styleBundle = new StyleBundle("~/bundles/master/css");

            styleBundle
                .Include("~/assets/css/plugins.css")
                .Include("~/assets/css/style.css")
                .Include("~/assets/css/custom.css");

            bundles.Add(styleBundle.WithLastModifiedToken());

            BundleTable.EnableOptimizations = true;
        }
    }

Now, after each build - you will get a unique extra parameter in your script URL:

<link href="/bundles/master/css?v=rJwD8GQBqCoQ6sc6KrlHuWYflkjwRDAm2RK3fPv7aOk1" rel="stylesheet"/>

This trick should resolve browser caching problems.

Bundles output

After going through all the steps above, you should get nice and warm bundles output:

CSS bundle output:

<link href="/bundles/master/css?v=rJwD8GQBqCoQ6sc6KrlHuWYflkjwRDAm2RK3fPv7aOk1" rel="stylesheet"/>

Js bundle output:

<script src="/bundles/master/js?v=8kmHc-ukmg3rp-jj2rNMjYYIG_lP3ErMNtowPG93XbM1"></script>

Happy bundling & minification 😀

Comments
Leave a Comment