Using ~SiteCollection prefix in SharePoint CSS files


Using CSS files is the recommended way for incorporating the chrome in a SharePoint WCM solution. Among the various definitions of the layout pieces there are also images’ references. Each such reference can be defined using an absolute of relative URL. As long as you use images in the same directory as the CSS file there is no problem at all: you can manage all the paths easily by simply using one of the URL kinds. Unfortunately a problem occurs when you would like to use images from other directories for example the PublishingImages folder. They should be referenced via relative URLs. Using absolute URLs results in problems when a Site Collection doesn’t reside in the root or /sites directory. Also sharing a CSS file among different Site Collections isn’t possible while using PublishingImages folder: there is no simple way to make the images’ paths be defined dynamically based on the Site Collection calling the CSS file.

Facing this challenge Erik and I figured out a solution: letting the CSS files be parsed by the ASP.NET handler and making SharePoint webcontrols write correct URLs based on the caller Site Collection. As this approach requires connecting of a file type with the parser it seems convenient to make all the files with .css extensions get parsed by ASP.NET. As there is a big possibility that you may use more than one CSS definition file within a solution it would create quite an overhead. That’s why we have decided to define a new file extension for our dynamic CSS: CSSX.

Getting our .cssx files parsed by ASP.NET required two steps: sending a .cssx file to the ASP.NET pageHandler (code 1) and making the page built using the buildProvider (code 2). Below you can see both code snippets added within the web.config of the Web Application:

Code 1: Sending .cssx files to the ASP.NET page handler

<system.web>
  <httpHandlers>
    <add verb="GET" path="*.cssx" type="System.Web.UI.PageHandlerFactory" />
  </httpHandlers>
</system.web>

Code 2: Making the page built using the build provider

<system.web>
  <compilation batch="false" debug="true">
    <buildProviders>
      <add extension=".cssx" type="System.Web.Compilation.PageBuildProvider" />
    </buildProviders>
  </compilation>
</system.web>

Then it came to adjusting our CSSX files to the new situation and make them benefit of the SharePoint webcontrols. On the top of the .cssx file we’ve added:

<%@ Page Language="C#"%> <%@ Register Tagprefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Registering the PublishingWebControls tag has granted us access to the $SPUrl prefix which helps creating the Site Collection URLs using ~SiteCollection.

It all worked out pretty well… in Microsoft Internet Explorer. Mozilla Firefox seemed to have problems with parsing the .cssx file. With the FireBug extension I’ve figured out that it doesn’t recognize the .cssx files as CSS. I’ve fixed it by adding ContentType="text/css" to the Page directive:

<%@ Page Language="C#" ContentType="text/css"%>

Now we could use the solution and refer to all image resources like:

background-image: url("<asp:Literal runat='server' Text='<%$SPUrl:~SiteCollection/SiteCollectionImages/someimage.jpg%>'/>");

SharePoint would take care for the proper URL and the literal would render it in the .cssx file.

Knowing this approach will require adding some information in the web.config file, we’ve created a separate feature with a custom featurereceiver that would do this task. Later on we’ve found out that we could use the same feature for adding incorporating some more elements like URL rewriting and captchas.

To make the feature put these code snippets in the right place we have use the SharePoint Add method of the WebConfigModifications collection. Before we did it we also had to define the XML to be added as an instance of the SPWebConfigModification class.

Code 3: Processing the modifications by the feature

SPWebConfigModification modification = new SPWebConfigModification("add[@path='*.cssx']","configuration/system.web/httpHandlers");
modification.Owner = "FeatureName";
modification.Sequence = 1;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Value = "<add verb='GET' path='*.cssx' type='System.Web.UI.PageHandlerFactory'/>";
someWebApplication.WebConfigModifications.Add(modification); modification = new SPWebConfigModification("buildProviders","configuration/system.web/compilation");
modification.Owner = "FeatureName";
modification.Sequence = 2;
modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
modification.Value = "<buildProviders><add extension='.cssx' type='System.Web.Compilation.PageBuildProvider'/></buildProviders>";

someWebApplication.WebConfigModifications.Add(modification);

To process the changes we used the ApplyWebConfigModifications():

SPFarm.Local.Services.GetValue<SPWebService>(someWebApplication.Parent.Id).ApplyWebConfigModifications();
Others found also helpful: