Inconvenient Content Query Web Part and provisioning it to the right place

XML snippet that provisions an instance of the Content Query Web Part.
Content Query Web Part is one of the most frequently used Web Parts available out of the box with SharePoint Server 2010. Thanks to its flexibility, great performance and rich configuration possibilities it’s a great solution for aggregating content. Because the presentation layer of the CQWP is based on XSLT, the possibilities are virtually unlimited, but as soon as you start using custom XSLT stylesheets some strange things start to happen.

The problem

If you have worked with the Content Query Web Part at least a little and used some custom XSLT stylesheets you probably noticed that there is a “bug” in the provisioning process of CQWP instances that use custom XSLT.

Imagine the following module that is supposed to provision a Content Editor Web Part and a Content Query Web Part just in that order:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">  
  <Module Name="Pages" Url="$Resources:osrvcore,List_Pages_UrlName;" Path="Pages">
    <File Url="CQWPTest.aspx" Type="GhostableInLibrary" Path="TemplatePage.aspx" IgnoreIfAlreadyExists="TRUE">
      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/WelcomeLinks.aspx, Summary links" />
      <AllUsersWebPart WebPartZoneID="TopColumnZone" WebPartOrder="1">
        <![CDATA[<?xml version="1.0" encoding="utf-8"?><WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title>Content Editor</Title>
  <FrameType>Default</FrameType>
  <Description>Allows authors to enter rich text content.</Description>
  <IsIncluded>true</IsIncluded>
  <ZoneID>TopColumnZone</ZoneID>
  <PartOrder>1</PartOrder>
  <FrameState>Normal</FrameState>
  <Height />
  <Width />
  <AllowRemove>true</AllowRemove>
  <AllowZoneChange>true</AllowZoneChange>
  <AllowMinimize>true</AllowMinimize>
  <AllowConnect>true</AllowConnect>
  <AllowEdit>true</AllowEdit>
  <AllowHide>true</AllowHide>
  <IsVisible>true</IsVisible>
  <DetailLink />
  <HelpLink />
  <HelpMode>Modeless</HelpMode>
  <Dir>Default</Dir>
  <PartImageSmall />
  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>
  <PartImageLarge>/_layouts/images/mscontl.gif</PartImageLarge>
  <IsIncludedFilter />
  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
  <ContentLink xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[​Hello World]]]]><![CDATA[></Content>
  <PartStorage xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
</WebPart>]]>  
      </AllUsersWebPart>
      <AllUsersWebPart WebPartZoneID="TopColumnZone" WebPartOrder="2">
        <![CDATA[<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" />
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="Title" type="string">My Content Query</property>
        <property name="Description" type="string">Displays a dynamic view of content from your site.</property>
        <property name="ChromeType">TitleOnly</property>
        <property name="ChromeState">Normal</property>
        <property name="ItemLimit" type="int">15</property>
        <property name="SortBy" type="string">{8c06beca-0777-48f7-91c7-6da68bc07b69}</property>
        <property name="SortByDirection" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+SortDirection,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c">Desc</property>
        <property name="GroupStyle" type="string">DefaultHeader</property>
        <property name="ItemStyle" type="string">Default</property>
        <property name="ServerTemplate" type="string"></property>
        <property name="ItemXslLink" type="string">/Style Library/XSL Style Sheets/MyItemStyle.xsl</property>
      </properties>
    </data>
  </webPart>
</webParts>  
]]>
      </AllUsersWebPart>
    </File>
  </Module>
</Elements>  

If you associate that module with a Feature and provision the page you will see the following result:

A Publishing Page with a Content Query Web Part and a Content Editor Web Part below it.

In spite of the Web Part Order specified in both AllUserWebPart tags, the Web Parts are provisioned to the page in the wrong order.

The confusing part is that as soon as you remove the ItemXslLink definition from the CQWP XML both Web Parts will appear on the page in the correct order just as you would expect:

A Publishing Page with a Content Editor Web Part and a Content Query Web Part below it.

The workarounds

The above issue is quite confusing, and although it’s easy to reproduce, so far there is no explanation of this behavior. Nevertheless when working on a solution you might need to provision a page with multiple Web Parts on it among which a Content Query Web Part with some custom XSLT. So what are the options?

Code when in doubt

Provisioning Web Parts declaratively is not the only place in SharePoint when things can get confusing. For quite some time now many people used code as a workaround. Although it’s more complicated and more difficult to maintain, provisioning configuration through code gives you more control of the configuration process and in case of trouble you can always attach a debugger and step through your code to get some more information.

In case of provisioning a Content Query Web Part linked to custom XSLT stylesheets you could create a Feature with a Feature Receiver and programmatically create an instance of the CQWP with its configuration and add it to your page. Because you have to code the whole process yourself, you are 100% in control of what’s happening and could easily work around the issue with positioning the CQWP.

While this might seem like an ideal solution, it’s quite difficult to manage not to mention the configuration of the Web Part being hidden inside an assembly.

Fifty-fifty

Another approach that you might consider is a combination of the declarative and code-based provisioning process. First you would provision all Web Parts through a module and then using a Feature Receiver you would reposition the Web Parts on the page so that everything looks like it should.

This approach is definitely better than coding everything yourself, as it allows you to leverage the pieces of the declarative deployment that work and still allow you to manipulate the process and rearrange the Web Parts on the page.

One downside that is worth consideration before you choose for this approach is the fact that the complete deployment is not being done in one place what adds some complexity to the maintenance of the solution. Luckily there is yet another approach worth trying.

Provisioning Content Query Web Part instances in a declarative way – revisited

Just recently, while helping out on a SharePoint 2010 project, I stumbled upon the issue with provisioning CQWP instances which use custom XSLT and having them rendered at the top of the Web Part Zone. After having done some tests I came to some surprising conclusions.

It works

First and the most important of all: it is possible to declaratively provision an instance of the Content Query Web Part that uses custom XSLT and have it rendered on the desired place within a Web Part Zone. There is one detail that you have to keep in mind when working with CQWP instances with custom XSLT.

The Web Parts’ Collection is 0-based

If you have looked carefully at the sample code I showed above you noticed that I set the value of the WebPartOrder attribute on the CEWP to 1 and to 2 for the CQWP. While it worked correctly when the CQWP didn’t use custom XSLT, it provisioned the CQWP on top once I set the value of the ItemXslLink property on the CQWP.

However as soon as you change the values of the WebPartOrder attributes and start numbering from 0, everything will work as expected. After having modified the above example to:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">  
  <Module Name="Pages" Url="$Resources:osrvcore,List_Pages_UrlName;" Path="Pages">
    <File Url="overview.aspx" Type="GhostableInLibrary" Path="TemplatePage.aspx" IgnoreIfAlreadyExists="TRUE">
      <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/WelcomeLinks.aspx, Summary links" />
      <AllUsersWebPart WebPartZoneID="TopColumnZone" WebPartOrder="0">
        <![CDATA[<?xml version="1.0" encoding="utf-8"?><WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title>Content Editor</Title>
  <FrameType>Default</FrameType>
  <Description>Allows authors to enter rich text content.</Description>
  <IsIncluded>true</IsIncluded>
  <ZoneID>TopColumnZone</ZoneID>
  <PartOrder>1</PartOrder>
  <FrameState>Normal</FrameState>
  <Height />
  <Width />
  <AllowRemove>true</AllowRemove>
  <AllowZoneChange>true</AllowZoneChange>
  <AllowMinimize>true</AllowMinimize>
  <AllowConnect>true</AllowConnect>
  <AllowEdit>true</AllowEdit>
  <AllowHide>true</AllowHide>
  <IsVisible>true</IsVisible>
  <DetailLink />
  <HelpLink />
  <HelpMode>Modeless</HelpMode>
  <Dir>Default</Dir>
  <PartImageSmall />
  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>
  <PartImageLarge>/_layouts/images/mscontl.gif</PartImageLarge>
  <IsIncludedFilter />
  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
  <ContentLink xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[​Hello World]]]]><![CDATA[></Content>
  <PartStorage xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor" />
</WebPart>]]>  
      </AllUsersWebPart>
      <AllUsersWebPart WebPartZoneID="TopColumnZone" WebPartOrder="1">
        <![CDATA[<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" />
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="Title" type="string">My Content Query</property>
        <property name="Description" type="string">Displays a dynamic view of content from your site.</property>
        <property name="ChromeType">TitleOnly</property>
        <property name="ChromeState">Normal</property>
        <property name="ItemLimit" type="int">15</property>
        <property name="SortBy" type="string">{8c06beca-0777-48f7-91c7-6da68bc07b69}</property>
        <property name="SortByDirection" type="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart+SortDirection,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c">Desc</property>
        <property name="GroupStyle" type="string">DefaultHeader</property>
        <property name="ItemStyle" type="string">Default</property>
        <property name="ServerTemplate" type="string"></property>
        <property name="ItemXslLink" type="string">/Style Library/XSL Style Sheets/MyItemStyle.xsl</property>
      </properties>
    </data>
  </webPart>
</webParts>  
]]>
      </AllUsersWebPart>
    </File>
  </Module>
</Elements>  

I got the following result:

A Publishing Page with a Content Editor Web Part and a Content Query Web Part below it.

The CQWP is positioned below the CEWP just as we wanted.

During the tests I found that it’s important to always have a Web Part provisioned to the 0th place. If you had for example two CEWP’s provisioned to indexes 1 and 2, and would then want to provision the CQWP to either 1, 2, 3 or anything above it would still end up on the top at position 0. It is therefore important to always provision the first Web Part to position 0 to be sure that things will keep working as you expect them to.

Summary

SharePoint Server 2010 ships with the Content Query Web Part which is a powerful solution for creating dynamic content aggregations. Unfortunately there is a known issue when provisioning CQWP instanced that use custom XSLT stylesheets. By ensuring that there is a Web Part provisioned to the 0th index in the Web Part, you can easily work around that issue without using a single line of custom code.

Technorati Tags: SharePoint 2010,Content Query Web Part

Comments

comments powered by Disqus