Sunday, June 06, 2010

Connecting XsltListViewWebParts together in code

Last week a client ran into some trouble trying to connect two XsltListViewWebParts (XLV) together using a feature receiver. Google yielded little so I thought I'd blog about it.

Assume that we have two lists. One list is called Categories and the other list is called Items. The Items list has a lookup field to the Categories list Title field, and the name of the lookup field is Category.

First, the two web parts need to be added to a web part page, in our case inside a module.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="WebPartModule">
    <File Path="WebPartModule\default.aspx" Url="default.aspx" >
      <View List="$Resources:core,lists_Folder;/Categories" Url="" BaseViewID="0" WebPartZoneID="_RightColumn" WebPartOrder="3" ID="wpCategories" >
        <![CDATA[
              <webParts>
                  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
                      <metaData>
                          <type name="Microsoft.SharePoint.WebPartPages.XsltListViewWebPart,Microsoft.SharePoint,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" />
                          <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
                      </metaData>
                      <data>
                          <properties>
                              <property name="AllowConnect" type="bool">True</property>
                              <property name="ChromeType" type="chrometype">Default</property>
                              <property name="AllowClose" type="bool">False</property>
                          </properties>
                      </data>
                  </webPart>
              </webParts>
          ]]>
      </View>
      <View List="$Resources:core,lists_Folder;/Items" Url="" BaseViewID="0" WebPartZoneID="_LeftColumn" WebPartOrder="1" ID="wpItems">
        <![CDATA[
              <webParts>
                  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
                      <metaData>
                          <type name="Microsoft.SharePoint.WebPartPages.XsltListViewWebPart,Microsoft.SharePoint,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" />
                          <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
                      </metaData>
                      <data>
                          <properties>
                              <property name="AllowConnect" type="bool">True</property>
                              <property name="ChromeType" type="chrometype">Default</property>
                              <property name="AllowClose" type="bool">False</property>
                          </properties>
                      </data>
                  </webPart>
              </webParts>
          ]]>
      </View>
    </File>
  </Module>
</Elements>

As configured here, there would be two XLV web parts places on the default.aspx file. What is important to notice here is that the View elements have an ID attribute which we are setting to something recognizable. This will be important later.

The next step in the puzzle is to create the connection between the web parts. I have seen samples that do this in XML, but I am not sure how that can be done for the XLV and we chose to write code in the feature receiver that accomplished this.

First we get an instance of the limited web part manager class for the page that we added these web parts to. In our case it is the default.aspx page. We then use the IDs form the XML as noted above to retrieve the web parts themselves. Without this ID, we don't have a robust way of fetching the web parts out of the collection.

// Get the web object form the feature properties
SPWeb web = (SPWeb)properties.Feature.Parent;

// Get the webpart manager
SPLimitedWebPartManager webPartManager = web.GetLimitedWebPartManager("default.aspx", PersonalizationScope.Shared);

// Get the webpart by id
System.Web.UI.WebControls.WebParts.WebPart providerWebPart = webPartManager.WebParts["wpCategories"];
System.Web.UI.WebControls.WebParts.WebPart consumerWebPart = webPartManager.WebParts["wpItems"];

Next we need to get the connection points of the web parts. This is where things start to get interesting. The XLV exposes two consumer connection points and two provider connection points. I used powershell to have a look at the connection points, and the result is the following:

Provider:

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartTable
ID : TableProvider
DisplayName : Table

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartRow
ID : DFWP Row Provider ID
DisplayName : Row of Data

Consumer:

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartParameters
ID : DFWP Parameter Consumer ID
DisplayName : Parameters

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartParameters
ID : DFWP Filter Consumer ID
DisplayName : Filter Values

I will be honest and admit that I don't know what all of them are intended to do. However when we connected the web parts by hand in the UI, I noticed that we were using the 'Filter Values' connection point and the 'Row of Data' connection point. Thus I know we need to use these in the code.

// Get the connection point for the consumer.
System.Web.UI.WebControls.WebParts.ConsumerConnectionPointCollection consumerConnections =
 webPartManager.GetConsumerConnectionPoints(consumerWebPart);
ConsumerConnectionPoint consumerConnection = consumerConnections["DFWP Filter Consumer ID"];

// Get the connection point for the provider.
System.Web.UI.WebControls.WebParts.ProviderConnectionPointCollection providerConnections =
 webPartManager.GetProviderConnectionPoints(providerWebPart);
ProviderConnectionPoint providerConnection = providerConnections["DFWP Row Provider ID"];

Now that we have the connection points, we want to use the LimitedWebPartManager to create a connection and save it to the collection of connections. This will not work without one extra step however, since the provider and consumer interfaces are not compatible. The exact error that you will see is:

The provider connection point "Row of Data" on "wpCategories" and the consumer connection point "Filter Values" on "wpItems" do not use the same connection interface.

In order to make this work, we need to create an instance of a RowToParametersTransformer, and pass it the field names that we want to map. If you remember, we had a lookup field named Category in the items list, and it pointed to the Title field in the Categories list. So we want to map these field in the connection.

// Get a new  RowToParametersTransformer object.
RowToParametersTransformer trans = new RowToParametersTransformer();
trans.ConsumerFieldNames = new string[] {"Category"};
trans.ProviderFieldNames = new string[] {"Title"};

// Get a new connection using the 2 connection points.
Microsoft.SharePoint.WebPartPages.SPWebPartConnection newConnection =
 webPartManager.SPConnectWebParts(
  providerWebPart, providerConnection,
  consumerWebPart, consumerConnection, trans);

// Add the new connection
webPartManager.SPWebPartConnections.Add(newConnection);

It took us a while to figure this one out, the RowToParametersTransformer threw us for a loop. Hope it helps someone out there.

The complete code for easier reading:

// Get the web object form the feature properties
SPWeb web = (SPWeb)properties.Feature.Parent;

// Get the webpart manager
SPLimitedWebPartManager webPartManager = web.GetLimitedWebPartManager("default.aspx", PersonalizationScope.Shared);

// Get the webpart by id
System.Web.UI.WebControls.WebParts.WebPart providerWebPart = webPartManager.WebParts["wpCategories"];
System.Web.UI.WebControls.WebParts.WebPart consumerWebPart = webPartManager.WebParts["wpItems"];

// Get the connection point for the consumer.
System.Web.UI.WebControls.WebParts.ConsumerConnectionPointCollection consumerConnections =
 webPartManager.GetConsumerConnectionPoints(consumerWebPart);
ConsumerConnectionPoint consumerConnection = consumerConnections["DFWP Filter Consumer ID"];

// Get the connection point for the provider.
System.Web.UI.WebControls.WebParts.ProviderConnectionPointCollection providerConnections =
 webPartManager.GetProviderConnectionPoints(providerWebPart);
ProviderConnectionPoint providerConnection = providerConnections["DFWP Row Provider ID"];

// Get a new  RowToParametersTransformer object.
RowToParametersTransformer trans = new RowToParametersTransformer();
trans.ConsumerFieldNames = new string[] {"Category"};
trans.ProviderFieldNames = new string[] {"Title"};

// Get a new connection using the 2 connection points.
Microsoft.SharePoint.WebPartPages.SPWebPartConnection newConnection =
 webPartManager.SPConnectWebParts(
  providerWebPart, providerConnection,
  consumerWebPart, consumerConnection, trans);

// Add the new connection
webPartManager.SPWebPartConnections.Add(newConnection);

6 comments:

Matt Blodgett said...

Joe,

I can't thank you enough for this post. I think you're the first person on the entire Internet to address this issue.

I was mucking around with the WebPart.Connections property for ages and couldn't get the filtering to work right. The key thing that you figured out was the RowToParametersTransformer. That's the part that I haven't seen documented anywhere prior to your post.

Thanks again.

Joe said...

@Matt: Glad I could help. I was quite surprised that there were no resources documenting this.

JTA said...

Joe,

This post has been a great help!

Out of interest...what were the Power Shell commands you used to investigate the connection points?

Thanks!

Joe said...

@JTA

Here is what I ran in PowerShell to get that output. It should work against any team site (set the url in the Get-SPWeb command) when you add the web parts to the default.aspx page. Note that I use a simple index to grab the web part of interest, in this particular case I ca see that the webpart named 'Delme' is the fourth one and is the one I am interested in.

PS C:\Users\Administrator> Add-PSSnapin microsoft.sharepoint.powershell
PS C:\Users\Administrator> $web = Get-SPWeb http://mywebsite.local
PS C:\Users\Administrator> $page = $web.Files[0]
PS C:\Users\Administrator> $scope = [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared
PS C:\Users\Administrator> $wpm = $page.GetLimitedWebPartManager($scope)
PS C:\Users\Administrator> $wpm.WebParts | ft Title

Title
-----
Manuals
Directly to...
FAQ Subjects
Delme

PS C:\Users\Administrator> $wp = $wpm.WebParts[3]
PS C:\Users\Administrator> $wpm.GetConsumerConnectionPoints($wp)


AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartParameters
ID : DFWP Parameter Consumer ID
DisplayName : Parameters

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartParameters
ID : DFWP Filter Consumer ID
DisplayName : Filter Values



PS C:\Users\Administrator> $wpm.GetProviderConnectionPoints($wp)


AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartTable
ID : TableProvider
DisplayName : Table

AllowsMultipleConnections : True
ControlType : Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
InterfaceType : System.Web.UI.WebControls.WebParts.IWebPartRow
ID : DFWP Row Provider ID
DisplayName : Row of Data

Chandrashekar Kollipara said...

Thanks Joe
Chandrashekar Kollipara

Mirclin Gohan said...

Hi Joe,

This blog was very useful to me. But I am facing a problem when I am trying to connect the webparts. The error say that "The Provider and the Transformer do not use the same connection interface".........
Can I know what is the problem.?

Thanks in advance