Inconvenient Content Query Web Part and server-relative URLs


Content Query Web Part is probably the best solution for creating dynamic content rollups in SharePoint 2010. It’s lightning fast and because it’s using XSLT it allows you to easily change layout without touching your data. And although changing the presentation in Content Query Web Part is a matter of a few mouse clicks, it gets quite inconvenient when you start using custom XSLT files.

What’s wrong with Content Query Web Part?

Imagine the following scenario: you’re working on a SharePoint 2010 solution and you need to create a custom content roll-up. To simplify the whole process you decide to use the Content Query Web Part. Because you need to make the rollup fit the branding of the solution, you create a custom Item Style. Just when you’re about to deploy it, you consider one more requirement: what if the Solution won’t be deployed to a root Site Collection but to one within a Managed Path? This is exactly where the things go wrong…

When working with the Content Query Web Part and custom Item Styles you have two options: you can either add a custom template to the standard ItemStyle.xsl file or create your own XSLT file and make the CQWP use it instead of the default file. Modifying the standard ItemStyle.xsl file is more of a customization approach. If you’re looking for a more repeatable and cleaner approach you will very likely choose for the second approach and create your own XSLT file.

Configuring the CQWP to use your custom XSLT files instead of the default ones isn’t that difficult. All you need to do is to export the CQWP and change the value of the MainXslLink, HeaderXslLink or the ItemXslLink property, depending on which file you want to override, and the CQWP will nicely load the specified file. There is however more to it than only providing the right URL.

Anatomy of Content Query Web Part’s XSL URLs

Although the Content Query Web Part allows you to use custom XSLT files instead of the default ones, there are some restrictions that you have to keep in mind.

First of all the Content Query Web Part allows you to link only to XSLT files located in the Style Library. If you try to use an XSLT file located in the Layouts folder or anywhere else you will get an exception.

Exception while previewing a Content Query Web Part using an XSLT file from the Layouts directory

Another restriction that you have to keep in mind is that the Content Query Web Part allows you to load only XSLT files located in the Style Library of the current Site Collection. If you try to load an XSLT file located in the Style Library of another Site Collection you will get the same exception as while trying to load a file from the Layouts directory.

What the issue really is

Dependency on the XSLT files located in the same Site Collection as where the Content Query Web Part is being used, is the real problem that we’re discussing here. The CQWP requires you to provide a server-relative URL to your custom XSLT files. And while you might think that you can get away with using a URL starting with /Style Library/ think twice: are you sure there will never be another Site Collection created on a Managed Path that will be using your Solution?

If you’ve been working with SharePoint for a while you might want to use the ~SiteCollection token to create a valid server-relative URL for the current Site Collection. Usually how it works is, that as soon as the file is being provisioned to SharePoint through Feature activation, SharePoint sees the token and replaces it with the URL of the Site Collection on which the Feature has been activated. Not here. If you provision a .webpart file with CQWP using a tokenized XSLT URL, the ~SiteCollection token won’t be replaced and the .webpart file will get provisioned to the Web Part Gallery as-is. On top of that the CQWP itself doesn’t understand the tokens either. Although you might expect it to be smart enough to translate the ~SiteCollection token, it doesn’t do it. As soon as you try to open the CQWP using a tokenized XSLT URL you will see an exception in the CreateChildControls method:

Exception in the Content Query Web Part’s CreateChildControls method thrown when trying to use a tokenized XSLT URL

So how do THEY do it?

At this moment you might start wondering how Microsoft solves this issue. After all you can use the standard Content Query Web Part on any Site Collection whether it’s a root one or not, and the CQWP works without any problems. How did Microsoft solve the issue of server-relative URLs?

By default the CQWP doesn’t have the XSLT URLs set. Because those properties are empty it falls back to the default URLs which are resolved programmatically using the URL of the current Site Collection. And although the CQWP could easily handle resolving tokens it doesn’t, so we have to figure out a solution ourselves.

Solving the issue of server-relative URLs and Content Query Web Part

One of the possible solutions that you might consider is to subclass the Content Query Web Part and extend it with the support for tokenized URLs. While you could do it, it has a serious drawback: subclasses of the Content Query Web Part are not allowed in Sandboxed Solutions. As you might have heard SharePoint Online 2010 will allow you to host Publishing Sites and using Sandboxed Solutions will very likely be the only way to deploy your customizations. It would be a serious limitation of your solution not to allow deployment in SharePoint Online, especially when there’s another way to solve this challenge.

Feature Receiver to the rescue

Another way to get around this issue is to use a Feature Receiver. After activating the Feature that provisions the .webpart file of the Content Query Web Part with custom XSLT files, it would modify the contents of the .webpart file and replace the tokens with server-relative URL of the current Site Collection. This could be done using the following code snippet:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SPSite site = properties.Feature.Parent as SPSite;

    if (site != null)
    {
        SPList webPartsGallery = site.GetCatalog(SPListTemplateType.WebPartCatalog);
        SPListItemCollection allWebParts = webPartsGallery.Items;
        SPListItem webPart = (from SPListItem wp
                              in allWebParts
                              where wp.File.Name == "MyCQWP.webpart"
                              select wp).SingleOrDefault();
        if (webPart != null)
        {
            string siteCollectionUrl = site.ServerRelativeUrl;
            if (!siteCollectionUrl.EndsWith("/"))
            {
                siteCollectionUrl += "/";
            }

            string fileContents = Encoding.UTF8.GetString(webPart.File.OpenBinary());
            fileContents = fileContents.Replace("~sitecollection/", siteCollectionUrl);
            webPart.File.SaveBinary(Encoding.UTF8.GetBytes(fileContents));
        }
    }
}

As the Feature deploying Web Parts is scoped to Site Collection we can easily retrieve the Web Part Gallery (5). From there we can easily retrieve our Content Query Web Part using LINQ (7-10). Once we have the URL of the current Site Collection (13-17) we can retrieve the contents of the .webpart file (19), replace the tokens with the URL (20) and save the changes back to SharePoint (21).

Although the above code snippet retrieves a single .webpart file with a specific name, you could easily make it more dynamic and make it automatically retrieve all .webpart/.dwp files provisioned by the Feature and rewrite tokens in there.

If you look at the Content Query Web Part it will all work okay now: the ~SiteCollection tokens are replaced with correct server-relative URLs what allows the CQWP to retrieve the custom XSLT files.

Preview of a Content Query Web Part using a custom XSLT file showing titles of three news items

Summary

Content Query Web Part provided with SharePoint Server 2010 is a great solution for creating dynamic content rollups. While it provides you with many configuration options out of the box, it has a serious shortcoming when it comes to using custom XSLT files. Although it might seem limiting at first it can be easily solved using a custom Feature Receiver.

Technorati Tags: SharePoint 2010

Others found also helpful: