One of the controls that have received alot of TLC in Sharepoint 2010 is the CssRegistration control. The CssRegistration control had limited functionality in MOSS: it made sure that multiple registered CSS links was only rendered once, but often you want to control the order that these CSS pages are loaded in, and this is supported in SP2010 with the After attribute/property. Read more on the CssRegistration control in Wictor's excellent blog post here.
So far so good, we can control the order of the CssLink's, but what happens when you want to change the CSS files referenced? It is a returning problem that client browsers does not detect changes in CSS, and hence the cached CSS on the clients are not updated before somebody empties the cache for example by pressing CTRL+F5.
So, out of the box CssRegistration solves this problem by adding ?Rev=<id> to the link url. But on several occasions I have noticed that SharePoint *does not* append the ?rev=<id> when using CssRegistration control, so what is going on here?
Example of what works and what doesnt:
This will not render the CSS with a ?rev=<id> tag:
<SharePoint:CssRegistration name="/_layouts/styles/MyCustomer/css/myCustomer.css" After="corev4.css" runat="server"/>
While this *will* render with a ?rev=<id> tag:
<SharePoint:CssRegistration name="MyCustomer/Css/myCustomer.css" After="corev4.css" runat="server"/>
I decided to dig into the code behind CssRegistration and CssLink to find out why.
Deep inside CssLink, the internal methods MakeCssUrl() and MakeLayoutsCssUrl() will check the Name attribute given from either CssLink control or CssRegistration control. If the link is "absolute" (starts with "/") the code simply returns the Name property and uses it without modifications, and hence without the ?rev=<id> tag!
For our second CssRegistration the code will eventually call MakeLayoutsCssUrl(String,Boolean) that will in turn call MakeLayoutsCssUrl(string cssFile, bool bInDesign, bool bLocalizable) with the third bLocalizable parameter hardcoded to true. This method will call the public static method SPUtility.MakeBrowserCacheSafeLayoutsUrl(string name, bool localizable) that in turn will render our CSS link with a nice ?rev=<id> tag.
Note: localization uses CurrentUICulture for determining which LCID folder it should render as link to the CSS. In this example CurrentUICulture is en-US, so the full path to the CSS file will become
Using Thread.CurrentThread.CurrentUICulture.LCID is problematic because SharePoint set this property when rendering the page depending on the current users regional settings. This means that calling code that utilizes CurrentThread.CurrentUICulture (and CurrentCulture) may fail if you dont run inside SPContext (like when calling code from PowerShell -theres a fix for that as well, but I will blog about that another time) if the culture varies from the Operating System language and regional settings.
Also it means that if you have more than one language pack installed on your farm, you need to provision your CSS file to every installed language, since end users can select the sites language to any language that site owner has made available! So even though everything looks fine, the second a user changes his language for example from Danish to English, and you have provisioned your CSS only to _LAYOUTS/1030/styles the whole site will fail to render (note that the page itself could be cached in the browser, so you might need to clear browser cache or do a CTRL+F5 to make the page fail -or render with localized CSS)!
So to sum things up:
Use CssRegistration control to register your CSS files, but be sure to provision the CSS file inside the [LCID]/styles folder of LAYOUTS, or you will not get the ?rev=<id> parameter and hence have problems whenever you update your CSS.
Be careful when you deploy the CSS file. To be sure you need to deploy it to every installed language LCID folder, or you will find that CSS fails on certain sites for certain users (and make sure that if a language pack is installed later on, you must also deploy CSS to support that language)!
You can use PowerShell to find out what languages are installed on your farm:
If you pointed to the wrong LCID ALL web frontends in your farm or your page will fail to render with a message that SharePoint could not create a cache safe URL address for the CSS file since it could not find it!