The Content Query Web Part (CQWP) is the workhorse of SharePoint Publishing sites. The power behind the CQWP is that it allows you to configure a query using the web part tool pane, but then style the resulting XML by using your own custom XSL style sheets. Unless you want to modify the existing XSL file in SharePoint Designer, however, you need to modify a property of the web part (called ItemXslLink) to point to your new, custom XSL file. You can’t update this property within the web part tool pane; the only way you can do it is to utiize a version of the web part that already has this value set. You can do this by either uploading a version of the .webpart file with this property set, or you can add a version of this .webpart file to your Web Part Gallery.

Often times, when developing SharePoint solutions using Visual Studio, the optimal things to do is to add this modified .webpart file to a solution package, and include it in a Feature, so that when the Feature is activated, this modified .webpart file gets uploaded to the Web Part Gallery.

There is a problem, though. The XslItemLink property requires an absolute URL. It does not use a site-collection-relative URL. That is to say, if you give your ItemXslLink property a value of “/XSL Style Sheets/MyStyleSheet.xsl”, it will always look for the file in the site collection with a base URL of “/”, so SharePoint will look for a file with a URL of something like http://mysite.com/XSL Style Sheets/MyStyleSheet.xsl. It doesn’t matter if your site collection is actually located at http://myurl.com/sites/accounting/. If you try to use the original URL in a different site collection, SharePoint displays an error message when you try to use the web part, which says, essentially, you can’t use that XSL file because it’s in a different site collection. If you wanted the style sheet to work in the latter site collection, you’d have to use a different version of the .webpart file that has an ItemXslLink property set to that current site collection’s URL, such as http://mysite.com/sites/accounting/XSL Style Sheets/MyStyleSheet.xsl.

Why is this a problem? Because if I deploy a Feature scoped to the Site Collection level, my assumption is that the Feature should work in any site collection where the Feature is activated, not just in the single site collection where that ItemXslLink property path points to. (It completely defeats the purpose of reusability in Features.)

Although I’m not typically a fan of customization, I don’t see a way around it when dealing with this issue. The code you see below is code you can put in a Feature event receiver that will dynamically look for the CQWPs in your Web Part Gallery (which presumably were just added there when the Feature was activated), search the .webpart XML for the ItemXslLink node, and populate it with a site collection-specific URL.

Please note that the query looks for any web part file in the Web Part Gallery that begins with “CQWP_”. I have purposely given my CQWPs this prefix, so my web parts have names like CQWP_NewsRollup.webpart, etc. You can choose to do a different CAML query if it suits you. Also, please substitute the path to the XSL style sheet with the path to your own style sheet. Finally, this code would obviously work, with a little modification, if you wanted to modify other properties in the CQWP web part file, too.

SPQuery query = new SPQuery();
query.Query = "<Where><BeginsWith><FieldRef Name="FileLeafRef"/><Value Type="Text">CQWP_</Value></BeginsWith></Where>";
SPListItemCollection items = site.RootWeb.GetCatalog(SPListTemplateType.WebPartCatalog).GetItems(query);
if (items.Count > 0)
{
    XmlDocument xmlDoc = null;
    XmlReader xmlReader = null;

    foreach (SPListItem item in items)
    {
        Stream fileStream = item.File.OpenBinaryStream();
        StreamReader fileReader = new StreamReader(fileStream);
        StringReader stringReader = new StringReader(fileReader.ReadToEnd());

        xmlReader = XmlReader.Create(stringReader);
        xmlDoc = new XmlDocument();
        xmlDoc.Load(xmlReader);

        XmlNamespaceManager nsm = new XmlNamespaceManager(xmlDoc.NameTable);
        nsm.AddNamespace("wp", "http://schemas.microsoft.com/WebPart/v3");

        XmlNode propertiesNode = xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[1].ChildNodes[0];
        XPathNavigator navigator = propertiesNode.CreateNavigator();
        XPathNavigator selectedNode = navigator.SelectSingleNode("wp:property[@name='ItemXslLink']", nsm);

       if (selectedNode != null)
       {
            selectedNode.InnerXml = site.ServerRelativeUrl + "/Style Library/XSL Style Sheets/CustomItemStyle.xsl";
            if (item.File.CheckOutType == SPFile.SPCheckOutType.None)
            {
                item.File.CheckOut();
                item.ParentList.RootFolder.Files.Add(item.File.Url, Encoding.UTF8.GetBytes(xmlDoc.InnerXml), true);
                item.File.CheckIn("Modified by feature event receiver.");
            }
        }
    }
}