Chris Roberts

Avoiding JavaScript and CSS Stylesheet Caching Problems in ASP.NET

• Posted in Programming

We frequently run into problems related to caching when building ASP.NET websites. In particular, changes to CSS Stylesheets and JavaScript files on which the sites rely. After a new release our clients often have problems until they manually clear their browser cache. Some of our clients sit behind a proxy server which has its own cache, making the situation even worse.

The problem also extends to our development environment. As more of our UI work is moved into the browser through JavaScript and AJAX, we more frequently run into caching problems ourselves.

So - the solution we sought was to ensure caching when we wanted it, and to avoid it when we didn't!

More specifically, in our development environment, we never want to cache the site's supporting files (as they're likely to be changing quite frequently). In our production environment, we want to cache these files all of the time (for improved performance) - until we make a new release at which point we want to force the clients to pick up the new files.

The Concept

As with all the best things in life (of which this probably isn't one), the concept is simple. All we need to do to force the browser to re-load the file from the server is to give it a different file name. Changing the file name each time we build the site would be a bit of a wrench. Instead, we can add a simple query string to the URL of the file with a new number in it every time we build the site.

As far as the browser is concerned, /scripts/site.js?v=123 is completely different to /scripts/site.js?v=456 - but of course, we know better!

The Implementation

Adding JavaScript files to a page is something that will happen on pretty much every page on the site, so we've added this function to our site's Page base class:

Public Sub RegisterClientJavaScriptFile(ByVal ScriptFilePath As String, Optional ByVal ScriptType As String = "text/javascript")
    Dim ScriptTag As HtmlGenericControl

    ScriptTag = New HtmlGenericControl("script")
    ScriptTag.Attributes.Add("type", ScriptType)

#If DEBUG Then
    ScriptTag.Attributes.Add("src", ScriptFilePath & "?v=" & Now.Ticks)
    Dim Revision as Integer

    Revision = System.Reflection.Assembly.GetExecutingAssembly().GetName.Version.Revision
    ScriptTag.Attributes.Add("src", ScriptFilePath & "?v=" & Revision)
#End If

    Page.Header.Controls.AddAt(0, ScriptTag)
End Sub

Once this is in place, our page simply needs to call the function with the path to the script file as follows:

Call RegisterClientJavaScriptFile("/scripts/site.js")

When the site is built for DEBUG, a reference to the JavaScript file will be added to the site at run-time which adds the current date in ‘ticks' to the query string. This value changes every millisecond or so, which satisfies our need to never cache the files in our DEBUG environment.

When the site is built for RELEASE, the reference to the JavaScript file gets the current assembly's revision number added to the end. This will stay constant for a given release of the site and fulfils our requirement of allowing the client to cache the files between releases.

Once this is implemented, all you need to do is hook your build process up to your version control system to automatically increment the assembly's revision number every time you build, and you're done!

And Stylesheets...?

It's pretty clear to see where this is going, but for the sake of completeness, here's the function we use for Stylesheets...

Public Sub RegisterClientStyleSheetFile(ByVal StyleSheetFilePath As String, Optional ByVal Media As String = "all")
    Dim LinkTag As HtmlGenericControl

    LinkTag = New HtmlGenericControl("link")
    LinkTag.Attributes.Add("rel", "stylesheet")
    LinkTag.Attributes.Add("media", Media)

#If DEBUG Then
    LinkTag.Attributes.Add("href", StyleSheetFilePath & "?v=" & Now.Ticks)
    Dim Revision as Integer

    Revision = System.Reflection.Assembly.GetExecutingAssembly().GetName.Version.Revision
    LinkTag.Attributes.Add("href", StyleSheetFilePath & "?v=" & Revision)
#End If

    Page.Header.Controls.AddAt(0, LinkTag)
End Sub

What's Missing?

Unlike .NET's built in RegisterClientScriptInclude method, there's nothing in this system to stop you adding references to the same file more than once. It also adds the entries directly after the opening <head> tag, which you may not want it to. However, adding them at that point does mean that you can write JavaScript in other parts of your <head> block safe in the knowledge that your includes are already in place.