Creating a Connectable Web Part that Supports Multiple Interfaces
There are times when a connectable Web Part needs to implement more than one of the connection interfaces. The most common scenario for creating such a Web Part is when the Web Part needs to be both a provider and consumer. For example, a Web Part that displays a list of data may want to allow filtering the list as well as providing a row from it. The best way to achieve this is to create separate classes that implement the different interfaces—in this example, the IRowProvider and IFilterConsumer interfaces. Within the RegisterInterface method of the main Web Part class, the classes that implement the interfaces can be instantiated and registered separately. The following code sample illustrates this approach.
//*********************************************************************************************
//
// DESCRIPTION: This is a simple server-side Web Part which registers both IRowProvider
// and IFilterConsumer interfaces. This is achieved by creating separate classes that implement
// the IRowProvider and IFilterConsumer interfaces. These objects are then passed in
// during the RegisterInterface call within the main Web Part class ServerSideRowProvFilterCons.
//
// This Web Part displays a table of data. When a row is selected, this row is passed
// to the other connected parts via the IRowProvider interface. This part can also be
// filtered via the IFilterConsumer interface.
//
//*********************************************************************************************
// Common .NET required namespaces
using System;
using System.ComponentModel;
using System.Web.UI;
// WebPart required namespaces
using Microsoft.SharePoint.WebPartPages;
using System.Xml.Serialization;
using System.Web.UI.WebControls;
// DataGrid and user interface namespaces Functionality
using System.Data;
using System.Drawing;
// Code Access Security namespaces
using System.Security;
using Microsoft.SharePoint.Utilities;
//Step #1: Reference the Communication namespace.
using Microsoft.SharePoint.WebPartPages.Communication;
namespace ConnectionCodeSamples
{
//----------------------------- IFilterConsumer Interface Class-------------------------------
// The IFilterConsumer class implements the events and event handlers for the IFilterConsumer interface.
// It also provides methods for the main Web Part class to fire these connection
// events and retrieve values from the event handlers. This is allows the main Web Part class
// to implement more than one connection.
// interface.
public class FilterConsumerInterface:IFilterConsumer
{
//Declare the IFilterConsumer Event
public event FilterConsumerInitEventHandler FilterConsumerInit;
//Tracking Variables
private bool _sentFilterConsumerInit = false;
private bool _receivedSetFilter = false;
private bool _receivedClearFilter = false;
private bool _receivedNoFilter = false;
private string _rowFilterExpression = string.Empty;
//Filter Field Information
private string FieldLabel = "FilterField";
private string ValueLabel = "FilterValue";
// Implement the FireFilterConsumerInit method.
// During the PartCommunicationInit phase, the main Web Part class will need to
// fire the FilterConsumerInit event; however, because it doesn't implement the
// IFilterConsumer interface, it can't do this directly. The main Web Part class
// can call into the FireFilterConsumerInit method to fire the FilterConsumerInit event.
// <param name="fieldName">Array of internal field names</param>
// <param name="fieldDisplayName">Array of display names</param>
public void FireFilterConsumerInit(string[] fieldList, string[] fieldDisplayList)
{
//If there is a listener, send init event
if (FilterConsumerInit != null)
{
//Create the args for the FilterConsumerInit event
FilterConsumerInitEventArgs filterConsumerInitArgs = new FilterConsumerInitEventArgs();
//Set the Field Names
filterConsumerInitArgs.FieldList = fieldList;
filterConsumerInitArgs.FieldDisplayList = fieldDisplayList;
//Fire the FilterConsumerInit event.
FilterConsumerInit(this, filterConsumerInitArgs);
//Set Flag
_sentFilterConsumerInit = true;
}
}
// Implement the SetFilter event handler.
// The connected provider part will call this method during its PartCommunicationMain phase
// to set the filter on the consumer Web Part.
// <param name="sender">Provider Web Part</param>
// <param name="SetFilterArgs">The args passed by the Provider</param>
public void SetFilter(object sender, SetFilterEventArgs setFilterEventArgs)
{
//Convert FilterExpression to the DataTable RowFilter syntax
if(setFilterEventArgs.FilterExpression != null)
{
//Need to parse the filter information
string[] values = setFilterEventArgs.FilterExpression.Split(new Char[] {'&'});
if (values.Length > 1)
{
int j = 1; //counts the number of Field/Value pairs
string filterField = string.Empty;
string filterValue = string.Empty;
string filterAnd = " AND ";
_rowFilterExpression = string.Empty;
for(int i=0; i < values.Length; i++)
{
//Clear out values
filterField = string.Empty;
filterValue = string.Empty;
//Create label portion of name value pairs
string currField, currValue;
currField = FieldLabel + j + "=";
currValue = ValueLabel + j + "=";
j++;
//Extract just the Field Name by replacing the rest of the string with string.Empty
filterField = filterField + values[i].Replace(currField, string.Empty);
//Move to next item in the array which is the value component
i++;
//Extract just the Value by replacing the rest of the string with string.Empty
filterValue = filterValue + values[i].Replace(currValue, string.Empty);
//Contruct Row Filter Expression
_rowFilterExpression += filterField + "=" + "'" + filterValue + "'" + filterAnd;
}
//Trim Off Trailing 'And'
if (_rowFilterExpression.Length != 0)
_rowFilterExpression = _rowFilterExpression.Substring(0,_rowFilterExpression.Length - filterAnd.Length);
//Set Flag
_receivedSetFilter = true;
}
}
}
// Implement ClearFilter event handler.
// The connected provider part will call this method during its PartCommunicationMain phase
// to remove the filter on the consumer Web Part.
// <param name="sender">Provider Web Part</param>
// <param name="eventArgs">The Event Argumentsr</param>
public void ClearFilter(object sender, EventArgs eventArgs)
{
//Clear the filter on the DataTable
_rowFilterExpression = string.Empty;
//Set Flag
_receivedClearFilter = true;
}
// Implement NoFilter event handler.
// The connected provider part will call this method during its PartCommunicationMain phase
// to indicate there is no change in the filter. This allows the consumer part to
// display its cached data instead of recalcuating the filter expression or potentially hitting a database again.
// <param name="sender">Provider Web Part</param>
// <param name="eventArgs">The Event Argumentsr</param>
public void NoFilter(object sender, EventArgs eventArgs)
{
//Set Flag
_receivedNoFilter = true;
}
//----------- Accessor Methods -----------------
//The main Web Part object can use these methods to determine if specific events
//have occurred and retrieve values.
public bool SentFilterConsumerInit
{
get {return _sentFilterConsumerInit;}
}
public bool ReceivedSetFilter
{
get {return _receivedSetFilter;}
}
public bool ReceivedClearFilter
{
get {return _receivedClearFilter;}
}
public bool ReceivedNoFilter
{
get {return _receivedNoFilter;}
}
public string GetRowFilterExpression
{
get {return _rowFilterExpression;}
}
}
//----------------------------- IRowProvider Interface Class-------------------------------
// The RowProviderInterface class implements the events for the IRowProvider interface.
// It also provides methods for the main Web Part class to fire these connection
// events. This is allows the main Web Part class to implement more than one connection
// interface.
public class RowProviderInterface:IRowProvider
{
//Declare the IRowProvider events.
public event RowProviderInitEventHandler RowProviderInit;
public event RowReadyEventHandler RowReady;
//Tracking Variables
private bool _sentRow = false;
private bool _sentRowProviderInit = false;
// Implement the FireRowProviderInit method.
// During the PartCommunicationInit phase, the main Web Part class will need to
// fire the RowProviderInit event; however, because it doesn't implement the
// IRowProvider interface, it can't do this directly. The main Web Part class
// can call into the FireRowProviderInit method to fire the RowProviderInit event.
// <param name="fieldName">Array of internal field names</param>
// <param name="fieldDisplayName">Array of display names</param>
public void FireRowProviderInit(string[] fieldList, string[] fieldDisplayList)
{
//If there is a listener, send init event
if (RowProviderInit != null)
{
RowProviderInitEventArgs rowProviderInitEventArgs = new RowProviderInitEventArgs();
//Load Arguments
rowProviderInitEventArgs.FieldList = fieldList;
rowProviderInitEventArgs.FieldDisplayList = fieldDisplayList;
//Fire the RowProviderInit event
RowProviderInit(this, rowProviderInitEventArgs);
//Set Flag
_sentRowProviderInit = true;
}
}
// Implement the FireRowReady method.
// During the PartCommunicationMain phase, the main Web Part class will need to
// fire the RowReady event; however, because it doesn't implement the
// IRowProvider interface, it can't do this directly. The main Web Part class
// can call into the FireRowReady method to fire the RowReady event.
// <param name="rowValue">row value</param>
// <param name="selectionStatus">selection status of the row</param>
public void FireRowReady(DataRow[] rowValues, string selectionStatus)
{
if (RowReady != null)
{
//Need to create the args for the RowReady event
RowReadyEventArgs rowReadyEventArgs = new RowReadyEventArgs();
//Set Values
rowReadyEventArgs.Rows = rowValues;
rowReadyEventArgs.SelectionStatus = selectionStatus;
//Fire the RowReady event.
RowReady(this, rowReadyEventArgs);
//Set Flag
_sentRow = true;
}
}
//----------- Accessor Methods -----------------
//The main Web Part object can use these methods to determine if specific events
//have occurred.
public bool SentRow
{
get {return _sentRow;}
}
public bool SentRowProviderInit
{
get {return _sentRowProviderInit;}
}
}
//----------------------------- Main Web Part Class-------------------------------
// Notice that no interface is implemented by the main class.
public class ServerSideRowProvFilterCons : WebPart
{
// Keep track of the connection state.
private bool _connectedOverRowProvider = false;
private bool _connectedOverFilterConsumer = false;
private string _connectedWebPartTitle = string.Empty;
private string _registrationErrorMsg = "An error has occurred trying to register your connection interfaces.";
private bool _registrationErrorOccurred = false;
private string _notConnectedMsg = "NOT CONNECTED. To use this Web Part connect it to another compatible part.";
// Declare variables for Web Part user interface.
private string _connectedWebPartLabel = "Connected to Web Part";
private DataGrid _dataGrid = new DataGrid();
private TextBox _cachedRowFilter;
//Row Information
private string[] _rowFieldNames;
private string[] _rowFieldDisplayNames;
// Create Interface Objects.
internal RowProviderInterface RowProviderInterface = new RowProviderInterface();
internal FilterConsumerInterface FilterConsumerInterface = new FilterConsumerInterface();
// Override the EnsureInterfaces method and call the RegisterInterface method.
// EnsureInterfaces() is called by the Web Part infrastructure during the ASP.NET PreRender event
// and allows the part to register all of its connection interfaces.
public override void EnsureInterfaces()
{
try
{
// Register the IRowProvider Interface and pass in the object which implements it
RegisterInterface("MyRowProviderInterface", //InterfaceName
InterfaceTypes.IRowProvider, //InterfaceType
WebPart.UnlimitedConnections, //MaxConnections
ConnectionRunAt.Server, //RunAtOptions
RowProviderInterface, //InterfaceObject
"", //InterfaceClientReference
"Provide Row To", //MenuLabel
"Provides a row to a consumer Web Part.", //Description
true); //allowCrossPageConnection
// Register the IFilterConsumer Interface and pass in the object which implements it
RegisterInterface("MyFilterConsumerInterface", //InterfaceName
InterfaceTypes.IFilterConsumer, //InterfaceType
WebPart.LimitOneConnection, //MaxConnections
ConnectionRunAt.Server, //RunAtOptions
FilterConsumerInterface, //InterfaceObject
"", //InterfaceClientReference
"Consume Filter From", //MenuLabel
"Consumes a filter from a provider Web Part.", //Description
true); //allowCrossPageConnection
}
catch(SecurityException se)
{
_registrationErrorOccurred = true;
}
}
// Override the CanRunAt method.
// CanRunAt() is called by the Web Part infrastructure during the ASP.NET PreRender event
// to determine where the Web Part can run based on its current configuration.
public override ConnectionRunAt CanRunAt()
{
// This Web Part can run on the server.
return ConnectionRunAt.Server;
}
// Override the PartCommunicationConnect method.
// PartCommunicationConnect() is called by the Web Part infrastructure to notify the Web Part that it
// is connected during the ASP.NET PreRender event. Relevant information is passed to the part such as
// the interface it is connected over, the Web Part it is being conected to, and where the part will be running,
// either client or server side.
// <param name="interfaceName">Friendly name of the interface that is being connected</param>
// <param name="connectedPart">Reference to the other Web Part that is being connected to</param>
// <param name="connectedInterfaceName">Friendly name of the interface on the other Web Part</param>
// <param name="runAt">Where the interface should execute</param>
public override void PartCommunicationConnect(
string interfaceName,
WebPart connectedPart,
string connectedInterfaceName,
ConnectionRunAt runAt)
{
//Keep track of interface connection
if (interfaceName == "MyRowProviderInterface")
{
//Keep track of the Connection
_connectedOverRowProvider = true;
_connectedWebPartTitle = SPEncode.HtmlEncode(connectedPart.Title);
}
//Keep track of interface connection
if (interfaceName == "MyFilterConsumerInterface")
{
//Keep track of the Connection
_connectedOverFilterConsumer = true;
_connectedWebPartTitle = SPEncode.HtmlEncode(connectedPart.Title);
}
}
// Override the PartCommunicationInit method.
// PartCommunicationInit() is called by the Web Part infrastructure during the ASP.NET PreRender
// phase to allow the part to pass initialization information to the other connected parts.
// It is important to always pass initialization information. Some parts
// may not behave properly if this initialization information is not received.
public override void PartCommunicationInit()
{
// Ensure that all of the Web Part's controls are created.
EnsureChildControls();
// Check if connected.
if(_connectedOverRowProvider)
{
//Fire the RowProviderInit event
RowProviderInterface.FireRowProviderInit(_rowFieldNames, _rowFieldDisplayNames);
}
// Check if connected.
if(_connectedOverFilterConsumer)
{
//Fire the RowProviderInit event
FilterConsumerInterface.FireFilterConsumerInit(_rowFieldNames, _rowFieldDisplayNames);
}
}
// Implement the PartCommunicationMain() method.
// PartCommunicationMain() is called by the Web Part infrastructure on the client during the ASP.NET PreRender
// phase to allow the part to pass its primary data to the other connected parts.
// It is important to always fire the RowReady event. Some parts
// may not behave properly if they are left waiting for this information.
public override void PartCommunicationMain()
{
// Check if connected.
if(_connectedOverRowProvider)
{
//Declare Variables
string selectionStatus = string.Empty;
DataRow[] dr = new DataRow[1];
//If a row is selected then send the row
if ( _dataGrid.SelectedIndex > -1)
{
//Generate array of DataRow(s) from the DataGrid DataSource
dr[0] = ((DataTable)_dataGrid.DataSource).Rows[_dataGrid.SelectedIndex];
//Set selection status
selectionStatus = "Standard";
}
else
{
//The user hasn't selected a row so send null
dr[0] = null;
//Set selection status
selectionStatus = "None";
}
//Fire the RowReady event
RowProviderInterface.FireRowReady(dr,selectionStatus);
}
}
// Override the GetInitEventArgs method.
// GetInitEventArgs() is called by the Web Part infrastructure during the ASP.NET PreRender event to gather the
// necessary information it needs to build the transformer dialog. The transformer dialog
// is needed when connecting different interfaces such as IRowProvider
// to ICellConsumer. The transformer dialog allows the user to map the fields between the
// interfaces. The GetInitEventArgs()method only needs to be implemented for interfaces that
// can participate in a transformer which are the following:
// ICellConsumer, IRowProvider, IFilterConsumer, IParametersOutProvider, IParametersInConsumer.
// <param name="interfacename">Name of interface on which the Web Part infrastructure is requesting information</param>
// <returns>An InitEventArgs object</returns>
public override InitEventArgs GetInitEventArgs(string interfaceName)
{
// Ensure that all of the Web Part's controls are created.
//The _rowFieldNames and _rowFieldDisplayNames are set during EnsureChildControls()
EnsureChildControls();
//Check if this is my particular row interface
if (interfaceName == "MyRowProviderInterface")
{
//Need to create the args for the RowProviderInit event
RowProviderInitEventArgs rowProviderInitEventArgs = new RowProviderInitEventArgs();
//Set the Field Names
rowProviderInitEventArgs.FieldList = _rowFieldNames;
rowProviderInitEventArgs.FieldDisplayList = _rowFieldDisplayNames;
//return the InitArgs
return(rowProviderInitEventArgs);
}
else if(interfaceName == "MyFilterConsumerInterface")
{
//Create the args for the FilterConsumerInit event
FilterConsumerInitEventArgs filterConsumerInitArgs = new FilterConsumerInitEventArgs();
//Set the Field Names
filterConsumerInitArgs.FieldList = _rowFieldNames;
filterConsumerInitArgs.FieldDisplayList = _rowFieldDisplayNames;
//return the InitArgs
return(filterConsumerInitArgs);
}
else
{
return(null);
}
}
protected override void RenderWebPart(HtmlTextWriter output)
{
// Check for connection interface registration error.
if (_registrationErrorOccurred)
{
output.Write(_registrationErrorMsg);
return;
}
// Ensure that all of the Web Part's controls are created.
EnsureChildControls();
// Check if connected.
if(_connectedOverRowProvider || _connectedOverFilterConsumer)
{
// Line break.
output.RenderBeginTag(HtmlTextWriterTag.Br);
output.RenderEndTag();
// Set Filter
if (FilterConsumerInterface.ReceivedSetFilter ||
FilterConsumerInterface.ReceivedClearFilter)
{
int x = 1;
((DataTable)_dataGrid.DataSource).DefaultView.RowFilter = FilterConsumerInterface.GetRowFilterExpression;
_dataGrid.DataBind();
// Store RowFilterExpression for use by NoFilter event
_cachedRowFilter.Text = FilterConsumerInterface.GetRowFilterExpression;
}
else if (FilterConsumerInterface.ReceivedNoFilter)
{
//Retrieve Filter Expression from StateBag
((DataTable)_dataGrid.DataSource).DefaultView.RowFilter = _cachedRowFilter.Text;
_dataGrid.DataBind();
}
//Render the DataGrid control
_dataGrid.RenderControl(output);
//Hidden Row Filter TextBox stores _rowFilterExpression
_cachedRowFilter.RenderControl(output);
// Line break.
output.RenderBeginTag(HtmlTextWriterTag.Br);
output.RenderEndTag();
// Render connected Web Part title.
output.Write(_connectedWebPartLabel + ": ");
output.RenderBeginTag(HtmlTextWriterTag.I);
output.Write(_connectedWebPartTitle);
output.RenderEndTag();
}
else
{
// The Web Part isn't connected.
output.Write(_notConnectedMsg);
}
}
// Create Web Part user interface controls.
protected override void CreateChildControls()
{
//Create hidden textbox to store _rowFilterExpression
_cachedRowFilter = new TextBox();
_cachedRowFilter.ID = "CachedRowFilter";
_cachedRowFilter.Visible = false;
Controls.Add(_cachedRowFilter);
//Create the DataTable
DataTable dataTable = new DataTable();
// Add four column objects to the table.
DataColumn idColumn = new DataColumn();
idColumn.DataType = System.Type.GetType("System.Int32");
idColumn.ColumnName = "ID";
idColumn.Caption = "ID";
idColumn.AutoIncrement = true;
dataTable.Columns.Add(idColumn);
DataColumn Product = new DataColumn();
Product.DataType = System.Type.GetType("System.String");
Product.ColumnName = "Product";
Product.Caption = "Product";
dataTable.Columns.Add(Product);
DataColumn productCategory = new DataColumn();
productCategory.DataType = System.Type.GetType("System.String");
productCategory.ColumnName = "Category";
productCategory.Caption = "Category";
dataTable.Columns.Add(productCategory);
DataColumn Stock = new DataColumn();
Stock.DataType = System.Type.GetType("System.Int32");
Stock.ColumnName = "Stock";
Stock.Caption = "Stock";
dataTable.Columns.Add(Stock);
// Once a table has been created, use the NewRow to create a DataRow.
DataRow dataRow;
// Add first row to the collection.
dataRow = dataTable.NewRow();
dataRow["Product"] = "Aniseed Syrup";
dataRow["Category"] = "Condiments";
dataRow["Stock"] = 25;
dataTable.Rows.Add(dataRow);
//Add a second row
dataRow = dataTable.NewRow();
dataRow["Product"] = "Vegie-spread";
dataRow["Category"] = "Condiments";
dataRow["Stock"] = 10;
dataTable.Rows.Add(dataRow);
//And a third
dataRow = dataTable.NewRow();
dataRow["Product"] = "Outback Lager";
dataRow["Category"] = "Beverages";
dataRow["Stock"] = 30;
dataTable.Rows.Add(dataRow);
//And a fourth
dataRow = dataTable.NewRow();
dataRow["Product"] = "Boston Crab Meat";
dataRow["Category"] = "Seafood";
dataRow["Stock"] = 10;
dataTable.Rows.Add(dataRow);
//And a fifth
dataRow = dataTable.NewRow();
dataRow["Product"] = "Tofu";
dataRow["Category"] = "Produce";
dataRow["Stock"] = 41;
dataTable.Rows.Add(dataRow);
//Set the DataGrid's DataSource
_dataGrid.DataSource = dataTable;
_dataGrid.ID = "DataGrid";
//Create the Selection Column
ButtonColumn selectionColumn = new ButtonColumn();
selectionColumn.ButtonType = ButtonColumnType.PushButton;
selectionColumn.CommandName = "Select";
_dataGrid.Columns.Add(selectionColumn);
//Format Data Grid
_dataGrid.HeaderStyle.Font.Bold = true;
_dataGrid.HeaderStyle.ForeColor = Color.DarkBlue;
_dataGrid.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
_dataGrid.AlternatingItemStyle.BackColor = Color.Beige;
_dataGrid.SelectedItemStyle.BackColor = Color.Yellow;
_dataGrid.DataBind();
Controls.Add(_dataGrid);
//Set the DataTable FieldName information.
//This information will be passed to the Consumer by firing the RowProviderInit event.
int columnCount = dataTable.Columns.Count;
_rowFieldNames = new string[columnCount];
_rowFieldDisplayNames = new string[columnCount];
for(int i = 0; i < columnCount; i++)
{
_rowFieldNames[i] = dataTable.Columns[i].ColumnName;
_rowFieldDisplayNames[i] = dataTable.Columns[i].Caption;
}
}
}
}