Practical .NET

Passing Selected Data Between WebParts in SharePoint

The IWebPartParameters interface provides a flexible way for one WebPart to select the data it wants from another WebPart. And, since implementing that interface doesn't require much code, you should consider using it on all your WebParts.

In my last column, I discussed how you can link your WebParts to the WebParts created by SharePoint. By using the default IWebPartRow interface built into SharePoint, your WebPart can react to the choice a user makes in a List displayed in another WebPart.

But once you start building WebParts that pull data through these default interfaces, you should also consider building WebParts that push data through those default interfaces. By doing that, you leverage these built-in interfaces to provide an architecture that lets users connect your WebParts in a variety of ways. In fact, implementing these default interfaces is sufficiently easy that you should consider putting them on all your WebParts.

In this column, I want to look at the IWebPartParameters default interface. In many ways, this is the most interesting of the default interfaces, because it allows the connecting WebPart to tell the providing WebPart exactly what, out of the data being provided, the connecting WebPart wants. A WebPart that pulls data through IWebPartParameters can work with a variety of other WebParts, because all that's required is that those WebParts share a common piece of data. For instance, a WebPart that only needed a CustomerID to figure out what to display could be connected to any WebPart with a CustomerID in it.

For this case study on implementing the IWebPartParameters interface, I'm going to create a WebPart that pulls data from a relational database and loads it into a DataSet that's displayed in a GridView.

Setting Up the WebPart
To support this, I first add a DataSet to a Visual WebPart project and drag a table into it (for this example, I used the Orders table from the Northwind database). Listing 1 shows the code in the "code-only" component of my Visual WebPart that supports this WebPart. In the code, I've created a property to provide access to the DataSet. In the WebPart's PreRender event, I load the OrdersTable into the DataSet. Also included is the connection method that tells SharePoint that this WebPart can provide data to another WebPart using the IWebPartParameters interface (for more detail on getting data from other WebParts, see my previous article). There's a fair amount of code here, but it's code you'd require for any WebPart that retrieves and displays data.

In the UserControl, I drag a GridView onto the UserControl, set the GridView's AutoGenerateSelectButton property to True, and the GridView's DataKeyNames property to OrderID. This code in the UserControl's Page PreRender event loads the DataTable into the GridView:

Me.GridView1.DataSource = CType(Me.Parent, SampleParameters).nwTB
Me.GridView1.DataBind()

This code in the GridView's SelectedIndexChanged event causes the OrderID for the current row to be passed to a method in the code-only portion of the WebPart:

CType(Me.Parent, SampleParameters).MarkRow(Me.GridView1.SelectedDataKey)

And here's that property that must be added to the code-only version of your WebPart:

Dim RowKey As String = String.Empty
Public Sub MarkRow(key As DataKey)
  RowKey = key.Value
End Sub

The next step is implementing the IWebPartParameters interface so that other WebParts can use the data that this WebPart has retrieved.

Implementing IWebPartParameters
Having your WebPart implement IWebPartParameters causes Visual Studio to add two methods (GetParametersData, SetConsumerSchema) and one property (Schema) to your WebPart. While it's your responsibility to add code to those methods, it's code that doesn't change much from one WebPart to another.

A WebPart that connects to your WebPart will read the Schema property to determine what data's available. To support that, the Schema property must return a collection of PropertyDescriptors with each PropertyDescriptor describing one of the data items available from your WebPart. If the data is stored in a DataTable in DataSet, as I've done, this is an easy property to implement:

Public ReadOnly Property Schema _ 
     As PropertyDescriptorCollection …        
  Get
    Return TypeDescriptor.GetProperties(nwTB.DefaultView(0))
  End Get
End Property

This is an example of the boilerplate code that doesn't change much from WebPart to another: it would be identical (except for the name of the property managing the DataSet) in every WebPart where you implement IWebPartParameters. Or, as a matter of fact, any of the other default interfaces, because they all have a Schema property.

The GetParametersData method is equally easy to implement: The connecting WebPart will call this method in order pass a reference to one of its methods to your WebPart (eventually, your WebPart will call that method to pass data to the connecting WebPart). This method is also easy to implement because you just need to store a reference to the method (passed as a ParametersCallback object) to use later:

Private pcb As ParametersCallback
Public Sub GetParametersData( _
   callback As WebParts.ParametersCallback) …
  pcb = callback
End Sub

And this really is boilerplate code: It doesn't change from one WebPart to another.

The connecting WebPart is also expected to call your WebPart's SetConsumerSchema method, passing a list of PropertyDescriptors to your WebPart. The collection contains a PropertyDescriptor object for each column the Connecting WebPart wants you to send (presumably, the Connecting WebPart has selected these PropertyDescriptor objects from the collection you provided through your Schema property). Again, all you have to do is store the collection (the user's choices) to use later:

Private selectedColumns As PropertyDescriptor
Public Sub SetConsumerSchema( _
       schema As PropertyDescriptorCollection) …
  selectedColumns = schema
End Sub

You also need to include a method that flags SharePoint that you have a connectable WebPart. This method does that while also providing some text to display in the SharePoint UI, giving the connection a unique name to SharePoint, and allowing multiple WebParts to accept data through this connection:

<ConnectionProvider("Selected Order Data", "ConnectionPointIWebpartParameters", _
            AllowsMultipleConnections:=True)> _
Public Function MyProviderMethodForParameters() As IWebPartParameters
  Return Me
End Function

The UI-related text ("Selected Order Data" in my example) and, perhaps, the name of the method, is all that changes from one WebPart to another.

Returning Data
With that work done, you can now return data to the connecting WebPart. The best place to do that is in the PreRender event, after the code in that method that retrieves the DataSet. In this event a little defensive programming is a good idea: You want to check that your part's connected, that the connecting WebPart has passed a set of columns, and that the user has selected a row in your WebPart:

If pcb IsNot Nothing AndAlso _
   SelectedColumns IsNot Nothing AndAlso _
   RowKey <> String.Empty Then

The next step is to find the row selected in the DataSet that corresponds to the row selected by the user in the GridView, and extract the value from the column the user selected:

Dim selectedRows() As DataRow
selectedRows = nwTB.Select("OrderId=" & RowKey)
Dim value = selectedRows(0) (selectedColumn.Name)

The final step is to pass the data to the method in the connecting WebPart that was handed to your WebPart. That method expects to receive a Dictionary, so for each selected column, you check to make sure that there's a corresponding column in the DataTable; if there is, add an entry to a Dictionary with a key set to the name of the property and the value from the corresponding column in the selected row. When the Dictionary's built, you just pass the Dictionary to the connecting part's method that you retrieved in the GetParametersData method:

Dim dict As New Dictionary(Of String, Object)
For Each pd As PropertyDescriptor In selectedColumns
  If nwTB.Columns.Contains(pd.Name) Then
    Dim value = selectedRows(0)(pd.Name)
    dict.Add(pd.Name, value)
  End If
Next
pcb(dict)

Ignoring the code used to retrieve the data (which you'd have to write even if you weren't implementing the IWebPartParameters interface), there's just over a dozen lines of code required to implement IWebPartParameters -- and much of it is identical from one WebPart to another.

Also, as I'll demonstrate in an upcoming article, much of the code used to implement IWebPartParameters can be used to implement the other default interfaces (IWebPartTable and IWebPartRow, for instance), which means that implementing additional default interfaces is actually easier than IWebPartParameters. So with a little work, you can make your WebParts even more valuable to your users by allowing users to connect them in useful ways.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube