In part 1 of this series, we looked at some tricks to optimize the performance of any website running in IIS 7 by only modifying the web.config. In this part we will focus on handling browser caching issues and optimize the number of JavaScript and CSS files loaded from an ASP.NET website.

NB! All the code (a single .cs file of 125 lines) is included in the zip file at the bottom of this post.

Browser caching

In part 1, we looked at how it was possible to set an expiration header to any static file such as JavaScript and CSS files, so the browser would cache them for a long time and thereby optimize both for bandwidth and the number of requested files going from server to browser.

The problem with setting a browser cache expiration date of i.e. a JavaScript file to a year in the future becomes clear when you change the file before it expires in your visitor’s browsers. They simply won’t see the changes until they either clear their cache or hits F5 manually.

Adding the version number

The only viable way to maintain a far-in-the-future expiration date is to change the URL of the file when the file changes. So instead of including script files like so:

<script type="text/javascript" src=”/scripts/global.js"></script>

…we really want to get a version number included in the src attribute, like so:

<script type="text/javascript" src=/scripts/v_634174870689341736/global.js"></script>

The problem with this is that ASP.NET doesn’t have any feature that will inject a version number, so we have to create that our selves. It is very simple to do so by looking at when the file was last changed and then retrieve the ticks from that date. In the zip file below you’ll find a method that does exactly that and it can be used like so:

<script type="text/javascript" src="<%=BundleHelper.InsertFile("/scripts/global.js") %>"></script>

The BundleHelper.InsertFile method is one you want to use for Stylesheets as well, like so:

<link rel="Stylesheet" href="<%=BundleHelper.InsertFile("includes/style.css") %>" type="text/css" />

Ok, now all our JavaScript and stylesheet references have the version number in the path. Next thing to look at is getting it working with the updated non-existing path.

The HTTP handler

To be able to serve the correct file even with the version number in the path, we need to register an HTTP handler in the web.config’s <system.webServer> section like so:

<add name="ScriptBundler" verb="GET,HEAD" path="*.js" type="FileBundleHandler" />
<add name="CssBundler" verb="GET,HEAD" path="*.css" type="FileBundleHandler" />

The handler we just registered is called FileBundleHandler and knows how to filter out the version number to find the right file. It supports both .css and .js files. The handler also makes sure to both output cache and browser cache correctly. Just add the FileBundleHandler.cs file from the zip file to your website and you are up and running.

Now the browser cache issue has been resolved by adding a version number to the path of the included file and by adding an HTTP handler that knows how to remove it again when serving the file.

Bundle multiple files

Another common website performance issue is that there are many JavaScript and CSS files included on a page. This scenario results in the browser have to download a lot of extra files and that all slows down the performance of a website. The solution to this is also very simple when you’ve first completed the above steps to register the HTTP handler in web.config and called the BundleHelper.InsertFile method when inserting JavaScript and CSS files.

The folder structure convention

There are many ways of bundling files into a single request, like Justin Etheredge’s Squisher. For this example I have chosen a convention based approach because that doesn’t require any code to implement.

Any given ASP.NET website might have a folder structure similar to this:

The folder convention supported in the FileBundleHandler lets you reference a folder instead of just a file. Both the HTTP handler and the BundleHelper.InsertFile understand when a folder is referenced and automatically bundles all the .js or .css files to a single response. So in order to bundle all the files in a given folder, simply reference the folder name and add the extension of the types of files you want bundled. Having the folder structure above, you can add a bundle like so:

<script type="text/javascript" src="<%=BundleHelper.InsertFile("/scripts/common.js") %>"></script>

Notice that the file /scripts/common.js doesn’t exist, but the folder /scripts/common does. By adding .js at the end, we tell the HTTP handler to look for all files with the same file extension – in this case .js files. It bundles all the files in alphabetical order and serve the as a single response. For security reasons, the HTTP handler will only serve .css and .js extensions.


Since we are now running all JavaScript and stylesheet files in bundles and through the HTTP handler, it makes sense to also look at the content of the files to optimize even further.

For this example I’m using the Microsoft Ajax Minifier (MAM), which is a single .dll file capable of minifying both JavaScript and stylesheets. The MAM is my favorite JavaScript minifier since it not only removes whitespace, it also rewrites variable and function names and a lot of other things as well. For me it has proven a better choice than the YUI Compressor and Google Closure Compiler. The stylesheet minifier feature of MAM also looks very nice, but I have honestly never used it before except for this example.

Basically what MAM does is that it optimizes and removes unwanted whitespace from both JavaScript and stylesheets. The HTTP handler makes use of MAM for both single files and bundled ones, so you get full benefit no matter your scenario.


No matter if you use the website model, the web application model or ASP.NET MVC you are now able to utilize the browser cache to the fullest. Furthermore, by bundling your files using the folder convention you can minimize the number of requests sent by the browser. Both JavaScript and stylesheet files are also minified and optimized for even smaller file sizes sent over the wire.

It's worth noticing that the output caching respects file changes and therefore refreshes evertime changes are made to the JavaScript and CSS files tunnelled through this code.

Following the techniques in part 1 combined with this example will improve any website’s server-to-browser performance substantially.


  1. Download the zip file below and place the AjaxMin.dll in your bin folder.
  2. Then place the FileBundleHandler.cs in your App_Code folder if you use the website model – otherwise place it where ever it makes sense in your structure.
  3. Now register the HTTP handler in your web.config under the <system.webServer> section like so:

    <add name="ScriptBundler" verb="GET,HEAD" path="*.js" type="FileBundleHandler" />
    <add name="CssBundler" verb="GET,HEAD" path="*.css" type="FileBundleHandler" />
  4. The last thing you need is to start using the BundleHelper.InsertFile method on your pages for both JavaScript and stylesheets like so:

    <script type="text/javascript" src="<%=BundleHelper.InsertFile("/scripts/common.js") %>"></script>
    <link rel="Stylesheet" href="<%=BundleHelper.InsertFile("styles/global.css") %>" type="text/css" />


FileBundler.zip (89,95 kb)


In this first installment of performance tuning tricks for ASP.NET and IIS 7 we will look at some of the easy, yet powerful possibilities in the web.config file. By taking advantage of these few tricks we can increase the performance of any new or existing website without changing anything but the web.config file.

The following XML snippets must be placed in the <system.webServer> section of the web.config.

HTTP compression

You’ve always been able to perform HTTP compression in ASP.NET by using third-party libraries or own custom built ones. With IIS 7 you can now throw that away and utilize the build-in compression available from the web.config. Add the following line to enable HTTP compression:

<urlCompression doDynamicCompression="true" doStaticCompression="true" dynamicCompressionBeforeCache="true"/>

By default, only text based content types are compressed.


Setting this attribute to true enables compression of dynamically generated content such as pages, views, handlers. There really aren’t any reasons not to enable this.


This attribute allows you to decide whether or not you want static files such as stylesheets and script files to be compressed. Images and other non-text content types will not be compressed by default. This is also something you want to enable.


If you do output caching from within your ASP.NET website, you can tell IIS 7 to compress the output before putting it into cache. Only if you do some custom output caching you might run into issues with setting this to true. Try it and test it. If your website works with this enabled, then you definitely want to keep it enabled.


By default, only text based content types are compressed. That means if you send application/x-javascript as content type, you should change it to text/javascript. If you use some custom modules in your website, then you might experience conflicts with the IIS 7 compression feature.


Cache static files

To speed up the load time for the visitors, it is crucial that everything that can be cached by the browser IS cached by the browser. That includes static files such as images, stylesheets and script files. By letting the browser cache all these files means it doesn’t need to request them again for the duration of the cache period. That saves you and your visitors a lot of bandwidth and makes the page load faster. A well primed browser cache also triggers the load and DOMContentLoaded event sooner.

By adding this snippet to your web.config, all static files are cached in the browser for 1 year:

     <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00"/>

This setting sets the expiration date of the file one year in the future. It does that by setting an HTTP header that instruct the browser to add the file to its internal cache. If you hit F5 or ctrl-F5, the browser will request the files no matter what the expiration is set to.

A major problem with client-side caching is if your static files change before the cache expires. Then the visitor with the old version in the cache won’t see the new file until she clears the browser cache or hit F5. Therefore, this setting must be used with caution and probably with a shorter expiration time. In part 2 of this series I’ll address this problem and provide a simple solution to it.


Make sure that user sensitive information isn't cached on the browser. It will then be available by anyone else using the same browser.



I recently had to come up with a good argument for upgrading a Windows Server 2003 to Windows 2008 R2. It is used as a web server, so the benefit I was looking for was the upgrade of IIS 6 to IIS 7.5.

So I thought, why not make two deploys of the exact same website to both server editions and compare the performance. It so happens to be that there are many performance comparison articles about this floating around, but no one addressed the client-side aspect of the performance differences.

I’d already prepared the <system.webServer> section of the web.config with various performance tricks for IIS 7. Of course, IIS 6 ignores the <system.webServer> section and therefore don’t get any of the performance enhancements.

I’ve recorded the performance using YSlowand here is what the result looks like:

IIS 6 vs. IIS 7
Click for larger image

The reason for the slightly different file sizes is due to the compression level of the native IIS 7 compression and my custom compression library. The interesting part is when the browser’s cache is primed. That means when the visitor has visited the website before.

It should be noted that it is indeed possible to achieve the same performance metrics using IIS 6, but then you either need to add custom HTTP handlers or write to the IIS 6 metabase.

Here are the performance optimization tricks I used.


I’ve used Google’s Page Speed plug-in for Firebug a lot since it was released last year. Even though it’s not as good as Yahoo’s YSlow plug-in, it’s still very usable for some scenarios YSlow doesn’t support – my favorite being the analysis of unused CSS and selector optimizations.

It also has a feature that will tell you how much your web page will gain by minifying the HTML.

Not only does it analyze the difference but it can also generate an optimized version of your HTML. It removes unnecessary whitespace which in most cases are pretty harmless. But, it does more than that. It actually strips out attribute quotes from the HTML elements as well as remove the closing </body> and </html> tags. This renders the entire page invalid according to any WC3 (X)HTML standards. 

Even though it is a good idea to minify your HTML, this feature of the Page Speed plug-in makes it completely useless to me. Unless you’re Google, Twitter or Facebook, this feature is just strange.


At the Mix10 conference, the Windows Phone 7 teams had some very big announcements – a lot of it had been kept secret and first revealed to the public now. There is however still some details they keep secret. Some of these secrets are the user agents of Internet Explorer for Windows Phone 7, which they simply wouldn’t give us.

After playing with Windows Phone 7 we managed to secure a copy of the user agent string. The user agent for IE on Windows Phone 7 running on the Asus Galaxy device is:

Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0) Asus;Galaxy6

Notice that it identifies the browser as IE7 and the operating system as Windows Phone OS 7. The IE team told us that the browser in Windows Phone 7 is a mobile version of IE7 with certain features ported from IE8. So it doesn’t use the full Trident 4 layout engine that IE8 uses, but instead Trident version 3.1 with a few extra capabilities.

The user agent was retrieved from server logs, so it is the actual user agent from the actual browser.