Basic Instincts

Introducing ASP.NET Web Part Connections

Ted Pattison

Code download available at:BasicInstincts0602.exe(763 KB)

Contents

Creating Web Parts for ASP.NET 2.0 Apps
Designing Connectable Web Parts
Timing a Web Part Connection
Defining Static Web Part Connections
Named Connection Points
Establishing Connections Dynamically

When you begin to work with the Microsoft® .NET Framework 2.0 and ASP.NET, you discover that the new Web Parts infrastructure adds some very powerful functionality to the underlying platform. In the September 2005 issue of MSDN®Magazine, Fritz Onion and I have an article on programming Web Parts titled "ASP.NET 2.0: Personalize Your Portal with User Controls and Custom Web Parts". In this month's column, I am going to build on the information we presented in that article by discussing how Web Part connections works.

For this column, I assume that you already understand the basics of Web Parts such as how to work with the WebPartManager control, Web Part zones, editors, catalogs, and persistence properties. If you don't, I encourage you to read the aforementioned article before continuing with this one.

Creating Web Parts for ASP.NET 2.0 Apps

There are two ways in which you can create a Web Part. The first involves creating a custom Web Part class that inherits from the WebPart class defined in the System.Web.UI.WebControls.WebParts namespace. When using this approach, it often makes sense to package custom Web Part classes in an assembly DLL to provide more control over reuse, versioning, and Visual Studio® 2005 integration. If you are familiar with building custom controls with previous versions of ASP.NET, many of the same techniques apply to building custom Web Parts into a DLL assembly.

A second approach for creating ASP.NET 2.0 Web Parts involves the use of User Controls. While this approach doesn't yield the same levels of reuse and version control, it does allow you to create the user interface aspects of a Web Part using the Visual Studio forms designer. If you like to create applications by dragging and dropping controls for user input, validation, and data binding onto a design surface, this approach is for you. Of course, it's also a good approach to take if you've already spent time creating a User Control that you'd like to use as a Web Part.

When you create a User Control that's specifically designed to be a Web Part, it's recommended that you implement the IWebPart interface. This allows the code behind your Web Part to programmatically assign several of its own internal Web Part properties such as its Title and TitleIconUrl.

The code sample that accompanies this month's column uses a custom base class named WebPartBase that inherits from UserControl and implements IWebPart. The definition for this base class is deployed in a source file named WebPartBase.vb in the App_Code directory. Whenever you create a new Web Part using a User Control, all you have to do is change the base class in the codebehind file to take advantage of this technique:

Partial Class WebParts_Customers Inherits WebPartBase Sub New() Title = "NorthWind Customer List" TitleIconImageUrl = "~\img\Customers.gif" End Sub End Class

Designing Connectable Web Parts

Using Web Part connections, you can make it easier for your users to visualize the relationships that exist between items of data. For example, Web Part connections can model a master-detail scenario where one Web Part displaying a customer list is connected to another Web Part that displays the details of the currently selected customer. Figure 1 shows an example of what the user interface might look like with such a design.

Figure 1 Visualizing Relationships with Web Part Connections

Web Part connections can also be used to model one-to-many relationships. As an example, one Web Part that displays a customer list can be connected to another Web Part that displays all the orders for the currently selected customer.

Another scenario that's commonly modeled using Web Part connections is query-by-form. In such a scenario, one Web Part provides a user interface that allows the user to choose search or filter criteria for a query on data such as a database table. This Web Part is then connected to another Web Part that displays the results of the query. The Web Part connection is used to pass the filter criteria from one Web Part to the other before the query is run.

Web Part connections are based on the notion of providers and consumers. A provider Web Part supplies information to one or more consumer Web Parts through a programmatic interface. The information that is exchanged between a provider and a consumer could be a simple data item such as a number or a string or it could be something more exotic such as a reference to a complex array or to a collection of custom objects.

If you have programmed Web Parts for Windows® SharePoint® Services 2.0 (WSS), you might already be familiar with its model for connecting Web Parts. In WSS, Web Parts can only be connected using a set of predefined interface pairs. Examples of these interface pairs are ICellProvider and ICellConsumer as well as IRowProvider and IRowConsumer.

The Web Part connections model in ASP.NET 2.0 is easier and more flexible than the older model in WSS because you can use your own custom interfaces. That means you are not required to use an interface definition created by someone at Microsoft. Furthermore, you are not required to work in terms of interface pairs which must be implemented by both the provider and the consumer. With ASP.NET 2.0, only the provider is required to implement an interface.

To see how it all works, let's start by creating a connection between two Web Parts. For the examples I am presenting in this month's column, I decided to use the Northwind database because it has a Customers table and an Orders table. This allows me to show you how to design Web Parts for both master-detail and one-to-many relationships. Just note that if you are using SQL Server™ 2005, the sample Northwind database is not installed during product installation. To install it you must download and run the script available on the Microsoft Web site (see Microsoft SQL Server Home).

Now, imagine you want to establish a Web Part connection between a Web Part showing a customer list and a consumer Web Part showing the details of the currently selected customer such as the one shown in Figure 1. The Web Part showing the customer list will play the role of the provider while the Web Part showing the details of the currently selected customer will be the consumer. In this situation, you want the provider to supply the CustomerID field for the currently selected customer to the consumer.

First, create a simple interface named ICustomerIDProvider:

Public Interface ICustomerIDProvider ReadOnly Property CustomerID() As String End Interface

In the code sample that accompanies this month's column, I have created the provider Web Part using a User Control with a SqlDataSource control and a GridView control to display customers from Northwind. The Web Part source files are Customers.ascx and Customers.ascx.vb, shown in Figure 2.

Figure 2 Customers.ascx.vb

Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Partial Class WebParts_Customers Inherits WebPartBase Implements ICustomerIDProvider Sub New() Title = "NorthWind Customer List" TitleIconImageUrl = "~\img\Customers.gif" End Sub <ConnectionProvider("Customer ID Provider")> _ Public Function GetCustomerProvider() As ICustomerIDProvider Return Me End Function Public ReadOnly Property CustomerID() As String _ Implements ICustomerIDProvider.CustomerID Get If gridCustomers.SelectedDataKey Is Nothing Then Return String.Empty Else Return Me.gridCustomers.SelectedDataKey.Value End If End Get End Property End Class

As you can see, WebParts_Customers acts as a provider and implements the interface used for the Web Part connection. In this case, WebParts_Customers implements the ICustomerIDProvider interface. While the most common pattern is that the provider WebPart implements the connection interface itself, it is not required to do so. The only actual requirement is that the ConnectionProvider method return an instance of the specified interface. So, as an alternative, the provider Web Part could instead return a helper object that implements the connection interface. This is typically necessary if a provider Web Part has more than one connection point with the same interface type.

The WebParts_Customers class implements the CustomerID property by returning the value from the GridView control's SelectedDataKey property. The GridView control has been set up to display records from the Northwind Customers table and it also recognizes the CustomerID field as the SelectedDataKey value.

You should notice that the WebParts_Customers class has a method named GetCustomerProvider with a return type defined in terms of the ICustomerIDProvider interface. In this case, since the Web Part itself implements the desired interface, the GetCustomerProvider can simply return the Me reference to the current instance of the class. Also note that this method has been defined using the ConnectionProvider attribute:

<ConnectionProvider("Customer ID Provider")>

The WebPartManager is responsible for connecting Web Parts at run time. When the WebPartManager sees that a Web Part contains a method defined with ConnectionProvider attribute, it knows the Web Part exposes a connection point and can, therefore, act as a provider and be connected to a consumer. When it's time to connect two Web Parts together, the WebPartManager will call the GetCustomerProvider method to obtain a strongly typed reference to the provider Web Part.

It's possible to define whether a provider Web Part accepts multiple connections to consumers. In some case it is fine for a provider to have connections to multiple consumers at the same time. In other situations, you might want to limit a provider so it can have at most a single connection to one consumer Web Part. By default, providers allow multiple connections, and consumers do not. To change this, when you apply the ConnectionProvider attribute, you can make use of the named parameter AllowsMultipleConnections, as shown here:

<ConnectionProvider("Customer ID Provider", _ AllowsMultipleConnections:=False)>

Now that you have seen how to create a method that exposes a connection point in the provider Web Part, let's see how this is complemented within a consumer Web Part. A consumer WebPart exposes a connection point by supplying a method defined with the ConnectionConsumer attribute. The consumer's connection point method is different from the provider's connection method because it does not define a return value. Instead, it takes a single parameter defined using the connection's interface type:

<ConnectionConsumer("Customer ID Consumer")> _ Sub RegisterCustomerProvider(ByVal provider As ICustomerIDProvider) ... ' implementation End Sub

Keep in mind that the names of the provider's connection point method and the consumer's connection point method are not important. The only thing that matters is that each method be defined using the ConnectionProvider attribute and the ConnectionConsumer attribute, respectively.

Now let's see how the connection is established by the WebPartManager at run time. The WebPartManager calls the provider's connection point method to acquire the reference to the provider object. Next, the WebPartManager calls the consumer's connection point method to pass it a strongly typed reference back to the provider.

Once the WebPartManager has done its work, the consumer Web Part has an active connection back to the provider Web Part. At this point, the consumer can interact directly with the provider by using this reference to access the methods and properties defined in the interface. However, the ASP.NET team recommends that consumer Web Parts should not use methods or properties on the provider interface until the PreRender phase. Specifically, they should not use methods or properties on the provider interface in the <ConnectionConsumer()> method itself. The reason is that connections may have dependencies on each other. You may have a ProviderWebPart, connected to a ProviderConsumerWebPart, connected to a ConsumerWebPart. The ConsumerWebPart cannot query the provider interface until both connections have been established, and the order in which the connections are established is up to the Framework.

A complete code listing for the consumer Web Part in CustomerDetails.ascx.vb is shown in Figure 3. You can see that the consumer Web Part is responsible for holding on to a reference so it can track its connection to the provider. This consumer Web Part contains a private field named provider that is defined in terms of the ICustomerIDProvider interface.

Figure 3 Consumer Web Part

Partial Class WebParts_CustomerDetails Inherits WebPartBase Sub New() Me.Title = "Customer Details" Me.TitleIconImageUrl = "~\img\Customer.gif" End Sub Private provider As ICustomerIDProvider <ConnectionConsumer("Customer ID Consumer")> _ Sub RegisterCustomerProvider(ByVal provider As ICustomerIDProvider) Me.provider = provider End Sub Protected Sub SqlDataSource1_Selecting(ByVal sender As Object, _ ByVal e As SqlDataSourceSelectingEventArgs) _ Handles SqlDataSource1.Selecting SqlDataSource1.FilterExpression = _ "CustomerID='" & provider.CustomerID & "'" End Sub End Class

When the WebPartManager calls RegisterCustomerProvider, the consumer takes the incoming reference parameter and assigns it to the provider field. Once the provider field has been assigned the reference, the consumer Web Part can then directly interact with the provider Web Part. When you design the interface for a connection, you should add whatever methods and properties will provide the interaction you require.

There are certain situations in which a consumer Web Part may be designed to work regardless of whether there is an active connection to a provider. In such a design, you will want to add contingency code into the consumer Web Part that works correctly in cases when the provider field has a value of Nothing:

If provider IsNot Nothing Then ... ' interact with provider Else ... ' contingency code goes here if required End If

Timing a Web Part Connection

When you begin to design Web Parts that support connections, your understanding of the timing involved is critical. Figure 4 shows a trace of a page running a provider Web Part and a consumer Web Part during an HTTP GET. While there are more page-level events that fire during an HTTP POST, the timing for when the connection is established remains the same.

Figure 4 Page Trace

The trace information shown in Figure 4 illustrates when each of the connection methods is fired with respect to the standard ASP.NET 2.0 page-level events. You should be able to see from this trace information that the WebPartManager connects Web Parts together during the page-level LoadComplete command.

It's important to remember that the Web Part connection has not been established when page-level events PreInit, Init, PreLoad, and Load are executing. That means you should never attempt to access the provider inside a consumer Web Part's handler method that is bound to one of these events. The code inside a consumer Web Part must wait until after the LoadComplete event has executed before trying to access the provider Web Part.

In this example, the consumer Web Part handles the Selecting event of the SqlDataSource control, which fires during the page-level PreRender event. At this point it is safe to access the provider and retrieve the customer ID.

Defining Static Web Part Connections

Now that you have seen how to create Web Parts that support connections and you understand the timing involved, it's time to explore how to actually connect them together. As you will see, you can add tags directly to a Web Part page definition to establish a static Web Part connection. Web Part connections can also be established dynamically at run time through either code or user interaction. I am going to begin by showing how to create a static connection between two Web Parts because it is the most straightforward approach.

To create a static Web Part connection between two Web Parts on a page, you add a StaticConnections element inside the WebPartManager tag:

<asp:WebPartManager ID="WebPartManager1" runat="server"> <StaticConnections> <asp:WebPartConnection ID="c1" ProviderID="Customers1" ConsumerID="CustomerDetails1" /> </StaticConnections> </asp:WebPartManager>

For this code to work properly, the provider Web Part named Customers1 and the consumer Web Part named CustomerDetails1 must also be statically defined and properly named within Web Part Zones on the same page.

It's important to remember that there can only be one WebPartManager control per page. However, in many application designs involving Web Parts, you'll find that it's convenient to add the WebPartManager to a User Control or a Master Page so that it can be reused across many pages.

When a Web Part page is based on a Master Page or is using a User Control that contains a WebPartManager control, you cannot add a second instance of the WebPartManager control to define a StaticConnections tag. For these situations, the ASP.NET 2.0 Web Part control set provides the ProxyWebPartManager control. Here's an example of how to use it:

<asp:ProxyWebPartManager ID="ProxyWebPartManager1" runat="server"> <StaticConnections> <asp:WebPartConnection ID="c1" ProviderID="Customers1" ConsumerID="CustomerDetails1" /> </StaticConnections> </asp:ProxyWebPartManager>

The value of the ProxyWebPartManager control is that it allows you to add static connections at the page level in situations where the page cannot contain a WebPartManager tag. In the sample page default.aspx that accompanies this column, the ProxyWebPartManager must be used to establish a static Web Part connection because the WebPartManager has been encapsulated within a User Control named WebPartManagerPanel.ascx.

Named Connection Points

In the example I have built up so far, the connection between the provider Web Part and the consumer Web Part have been based on default connection points. However, it is possible for a Web Part connection method to provide a named connection point. To add a named connection point to the provider, you simply add a second string parameter to the ConnectionProvider attribute:

<ConnectionProvider("Customer ID Provider", "CustomerIDProvider")> _ Public Function GetCustomerProvider() As ICustomerIDProvider Return Me End Function

One motivation for named connection points is that a provider or a consumer can have more than one connection point and, therefore, must be able to differentiate between them. To add a named connection point to the consumer Web Part, you can add a second string parameter to the ConnectionConsumer attribute:

<ConnectionConsumer("Customer ID" & "Consumer", "CustomerIDConsumer")> _ Sub RegisterCustomerProvider(ByVal provider As ICustomerIDProvider) Me.provider = provider End Sub

When you begin to use named connection points, you must supply two extra attribute values when you define a StaticConnections tag at the page level for the ProviderConnectionPointID and the ConsumerConnectionPointID.

Establishing Connections Dynamically

There are times when you will want to connect Web Parts together where you will not be able to rely on static Web Part connections. For example, this will be the case if you want to connect Web Parts that have been dynamically created either through custom code or by users who have added Web Parts to a page using a Web Part catalog.

In cases where static Web Part connections cannot be used, you must connect Web Parts using a dynamic technique. This can be done either through custom code or by using the ConnectionsZone control that is included with the ASP.NET 2.0 Web Part control set.

Let's start by examining custom code that creates two Web Parts and connects them together dynamically, as you can see in Figure 5. This code creates Web Part instances from user controls for the provider and the consumer and adds them to existing Web Part zones on the hosting Web Part page. It then establishes a connection between them. Note that the Web Parts and connections will be saved as part of a user's personalization information, so they should only be added to the WebPartManager once. Additional APIs are available if you don't want the Web Parts and connections saved in this fashion.

Figure 5 Web Part Connections Established at Run Time

'*** get WebPartManager object Dim wpMgr As WebPartManager = _ WebPartManager.GetCurrentWebPartManager(Me.Page) '*** create and add provider Web Part Dim uc1 As UserControl = Me.Page.LoadControl("~\WebParts\Customers.ascx") uc1.ID = "wp1" Dim wp1 As GenericWebPart = wpMgr.CreateWebPart(uc1) wp1 = wpMgr.AddWebPart(wp1, LeftWebPartZone, 0) '*** create and add consumer Web Part Dim uc2 As UserControl = _ Me.Page.LoadControl("~\WebParts\CustomerDetails.ascx") uc2.ID = "wp2" Dim wp2 As GenericWebPart = wpMgr.CreateWebPart(uc2) wp2 = wpMgr.AddWebPart(wp2, RightWebPartZone, 0) '*** get desired connection points for provider and consumer Dim cp1 As ProviderConnectionPoint = _ wpMgr.GetProviderConnectionPoints(wp1)("CustomerIDProvider") Dim cp2 As ConsumerConnectionPoint = _ wpMgr.GetConsumerConnectionPoints(wp2)("CustomerIDConsumer") '*** dynamically establish Web Part connection wpMgr.ConnectWebParts(wp1, cp1, wp2, cp2)

Figure 6 Connect Display Mode

Figure 6** Connect Display Mode  **

You can see that the technique shown in Figure 5 requires the use of a ProviderConnectionPoint object and a ConsumerConnectionPoint. These objects can be retrieved by calling methods supplied by the WebPartManager and passing the string identifiers for those named connection points.

The other technique you and your users can employ to establish a dynamic Web Part connection involves the ConnectionsZone control. To use this technique effectively, you should create a Web Part page with a right-hand task pane that contains the ConnectionsZone control. Once the user puts the page into connect view display mode, a Connect command will be added to the Web Part menu of each Web Part that exposes connection points, as shown in Figure 6.

When the user selects the Connect command, the Web Part page then displays the ConnectionsZone control and allows the user to see all the compatible connection points from all the connectable Web Parts on that page (see Figure 7). Using this technique, you can connect a consumer to a provider. Likewise, you can also connect a provider to the consumer.

Figure 7 ConnectionsZone Control

Send your questions and comments for Ted to  instinct@microsoft.com.

Ted Pattison is an author and trainer who delivers hands-on training through his company, the Ted Pattison Group. Ted is currently researching and writing a new book focusing on Windows SharePoint Services "V3" and Office 12 servers technology.