The idea of minimizing and combining multiple script and style files into one file has been popular among web developers for quite some time. With the 4th version of ASP.NET MVC Microsoft introduced a mechanism (called bundles) that allow .NET developers to automate and control this process. Although bundles are quite easy to configure and use, they might sometimes not behave as expected. In this post I’m going to acquaint you with bundles internals and present you ways to troubleshoot problems they may generate.
To examine bundles, let’s create a default ASP.NET MVC project in Visual Studio 2013. This project should have a BundleConfig.cs file in App_Start folder with some bundle routes defined, e.g.:
public class BundleConfig
public static void RegisterBundles(BundleCollection bundles)
After the above code is called from Global.asax on
Application_Start event, a new route will be created and a request to http://localhost:8080/bundles/jquery.js?v=JzhfglzUfmVF2qo-weTo-kvXJ9AJvIRBLmu11PgpbVY1 will render a minimized version of jquery (unless the
<compilation> tag does not have the
debug attribute set to
true). To understand how it works, let’s have a look at how bundles interact with the ASP.NET pipeline. As we know, requests coming to an ASP.NET application need to be served by a handler. At first, a default handler is assigned by IIS based on a mask (handlers tag in applicationhost.config). Then the request is processed by all the HTTP modules defined in the configuration files (in the integrated mode, a precondition must also be fulfilled). Each module has a chance to change the already assigned handler. Finally, the chosen handler processes the request. Starting from .NET 4, there is also a possibility to inject HTTP modules into the ASP.NET pipeline dynamically from our application code. For this purpose, we need to add a
PreApplicationStartMethodAttribute attribute to our assembly. When HTTP runtime detects an assembly with such an attribute, it will execute a method the attribute defines before the application starts. As we are examining bundles, let’s take as an example System.Web.Optimization.dll assembly. It has the following attribute set:
[assembly: PreApplicationStartMethod(typeof (PreApplicationStartCode), "Start")]
PreApplicationStartCode class looks as follows:
public static class PreApplicationStartCode
private static bool _startWasCalled;
public static void Start()
PreApplicationStartCode._startWasCalled = true;
Notice that the above code registers a new
BundleModule in the ASP.NET pipeline:
public class BundleModule : IHttpModule
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
HttpApplication app = (HttpApplication) sender;
if (BundleTable.Bundles.Count <= 0)
Remapping happens only if a
static file with a name equal to our bundle does not exist:
internal static bool RemapHandlerForBundleRequests(HttpApplication app)
HttpContextBase context = (HttpContextBase) new HttpContextWrapper(app.Context);
string executionFilePath = context.Request.AppRelativeCurrentExecutionFilePath;
VirtualPathProvider virtualPathProvider = HostingEnvironment.VirtualPathProvider;
if (virtualPathProvider.FileExists(executionFilePath) ||
string bundleUrlFromContext = BundleHandler.GetBundleUrlFromContext(context);
Bundle bundleFor = BundleTable.Bundles.GetBundleFor(bundleUrlFromContext);
if (bundleFor == null)
context.RemapHandler((IHttpHandler) new BundleHandler(bundleFor, bundleUrlFromContext));
BundleHandler is chosen to process a given request, it creates a context for bundle operations and examines the
BundleTable in search for a bundle that should be sent to the browser. Bundles are cached by their hash so subsequent calls for the same bundle perform much faster than the first one.
IIS Configuration for Bundles
For simplicity’s sake, I will focus only on Integrated Pipie in IIS7+. You need to be sure that ASP.NET handler is called for your bundle requests, otherwise they won’t be served. If you are using URLs in the form of
/bundlename?v=bundlehash, the default handler configuration in IIS (presented below) should be good.
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*."
<add name="StaticFile" path="*" verb="*"
resourceType="Either" requireAccess="Read" />
And in the IIS Failed Request Trace, you should see the following events (I marked in red the ones that are related to bundles):
Notice that the
ExtensionlessUrlHandler-Integrated-4.0 handler assigned at first by IIS is then replaced by
System.Web.Optimization.BundleHandler. We already know that this replacement is ordered by
System.Web.Optimization.BundleModule on the
RESOLVE_REQUEST_CACHE notification (marked in red on the image).
StaticFileHandler (which was in accordance to its handlers mask configuration). Also, it appears that IIS caches which modules were run for a given URL. Thus, even when our application was ready to serve the bundle requests, IIS didn’t run
System.Web.Optimization.BundleModule on them. We eventually removed the .js extension from the bundles URL. Another solution might have been to change the mask for the
*. This would force IIS to run the managed module for all the requests to the application.
If you would like to check which files were included into a bundle, you may tamper the request (using for example fiddler) by modifying the
User-Agent header to
Eureka/1, example request:
GET http://localhost:8080/Content/css?v=WMr-pvK-ldSbNXHT-cT0d9QF2pqi7sqz_4MtKl04wlw1 HTTP/1.1
If-Modified-Since: Sat, 15 Feb 2014 15:52:46 GMT
and the response:
HTTP/1.1 200 OK
Content-Type: text/css; charset=utf-8
Date: Sat, 15 Feb 2014 22:12:52 GMT
/* Bundle=System.Web.Optimization.Bundle;Boundary=MgAwADcANgAwADIAMwAyADUA; */
/* MgAwADcANgAwADIAMwAyADUA "~/Content/site.css" */
I hope that this post helped you better understand ASP.NET bundles. They are a great mechanism to automatically group and minimize script and style files in your application. And if you ever encounter any problems with them, remember about IIS Failed Request Trace and Eureka/1 user agent.
Filed under: CodeProject, Diagnosing ASP.NET