During the development cycle of my latest SharePoint project, we kept getting the same bug reported by the client: a lot of the branding updates weren't visible after refreshing their browsers upon a deployment. The obvious reaction is to have them do a Control + F5 (IE only in this particular client) first, and then panic if that doesn't work. Well, it always worked, so we never panicked, and like any other bug that isn't immediately apparent to end users, this issue slowly became a lower and lower priority.
So we created a feature that deployed an SPWebConfigModification to our web app that stored a revision number (set programmatically to the short date of the latest build). Next we rolled a static property that exposed this as a querystring parameter ready to be tacked onto the end of URLs either via code behind or <% %> tags. Finally we went through our code, and updated all "loose" script tags and CSS includes to consume this (we have a lot of dynamic theming going on). However, we omitted this step for any existing ScriptLink or CssRegistration tags, since they do that automatically.
Or so I thought.
Actually, ScriptLink does the "rev" thing just fine; tacking on a querystring value that "busts" the browser cache by making it technically a new URL. I just assumed that CssRegistration provided this kindness for us, but not so much. Looking back over all the SharePoint projects I've done, I feel like I now owe some old clients a few Control + F5's. No big deal though; just append our cache busting logic to the "Name" property of our CSS controls (which stores the URL to the CSS) in code, redeploy, and be done.
Or, again, so I thought.
After discussing this with Jonathan Rupp (great developer) and Jim Noellsch (great hair) I learned that CssRegistration does indeed have native support for cache busting. Instead of a normal relative URL to a CSS file in the 14 mapped layouts folder, (presumably _layouts/[name of feature]/styles/[name of file].css) you need to set the "Name" property to "../../[name of feature]/styles/[name of file].css" instead. This causes CssRegistration to render a link tag with a value of "/_layouts/1033/styles/../../[name of feature]/styles/[name of file].css?rev=[some hash of the current content of the file]" for the href.
Even though standard WSP deployment doesn't put anything in the 1033/styles folder in 14, the "../../" parent directory fixes it, while preserving the apparent format the CssRegistration control needs to do its cache busting. When the contents of the file changes, the rev querystring value hash changes, and the cache is busted. Otherwise, the "old" CSS file is loaded by your browser. But keep reading for a different, code-based approach, which I usually prefer to markup anyway.
We had these URLs set in the HTML for the "Name" property of our CssRegistrations, so in the code behind of the corresponding pages, we programmatically appended our query string to this value. When we tested, we apparently didn't have any new noticeable CSS changes, so we didn't notice that this wasn't working. After the bug was reported that new CSS still wasn't being manifested, I did a view source, and sure enough, CssRegistration tags didn't have the cache buster URL. So I figured that the hard-coded value in the HTML was winning over the code behind (set in page load) so I redeployed with no Name property in the controls, and the URL completely set in code.
So that didn't work. It turns out the answer was to dynamically create the CssRegistration controls in the page load event, and set everything programmatically as follows:
And there you have it: cache busting CSS using the SharePoint CssRegistration control. This works great; end users never see old CSS. And, by programmatically setting the web.config value in our feature, we get a nice correlation between builds and cache busting URLs. I swore that the CssRegistration control did this out of the box, so it's possible that I'm still missing something and re-inventing the wheel. However, this simple architecture can be applied to any asset included in your site, so there's still plenty of value.
Have fun busting some cache!