2

Caching CSS & JavaScript using Rewrite Rules in IIS6

Recently I’ve been working on improving the caching within the website at Huddle to improve performance for users. After reading around, I found the following rules to be valuable.

  • Cache your CSS and JavaScript forever (set a long, long expiry)
  • Handle updates by changing the URL of your CSS and JavaScript

When caching “forever” the main problem is if you release an update to “styles.css” the browser caches the URL of that file, but won’t pick up any future updates unless the user clears their browser cache (you don’t want to have to tell people to “Ctrl + F5″). By changing the URL of the file, the browser thinks it’s requesting a new file, and your updates get applied. But how do you go about doing this without it becoming a pain to manage?

One common solution I found was to add a querystring to the url for the CSS/JavaScript. For example:

\webresources\css\styles.css?version=3

This is commonly used, but not 100% effective. According to the HTTP caching specification, the user agent should cache a URL ignoring the querystring. Carl Henderson goes into more detail about it in his article, Serving JavaScript Fast. So, according to the specification, “styles.css?version=3″ is no different from “styles.css” as “?version=3″ is ignored.

Firefox and IE ignore this specification, so using a querystring will still work. However, browsers such as Opera and Safari do apply the HTTP caching specification, so using a querystring won’t work.

Anyway to be safe, I took a different approach. Using the C# codebehind of our MasterPage we read the LastWriteTime of the styles.css file and include this in the filename. For example:

/webresources/css/styles.{mmhhddMMyy}.css
/webresources/css/styles.1220010108.css

Here’s a snippet of the C# code used:

protected override void OnPreRender(EventArgs e)
{
    CssMerged.Href = string.Format(CssMerged.Href, GetVersion("/webresources/css/merged.css", "CssVersion"));
}

public string GetVersion(string filePath, string cacheName)
{
    FileInfo file = new FileInfo(Server.MapPath(filePath));

    string date;

    if (HttpRuntime.Cache[cacheName] == null)
    {
        if (file != null)
        {
            date = file.LastWriteTimeUtc.ToString("mmhhddMMyy");
            HttpRuntime.Cache.Add(cacheName, date, null, DateTime.Now.AddMonths(3), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);
        }
        else
        {
            date = "0000000000";
        }
    }
    else
    {
        date = HttpRuntime.Cache[cacheName].ToString();
    }

    return date;
}

Using Ionic’s ISAPI Rewrite Filter in IIS we have a simple rule to ignore the “.1220010108″ part of the URL request. So that styles.1220010108.css points to styles.css.

RewriteRule ^/webresources/css/(.*).([0-9]{10}).css /webresources/css/$1.css [I]
RewriteRule ^/webresources/javascript/(.*).([0-9]{10}).js /webresources/javascript/$1.js [I]

I could write a more generic rule, but for now the above is fine. This was easy to apply as we only have one file for our CSS and one for our JavaScript. For developing we keep all of our CSS and JavaScript in separate files. However, when performing a release build we merge all of the CSS/JavaScript into one file each to reduce the number of requests needed in a single page load. We then compress and validate it using YUICompressor. More on that in another post though.

Moving forward, we may move to IIS7 eventually, but for now will be sticking with IIS6. In doing so it would mean we could implement something such as Url Rewriter (rather than Ionic’s ISAPI Rewrite Filter) within the web application to handle the rewriting. The above can also be applied to images, but in our case that’s not so easy to do. That’s a story for another time.

Related Posts

  • Cornelius Breslauer

    Hey, I came across this blog post while searching for help with JavaScript. I have recently switched browsers from Opera to Internet Explorer 7. Just recently I seem to have a problem with loading JavaScript. Everytime I go on a page that requires Javascript, the site doesn't load and I get a "runtime error javascript.JSException: Unknown name". I can't seem to find out how to fix it. Any help is very appreciated! Thanks

  • Robert

    Hmmm I don't know if I can be much help around client issues. The only times I use IE is during cross browser testing! How come you switched away from Opera? Not the most helpful response, but I'd stick with Opera, or try Firefox or Google Chrome.