Web Part Connections in WSS 3.0

Josh Holmes recently asked me to present a couple of small demos for an ArcReady event in Memphis.  While looking through my material, I discovered a couple of web parts that I had written a while ago.  I wrote these web parts in order to investigate how to send data from one part to another.  They illustrate how easy it is to set up Web Part Connections in WSS 3.0, so I thought I'd share them here.

In this post, I'll show how to send data from one web part to another.  Later posts will demonstrate how one web part can provide data to multiple web parts, how one web part can consume data from multiple parts, and how a web part can be a provider and a consumer at the same time.  As you will see, with WSS 3.0, this is very easy to do.

Now, programs need a purpose, and web parts are no different.  These web parts provide different types of information about the Towers of Hanoi puzzle.  In this post, I'll use two web parts.  One web part contains a drop-down list that can be used to specify the number of disks used for the puzzle.  The second web part contains a list box that enumerates the steps needed to solve the puzzle with the number of disks specified in the first web part.  For this to work properly, the second web part must obtain data from the first web part.  So, how does this work?

First, data is exchanged via interfaces. In this case, the interface is called IDisks:

IDisks.vb

Public Interface IDisks

    ReadOnly Property NumberOfDisks() As Integer

End Interface

This interface will be used to pass the selected number of disks from one web part to the other.

The first web part will allow the user to select the number of disks that should be used for the puzzle.  It is called HanoiDisks:

HanoiDisks.vb 

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

Public Class HanoiDisks
Inherits WebPart
Implements IDisks

    Protected diskList As DropDownList = Nothing

    Protected Overrides Sub CreateChildControls()
MyBase.CreateChildControls()

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

        Try
diskList = New DropDownList

            With diskList
.AutoPostBack = True

                .Items.Clear()

                .Items.Add(New ListItem("Three", 3))
.Items.Add(New ListItem("Four", 4))
.Items.Add(New ListItem("Five", 5))
.Items.Add(New ListItem("Six", 6))
.Items.Add(New ListItem("Seven", 7))
.Items.Add(New ListItem("Eight", 8))

                .SelectedIndex = 0
End With

            t = New Table
tr = New TableRow

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

            td = New TableCell
td.Controls.Add(diskList)
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("Number of Disks")> _
Public Function GetDiskInterface() As IDisks
Return Me
End Function

    Public ReadOnly Property NumberOfDisks() As Integer Implements IDisks.NumberOfDisks
Get
Return diskList.SelectedValue
End Get
End Property

End Class

Notice that HanoiDisks inherits from WebPart.  This is the standard .NET 2.0 WebPart class, and it performs most of the work for us.  All we have to do is override CreateChildControls to add our UI.  The HanoiDisks class also implements the IDisks interface that was mentioned earlier.  It returns the value of the selected item in the dropdown list in our UI.

The GetDiskInterface method is the key to the web part communication.  It returns the HanoiDisks class as an instance of the IDisks interface.  It uses the ConnectionProvider attribute to inform WSS that this method provides data for another web part.  WSS does the rest.

Now let's look at the HanoiSteps web part, which lists the steps needed to solve the puzzle.

HanoiSteps.vb 

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

Public Class HanoiSteps
Inherits WebPart

    Protected _diskInterface As IDisks = Nothing
Protected _disks As Integer = 0
Protected _stepList As ListBox = Nothing
Protected _headerMessage As Literal = Nothing

    Protected Overrides Sub CreateChildControls()
MyBase.CreateChildControls()

        Try
_headerMessage = New Literal
Me.Controls.Add(_headerMessage)

            Dim br As New HtmlGenericControl("br")
Me.Controls.Add(br)

            _stepList = New ListBox

            With _stepList
.SelectionMode = ListSelectionMode.Single
.Rows = 7
End With

            Me.Controls.Add(_stepList)
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 _diskInterface IsNot Nothing Then
_disks = _diskInterface.NumberOfDisks
End If

    End Sub

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

        If _headerMessage IsNot Nothing Then
_headerMessage.Text = "Here are the steps for " & _disks & " disks:"
End If

        If _stepList IsNot Nothing AndAlso _disks > 1 Then
FillListWithSteps(_stepList, _disks)
End If

        MyBase.RenderContents(writer)

    End Sub

    <ConnectionConsumer("Number of Disks")> _
Public Sub AcceptDiskInterface(ByVal diskInterface As IDisks)
_diskInterface = diskInterface
End Sub

    Private Sub FillListWithSteps(ByVal list As ListBox, ByVal disks As Integer)

        list.Items.Clear()

        Towers(list, disks, 1, 3, 2)

    End Sub

    Private Sub Towers(ByVal list As ListBox, ByVal disks As Integer, ByVal fromPole As Integer, ByVal toPole As Integer, ByVal usingPole As Integer)

        If disks = 1 Then
list.Items.Add(String.Format("Move a disk from pole {0} to pole {1}", fromPole, toPole))
Else
Towers(list, disks - 1, fromPole, usingPole, toPole)
Towers(list, 1, fromPole, toPole, usingPole)
Towers(list, disks - 1, usingPole, toPole, fromPole)
End If

    End Sub

End Class

Most of this code is needed just to make this web part work.  That is, to create UI controls that list the steps needed to solve the puzzle.  The AcceptDiskInterface method is the key to accepting data from the first web part.  It accepts an instance of the IDisks interface and stores it for later use.  The ConnectionConsumer attribute is used to tell WSS that this method will consume data.  The OnPreRender method checks to see if the interface has been received.  If so, it stores the number of disks in a variable.  The RenderContents method uses this variable to determine how many disks have been specified, and what steps are needed to solve the puzzle.

All that's left is to edit one of the web parts, select the connections menu, and link the parts together.

This completes the first post.  We've seen how one web part can pass data to another.  Look for another post soon, where I'll expand on this topic.

Note that, while you are free to use this code as you wish, it comes with no guarantees. I wrote it simply for illustration purposes, not for actual use.