Simplifying prevention of undesired Css/Js file caching

September 26, 2008 at 8:12 PMBen

It's an unfortunate reality that after updating an external CSS or JavaScript file referenced from a web page, not all browsers that have been to your site before will detect an updated file it has previously cached.  When this happens, browsers are running outdated JS and applying old styles to your page elements.

One of the common workarounds for this situation that I've found quite effective is to append some value to the query string of the external file.  So instead of the typical link (to a CSS file) in the Head section of your document,

<link href="styles.css" type="text/css" rel="stylesheet" /> 

You instead use a link with an arbitrary value following the actual file name:

<link href="styles.css?v=1" type="text/css" rel="stylesheet" />

Browsers cache the CSS file with an Id of "styles.css?v=1".  If you update your CSS file, you can change the "v=1" to "v=2" in your HTML page and the browser treats styles.css?v=2 as a different file than styles.css?v=1.  Since styles.css?v=2 isn't already cached, the browser will fetch the latest copy of styles.css from your web server.  Constantly modifying (and trying to remember to modify) the query string value when I make changes to CSS and JS files has always been a manual task for me.  Recently, however, I created a mechanism to automate this process.

The automated process appends the timestamp of the external file (CSS of JS) to the query string following the file's name that is sent to the browser.  The file timestamp is stored in the .NET cache with a cache dependency to the actual file on the web server.  Whenever I update the CSS or JS file, the timestamp is automatically removed from the cache and the next time a visitor arrives at the page and the timestamp is needed, the timestamp is retrieved and stored in cache.  The purpose of the cache is to obviously reduce the amount of file I/O and overall time required in getting the page to the browser.  I've fallen in love with this process as it's made my life easier!

The download link at the bottom of this post contains the code for a static GetFileWithVersion() function.  The same function can be used for any external file that you want to prevent browsers from mistakenly hanging onto a copy of after you may have updated the file.  At present, GetFileWithVersion returns a string value containing the filename and appended query string value.  It's the caller's responsibility to add the necessary link or script tag to the head section of the page.

Example usage of the GetFileWithVersion() function with a CSS file:

string CssFileWithVersion = utils.GetFileWithVersion("MainCss", "~/styles.css");
System.Web.UI.HtmlControls.HtmlLink cssLink = new System.Web.UI.HtmlControls.HtmlLink();
cssLink.Href = CssFileWithVersion;
cssLink.Attributes.Add("type", "text/css");
cssLink.Attributes.Add("rel", "stylesheet");

Example usage of the GetFileWithVersion() function with a JavaScript file:

string JsKey = "MainJs";
if (!ClientScript.IsClientScriptIncludeRegistered(this.GetType(), JsKey))
    string JsFileWithVersion = utils.GetFileWithVersion(JsKey, "~/tools.js");
    ClientScript.RegisterClientScriptInclude(this.GetType(), JsKey, ResolveClientUrl(JsFileWithVersion));

I've been using the GetFileWithVersion() function from master pages and standalone pages for a few weeks now.  Putting this type of mechanism in place is definitely a time saver and since implementing this, I personally haven't seen or heard of any issues with old CSS or JavaScript showing up.

Code Download (1.74 kb)

Posted in: Development

Tags: ,

Comments (2) -

Great idea.

What about changing the

                    version =
                        LastModifiedDate.Year.ToString("0000") +
                        LastModifiedDate.Month.ToString("00") +
                        LastModifiedDate.Day.ToString("00") +
                        LastModifiedDate.Hour.ToString("00") +
                        LastModifiedDate.Minute.ToString("00") +
to simply use:

version = LastModifiedDate.Now.ToString("yyMMddHHmmss");
version = LastModifiedDate.Now.GetHashCode().ToString();

1: DateTime.ToString use a StringBuilder to concatenate the substrings, your code will create around 11 strings before getting the result.. I believe Wink (String objects are immutable: they cannot be changed after they have been created. All of the String methods and C# operators that appear to modify a string actually return the results in a new string object.

2: You don't need 4 digits in the year - proberly you only need the last (it will take 10 years before the chance for a "doubled" version number occur. The GetHashCode produce the shortest string.

Hi Henrik.  Great point about the string immutability.  Looking back at what I did now, I clearly paid little attention to this part of the code.  Both your suggestions are great.  I'll need to update the 3 or 4 projects using this code (DOH!)

Pingbacks and trackbacks (1)+

Comments are closed