Inconvenient Content Query Web Part and server-relative URLs
Content Query Web Part, Deployment, Development, Inconvenient SharePoint, SharePoint 2010, Structured and repeatable deployment, Tips & Tricks, WCM
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.
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:
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.
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.




January 12th, 2011 at 6:03 pm
Thanks a lot for this solution. It really helped in my extended recurring calendar CQWP. You are wonderful!
January 12th, 2011 at 6:23 pm
@Lavanya: My pleasure. Glad I could help :)
January 18th, 2011 at 4:40 pm
I used this trick to fix a similar problem in the Silverlight webpart. Can't describe how awesome and timely this tip is.
October 31st, 2011 at 12:07 pm
Thanks a lot :)
January 19th, 2012 at 5:56 pm
Hi Waldek.
Returning to an old CQWP to fix this issue when displaying on managed path site collections. The issue I have though is the web part WILL NOT play nice even when the URL's are set nicely like so /sites/new/style library….
It forever displays:
"The web part references an untrusted XSL file. Only XSL files contained in this site's Style Library may be referenced."
This happens both when the URL is set dynamically but more worryingly when jsut hardcoded in the web part. The URL's are correct and reference the right files.
Any idea why I get this behaviour? This can't be right if what you state in this blogs works.
Cheers
January 23rd, 2012 at 1:59 pm
Looking further into the code I don't understand how it works for any of you.
The base CmsDataFormWebPart has a VerifyXslInSafeLocation private method which internally sets up the field hasUnsafeXsl. The default is false and this is the only method that sets it.
Using your feature rewrite and not inheriting it does pass the first if which only continues if the url DOES NOT start with ~sitecollection/style library. In your example it will not so job's a good one.
However the next part sets a flag using a (SPContext.Current.Site.WebApplication.AllowContributorsToEditScriptableParts). It then sets the hasSafeXsl field nagating the flag just set.
To me this means, this NEVER WORKS unless the web application is configured to allow "Scriptable Web Parts" for contributors in the web part security section.
I'm running fresh SP2010 install with no SP's. Unless changed in an update or you have changed the above configuration how is it working for the rest?
January 25th, 2012 at 7:09 am
@TJ: I have just double checked it for you. I have created a new Publishing Site under /sites/site1 and set the ItemXslLink property to:
/sites/site1/Style Library/XSL Style Sheets/ItemStyle.xsl
Everything seems to be working as expected.
Regarding the safeXslLocation: it's not set to ~SiteCollection/Style Library. Please note that before the URL is assigned it's processed by the SPUrlExpressionBuilder which translates ~SiteCollection/ into the real server-relative Site Collection URL.
January 25th, 2012 at 11:21 am
Thanks for the response. Appreicated.
hmmmm…..Where is hasUnsafeXsl set to true if not in the VerifyXslInSafeLocation method? I can't find any other assignment.
If this is the only assignment then the following would always set hasUnsafeXsl to true would it not based on the AllowContributorsToEditScriptableParts check? (that is unless we enable it in WP security which doesn't seem right)
bool flag = (((SPContext.Current != null) && (SPContext.Current.Site != null)) && (SPContext.Current.Site.WebApplication != null)) && SPContext.Current.Site.WebApplication.AllowContributorsToEditScriptableParts;
this.hasUnsafeXsl = !flag;
Am I still missing something :) ?? …I guess so given I can't see how this ever works without enabling contributor script access for web parts.
January 25th, 2012 at 11:39 am
I guess I'm looking in the wrong place though. It's the other check:
if ((!string.IsNullOrEmpty(linkUrl) && !linkUrl.StartsWith(this.safeXslLocation, StringComparison.OrdinalIgnoreCase)) && !linkUrl.StartsWith(this.encodedSafeXslLocation, StringComparison.OrdinalIgnoreCase))
..I should be concerntrating on. Obviously this evaluates to true for some reason for me and not yourself. Question is why considering I definitely are using a url of /sites/new/style library…
I'll post when I find out why maybe for others facing a similar issue. Thanks for your time as usual.
January 25th, 2012 at 5:39 pm
All good.
I have no idea what was going on. I tried another (new) site collection and it worked fine on that one. It remains not working on the old! I'm putting it down as corrupt as it's taken a lot of abuse :)
Happy at last. Onto the next thing. Cheers.
August 31st, 2012 at 11:20 am
Hi Waldek I'm a bit new to Sharepoint. Where do I put this Feature Receiver code?
September 13th, 2012 at 7:30 am
You should put it in the Feature Receiver of the Feature responsible for provisioning preconfigured instance of the CQWP to the Web Part Gallery.
October 3rd, 2012 at 6:35 pm
when i upload a web part .it generates this error.
Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Microsoft SharePoint Foundation-compatible HTML editor such as Microsoft SharePoint Designer. If the problem persists, contact your Web server administrator.
Correlation ID:e87c23cf-3b4d-4e52-8f87-c4925fd2ddf9
how can i get rid of this error . reply as soon as possible
October 3rd, 2012 at 6:37 pm
Check the ULS log to find out what the real error is.
October 3rd, 2012 at 7:01 pm
How to check ULS log ??
October 3rd, 2012 at 7:03 pm
You can use ULSViewer available at http://archive.msdn.microsoft.com/ULSViewer
October 3rd, 2012 at 7:30 pm
me download ULSViewer.exe from this site .but can't find any thing for my help .please elaborate properly plzzzz
October 3rd, 2012 at 8:19 pm
http://blogs.msdn.com/b/opal/archive/2009/12/22/uls-viewer-for-sharepoint-2010-troubleshooting.aspx
November 27th, 2012 at 5:22 pm
Thanks a lot for this blog post, it saved my hours of my precious time!
December 13th, 2012 at 2:47 am
hi
I want to use content query web part but my source library is located in another site collection, by default CQWP does not allow to connect to a library across the site collection. Is there any possible way? I can deploy a sandbox solution if it is possible with some custom coding.
December 14th, 2012 at 9:17 am
For cross-Site Collections content aggregations using search is the only option.
May 16th, 2013 at 12:37 am
Once agin thank you for this excellent blog post.
I reckon, the visual studio solution to deploy my custom CQWP to UAT/Prod environment should be a Farm solution, right? I tried it using Sandboxed solution but I am getting "Unable to load assembly group. The user assembly group provider was unable to provide any user assemblies for the specified assembly group." error message i.e. while trying to activate the solution. Could you please help?
May 17th, 2013 at 12:17 am
Never mind it worked. I created it as a Sandboxed solution and was able to deploy my dev wsp solution to a UAT environment with out a hitch. Once again thank you so much for such a nice work around.