Wednesday, June 30, 2010

Strange SPANs in SharePoint 2010 Team Site

My colleague and I came across something quite strange yesterday and I think it's worth sharing. We were inspecting the HTML source of a SharePoint 2010 Out-Of-Box Team Site and found this at the end of the file:
</body>
</html>
<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
For those who are not HMTL savvy, this is not valid HTML.

We are trying to look into what the cause of this could be, but so far no luck. If anyone has any idea as to what these spans are for or where they come from, I am very interested!

Update: There is now a fix for this from Wictor WilĂ©n, see his blog

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);