Apr 20th 2008 09:51 pm

YSlow and ASP.NET - Expires Header (Part 1 of 3)

At our company there’s a focus on getting our asp.net web application to perform in IE6/7 and FF.  Our site uses javascript and css heavily which results in less than optimal performance.  In an effort to improve performance we took proactive steps by:

  • the size of viewstate and http response on all asp.net requests
  • logging the number of queries used on each http request
  • using the excellent tool YSlow to get insight into the actual content rendered

In future posts I plan to touch upon changes we made in response to these results including minimizing our javascript and css files with YUICompressor and concatenating the results to reduce the number of http requests through our continuous integration process.

The specific challenge I hope to illustrate concerns the default asp.net http headers for content expiration.  By default, asp.net and IIS serve resources with “Cache-Control: private”.  When you close your browser (or the current tab) the content may be cached (depending on your browser (FF and IE6/7 each handle this differently). When the user returns to your site he will either re-request the file and receive an HTTP 304 (”not modified since”) response or download the entire file again. 

In the case where your site has lots of images, js, or css files, you can end up issuing a lot of needless http requests for recurring users.  If instead you were able to set the http cache header to something in the far future, then the browser wouldn’t request the file in the first place.  Scott Hanselman has a good discussion of this behavior here.

In order to cache items at the client you need a way to version them e.g. “~/images/logo.jpg” becomes “v1.0.0/images/logo.jpg”. This way when the resources change they have new paths. These new paths are served up in your markup- the img.src attribute is now different than anything in cache and the browser must therefore request the new version (whose expiration header is also set to a far future date).

The catch is that while asp.net uses the ResolveUrl method and natively changes “~/images/logo.jpg” to “/myapplication/images/logo.jpg”, there is no such mechanism built into the framework to make it translate “~/images/logo.jpg” to “/myapplication/v1.0.0/images/logo.jpg”.

This is a fun problem to solve. It has two parts.

First we must create a framework so that static content can be served easily from the markup by substituting “~/images/logo.jpg” for “<%$ Images: logo.jpg %>”. 

Part 2 will be addressed using expressionBuilders  

Secondly we need to create the framework so that in the code behind, we can replace img.url = “~/images/logo.jpg” with img.url = StaticContent.images.logo_jpg.

Part 3 will be addressed using a build provider.

Both parts will require changes to our build process not covered here.  The continuous integration process should include in its build archive a directory of the static content named by the version number (e.g. v1.0.0) and updating a application configuration property with the specific version so that the application can reference the static content directory properly. 

The Continuous Integration changes will not be covered here because they are very specific to one’s own continuous integration process.  We currently use nant with xmlPreprocess to generate our configuration files for each version and environment (prod, dev, qa, etc.), but there are a ton of ways to do this, all outside of asp.net. Using a small ruby script we generate an additional master settings file to incorporate the latest version number in the master configuration.

No Comments yet »

Trackback URI | Comments RSS

Leave a Reply

« What I wish I knew about Service Broker before I started using it | YSlow and ASP.NET - Concatenating and Minifying Css and Js Resources »