Web Part Connections in WSS 3.0 (Part 4)

In part 1 of this series, I showed how to make one web part accept data from another.  In part 2, I showed that this can be extended so that one web part can provide data for many.  In part 3, which I thought was going to be the last part, I showed how a single web part can accept data from multiple web parts.  Now, in the fourth part of my three-part series, I'm going to show how data can be passed from one web part to another in a sort of chain.

I have three web parts; NumberPicker, NumberDoubler, and NumberTripler.  I want the data to flow like this:

NumberPicker --> NumberDoubler --> NumberTripler

The user will use NumberPicker to select a number from one to five.  This number is passed to NumberDoubler, which doubles the number and displays it.  This doubled number is then passed to NumberTripler, which triples the number and displays the final result.  The important point here is that data is received from one part and provided to the next, and specifically that the data provided depends upon the data recieved. Here's the code that I used:

INumber
Public Interface INumber

    ReadOnly Property Number() As Integer

End Interface

NumberPicker
Imports System
Imports System.ComponentModel
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Public Class NumberPicker
    Inherits WebPart
    Implements INumber

    Private _NumberList As DropDownList

    Public Sub New()

        Me.ExportMode = WebPartExportMode.All

    End Sub

    Protected Overrides Sub CreateChildControls()
        MyBase.CreateChildControls()

        Dim t As Table = Nothing
        Dim tr As TableRow = Nothing
        Dim td As TableCell = Nothing

        Try
            _NumberList = New DropDownList

            With _NumberList
                .AutoPostBack = True

                .Items.Clear()

                .Items.Add(New ListItem("One", 1))
                .Items.Add(New ListItem("Two", 2))
                .Items.Add(New ListItem("Three", 3))
                .Items.Add(New ListItem("Four", 4))
                .Items.Add(New ListItem("Five", 5))

                .SelectedIndex = 0
            End With

            t = New Table
            tr = New TableRow

            td = New TableCell
            td.Text = "Number: "
            tr.Controls.Add(td)

            td = New TableCell
            td.Controls.Add(_NumberList)
            tr.Controls.Add(td)

            t.Controls.Add(tr)

            Me.Controls.Add(t)
        Catch ex As Exception
            Me.Controls.Clear()

            Dim msg As New Literal()
            msg.Text = ex.Message
            Me.Controls.Add(msg)
        End Try

    End Sub

    <ConnectionProvider("Selected Number")> _
    Public Function GetNumberInterface() As INumber
        Return Me
    End Function

    Public ReadOnly Property Number() As Integer Implements INumber.Number
        Get
            Return _NumberList.SelectedValue
        End Get
    End Property

End Class

NumberDoubler
Imports System
Imports System.ComponentModel
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Public Class NumberDoubler
    Inherits WebPart
    Implements INumber

    Protected _numberInterface As INumber = Nothing
    Protected _number As Integer = 0
    Protected _result As Integer = 0

    Protected _headerMessage As Literal = Nothing

    Public Sub New()

        Me.ExportMode = WebPartExportMode.All

    End Sub

    Protected Overrides Sub CreateChildControls()
        MyBase.CreateChildControls()

        Try
            _headerMessage = New Literal
            Me.Controls.Add(_headerMessage)
        Catch ex As Exception
            Me.Controls.Clear()

            Dim msg As New Literal()
            msg.Text = ex.Message
            Me.Controls.Add(msg)
        End Try

    End Sub

    Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
        MyBase.OnPreRender(e)

        If _numberInterface IsNot Nothing Then
            _number = _numberInterface.Number
            _result = 2 * _number
        End If

    End Sub

    Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter)

        If _headerMessage IsNot Nothing AndAlso _number > 0 Then
            _headerMessage.Text = _result
        End If

        MyBase.RenderContents(writer)

    End Sub

    <ConnectionConsumer("Selected Number", "1")> _
    Public Sub AcceptNumberInterface(ByVal numberInterface As INumber)
        _numberInterface = numberInterface
    End Sub

    <ConnectionProvider("Doubled Number", "2")> _
    Public Function GetNumberInterface() As INumber
        Return Me
    End Function

    Public ReadOnly Property Number() As Integer Implements INumber.Number
        Get
            Return _result
        End Get
    End Property
End Class

NumberTripler
Imports System
Imports System.ComponentModel
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Public Class NumberTripler
    Inherits WebPart

    Protected _numberInterface As INumber = Nothing
    Protected _number As Integer = 0
    Protected _result As Integer = 0

    Protected _headerMessage As Literal = Nothing

    Public Sub New()

        Me.ExportMode = WebPartExportMode.All

    End Sub

    Protected Overrides Sub CreateChildControls()
        MyBase.CreateChildControls()

        Try
            _headerMessage = New Literal
            Me.Controls.Add(_headerMessage)
        Catch ex As Exception
            Me.Controls.Clear()

            Dim msg As New Literal()
            msg.Text = ex.Message
            Me.Controls.Add(msg)
        End Try

    End Sub

    Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
        MyBase.OnPreRender(e)

        If _numberInterface IsNot Nothing Then
            _number = _numberInterface.Number
            _result = 3 * _number
        End If

    End Sub

    Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter)

        If _headerMessage IsNot Nothing AndAlso _number > 0 Then
            _headerMessage.Text = _result
        End If

        MyBase.RenderContents(writer)

    End Sub

    <ConnectionConsumer("Doubled Number", "3")> _
    Public Sub AcceptNumberInterface(ByVal numberInterface As INumber)
        _numberInterface = numberInterface
    End Sub

End Class

The real trick here is that the numbers are calculated during the OnPreRender event.  We have to wait long enough to ensure that the NumberPicker web part has created its internal controls, which is where the original data resides.  We also need to act early enough to allow the data to be passed along from part to part.  If you are not careful, one of the web parts may be rendered before the data has been passed to it.

I hope this helps.