Web optimization frameworks include two defaults transform type JsMinify and CssMinify which is used by ScriptBundle and StyleBundle respectively. However we can create our own custom transform type to processe references as per our need. To create custom transform type, we need to create class which implements IBundleTransform interface.
IBundleTransform interface define a method named Process which process bundle response. In developer preview version, Process method had only one parameter of type BundleResponse, however onwards RC release, Process method introduced one more parameter of type BundleContext. In this post, we will see how we can utilize this additional parameter while creating our custom transform type.
BundleContext
As name suggest, with BundleContext, we can get information about bundles which could include existing bundle information, bundle url, HTTP context for bundle, etc. Following is the list of all property of BundleContext.
- BundleContext.BundleCollection : We can get collection of all bundles including default and custom bundle in application through this property.
- BundleContext.BundleVirtualPath : This property expose virtual bundle url i.e. ~/bundles/MyBundle.
- BundleContext.HttpContext : This property is type of HttpContextBase, and we can have access of HTTP context through this property. This is very much useful property when we are creating transform type which generate dynamic response. For e.g. we can access query string parameter passed to bundle url (~/bundles/MyBundle?id=123) through this property (context.HttpContext.Request.QueryString["id"]) and we can use it to create dynamic bundle response.
- BundleContext.UseServerCache : Default value of this property is true. It means only first request to bundle url will be intercepted by transform types and once response is generated it will be stored in server cache and further request to bundle url will be served from server cache without processing it. This will help to reduce bundle processing time and to increase performance. If we set BundleContext.UseServerCache to false then all request will be processed by transform type this is only necessary when bundle url are generating dynamic response. See detailed walkthrough later in this post showing how to use this property in accordance with BundleResponse.Cacheability.
- BundleContext.EnableInstrumentation : Default value of this property is false. This is used for tracing and analysis purpose. We can check value of this property and can write tracing code accordingly. We can also set true to this property to enable instrumentation for further lifecycle of Web optimization frameworks for current bundle request.
BundleResponse
BundleResponse is used to retrieve list of files included in bundle so we can process it and generate response for bundle. As BundleResponse is used to generate response of bundles, it needs to take care of two primary properties of generated response. One is response content type and another one is HTTP Cache-Control header. So BundleResponse also expose properties for the same. Following is the list of all properties in BundleResponse class.
- BundleResponse.Files : This is IEnumerable collection of files which is included in bundle. We can iterate through this collection and process file content to generate bundle response.
- BundleResponse.ContentType : Through this property, we can set content type for bundle so that browser can render it appropriately. Default content type "text/html".
- BundleResponse.Cacheability : We can use this property to set Cache-Control HTTP header of bundled response. Default value of this property is Public.
- BundleResponse.Content : Anything which we set as a value of this property, that content will be sent back to browser as a response of bundle.
Following is the complete code which shows how to create custom transform type and how we can use it with bundling.
public class CustomTransformType : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
string strBundleResponse = string.Empty;
foreach (FileInfo file in response.Files)
{
// PROCESS FILE CONTENT
}
response.Content = strBundleResponse;
}
}
Bundle myBundle = new Bundle("~/bundles/MyBundle", new CustomTransformType());
myBundle.Include("~/path/to/file");
bundles.Add(myBundle);
Bundle and truly dynamic response
As we noted earlier, we can set BundleContext.UseServerCache to false in order to process all bundle request and generate dynamic response. Let try to simulate this by small walkthrough and see it works or we need to take care any additional parameter.
public void Process(BundleContext context, BundleResponse response)
{
context.UseServerCache = false;
response.Content = DateTime.Now.ToString();
}
We are returning current date time with UseServerCache set to false. Now try to hit bundle url multiple times by pressing F5. Oops… it seems it has processed bundle response only first time. Let dig more into this, open another browser and hit same url… ahmm it seems it has processed bundle response one more time… again press F5 multiple times…bad luck
As we can see, it seems (read again it seems) it is processing bundle response only first time for separate client (is it really? nop). Nop this is not the case. In fact this is how client deals with it due to HTTP cache control header. Confused? See response header of bundle url to get more information.
As we noted earlier default value of BundleResponse.Cacheability is Public. So even if we have set BundleContext.UseServerCache to false then also due to Expires response header and Public Cache-Control header client is not sending request back to server. So in this case we need to also set BundleResponse.Cacheability to NoCache. We can also set it to Private but in some client we need to press Ctrl + F5 to refresh bundle response.
public void Process(BundleContext context, BundleResponse response)
{
context.UseServerCache = false;
response.Cacheability = HttpCacheability.NoCache;
response.Content = DateTime.Now.ToString();
}
After setting BundleResponse.Cacheability to NoCache try to refresh bundle url again now it is re generating bundle response on each request.