Share via


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