ASP.NET 4.5 has introduced built-in support for bundling and minification of javascript and css resources.Here minification refers to the mechanism of compressing the javascript & stylesheet files thus reducing the size of data to transferred over the wire. The process of bundling involves combining multiple javascript/css files into one, thus reducing the number of HTTP calls made from the browser. The following post by Abhijit Jana gives a nice illustration of the feature covering the basics and the advanced areas as well.
http://abhijitjana.net/2011/10/06/bundling-and-minification-in-asp-net-4-5/
Having read this, I was just thinking about these elements/classes under the System.Web.Optimization namespace are all stitched together to implement the bundling and minification functionality.There were some design level questions as well like is the bundled content cached once created at server side or the bundling is done for every request?.
I went through the object model using the Visual Studio 11 Developer Preview and found the following key classes:
- BundleResponse – This class represents a resource bundle which contains the response after the resources are bundled and minified.
- IBundleTransform – This is the interface, defining the contract for transformation i.e. Bundling + Minification
- JSMinify – This is default implementation of IBundleTransform provided by the framework for javascript resources.
- Bundle – This class represents a resource bundle with a list of files or directory from where resources needs to be read. This class accepts the type of transformer in the constructor as shown below:
public Bundle(string virtualPath, System.Type transformType)
NOTE:
- Not quite sure why this signature accepts a System.Type rather than accepting an instance of IBundleTransform.
- The IBundleTransform.Process method should accept a Bundle as input and return BundleResponse as output not a void return type with BundleResponse as input. This does not seem to be a good API design to me.
So I can understand logically that
- A Bundle is defined by providing resource (js/css files & folders) locations
- A class implementing IBundleTransform interface converts this to a minified and merged content and puts them in BundleResponse.
But how IBundleTranform.Process method is invoked? Is there any caching happening?
I implemented a small custom transform class which throws an Exception.
public class MyJSTransform:IBundleTransform { public void Process(BundleResponse bundle) { throw new ApplicationException("..."); } }
The stacktrace captured is as shown below:
[ApplicationException: ...]
BundingDemo.MyJSTransform.Process(BundleResponse bundle) in d:\Demos\BundingDemo\BundingDemo\MyJSTransform.cs:12
System.Web.Optimization.Bundle.GenerateBundleResponse(HttpContextBase context, String bundleVirtualPath) +99
System.Web.Optimization.Bundle.GetBundleResponse(HttpContextBase context, String bundleVirtualPath) +37
System.Web.Optimization.Bundle.ProcessRequest(HttpContextBase context) +25
System.Web.Optimization.BundleHandler.ProcessRequest(HttpContext context) +62
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +116
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
There are many methods which seems to be internal methods and classes, not visible through object browser. So a decompiler is the only resort. From the decompiled code we get some insight into the three critical method:
- BundleHandler
- This is an IHttpHandler implementation handling the JS & CSS resource requests
- The ProcessRequestmethod
- Get the Bundle from the Singleton class BundleTable.
- Invokes Bundle.ProcessRequest method
- Bundle.ProcessRequest
- This method retrieves the bundle url first
- Invokes the GetBundleResponse method
- Bundle.GetBundleResponse
- Perform the cache lookup (normal ASP.NET cache with bundle url as key)
- If there is a cache miss it calls GenerateBundleResponse
- Bundle.GenerateBundleResponse
- Create an instances of BundleResponse class
- Set the files to be processed in correct order
- Invokes IBundleTransform.Process
The storage of the JS content in Cache can be easily viewed using Quick Watch window as shown below:
The entire flow can be summarized as:
