|   Contact  

ASP.NET WebParts Connections Transformers Tutorial


Sept 2004

Summary:
This article explains how you can use the WebPart Connections Transformers feature to allow two incompatible WebParts to communicate between each other without having to modify their source code, building an intermediate class that transforms communication between them.

Visits:


Contents

Download: Pending...

Introduction
Sample Scenario
Summary


Introduction

Connections are a very powerful feature of WebParts that allows interchanging data between two webparts, allowing creating more interesting non-monolitic WebParts.
A connection in WebParts is essentially the ability to expose an interface to a WebPart (Provider) that another WebPart (Consumer) can connect to and use it. This is an interesting feature that allows the creation of atomic units that are completely decoupled from each other. This allows end users to connect WebParts from different vendors to interact their web site even when the vendors were never aware of them.

ASP.NET WebPart connections give a lot of flexibility allowing WebPart developers to expose any arbitrary interfaces that might include events, methods, properties or anything that interfaces allow. This allows building interesting connections that can allow consumers notify providers using methods or events.
However, with this flexibility comes a problem, each vendor might start defining their own interfaces which will make potentially imposible to end users inter-connect webparts seemlesly.
ASP.NET understand the problem and to mitigate it, it has two features:

  • It defines a pre-defined set of "standard" interfaces that different vendors could try to stick to, so their webparts are more "connectable". Interfaces like IField, IParameters, IRow, etc try to provide common patterns of data interchange.
  • Transformers. This feature allows to build special "bridges" that allows to connect webparts that by definition are incompatible. This is what we will be covering in this article.

Sample Scenario

The scenario that we will create is the simplest transformer possible.
Lets assume we downloaded a DLL from a WebSite that "vendor A" created. This webpart exposes a connection with an interface IFoo (FooProvider WebPart).
We also downloaded from another site a WebPart that knows how to consume an interface called IBar (BarConsumer WebPart).
The problem that we have is that we would want to connect them to share data, but they are incompatible since they expose/consume different interfaces, so the ASP.NET WebPart Framework will not allow us to connect them.
So our job is to create the "bridge" that will tell the WebPart framework that they are "connectable", and that we know how to translate between one and the other.

Create a new Project


Create a new Web Application in Visual Web Developer or Visual Studio .NET 2005.

Create the WebParts

Even though this sample is not meant to show how to do the WebParts and the connections, we will show the code so you see how simple it is.
Just keep in mind that you do not need the source code of WebParts, or the interfaces to be able to build a transformer that converts between two different webparts.

Create the Code Directory
  • Select the project node in the Solution Explorer.
  • Right click it and select New Folder
  • Type Code as the name of the folder
Note. the Code directory is new to ASP.NET 2.0 that allows developers to place code inside this directory with the .cs or .vb extension and the asp.net build infrastructure will compile the code on demand only when needed. In the Beta2 it might be renamed to be called Application_Code.

Add a new BarConsumer file to the directory.
  • Select the Code folder
  • Right click it and select the option Add New Item
  • Select the option Class and type as the name BarConsumer
  • Replace the content of the generated class with the following code.
C# Code:
namespace CarlosAg.Samples {
    
using System;
    using 
System.Web.UI;
    using 
System.Web.UI.WebControls.WebParts;

    
/// <summary>
    /// Simple Interface that Shares an Integer property
    /// </summary>
    
public interface IBar {
        
int IntegerData { get;}
    }

    
/// <summary>
    /// WebPart that consumes an IBar.
    ///     Displays the info
    /// </summary>
    
public class BarConsumer : WebPart {
        
private IBar _bar;

        
/// <summary>
        /// Exposes the Consumer Connection Point
        /// </summary>
        
[ConnectionConsumer("IBar Consumer")]
        
public void SetProvider(IBar bar) {
            
this._bar bar;
        
}

        
/// <summary>
        /// Renders the Data
        /// </summary>
        /// <param name="writer"></param>
        
protected override void RenderContents(HtmlTextWriter writer) {
            
if (_bar != null) {
                writer.Write(
"Integer Data:" + _bar.IntegerData.ToString());
            
}
            
else {
                writer.Write(
"IBar is null");
            
}
        }
    }
}

VB.NET Code:

Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls.WebParts

Namespace CarlosAg.Samples
    
' <summary>
    ' Simple Interface that Shares an Integer property
    ' </summary>
    
Public Interface IBar
        
ReadOnly Property IntegerData() As Integer
    End Interface

    
' <summary>
    ' WebPart that consumes an IBar.
    '     Displays the info
    ' </summary>
    
Public Class BarConsumer
        
Inherits WebPart

        
Private _bar As IBar

        
' <summary>
        ' Exposes the Consumer Connection Point
        ' </summary>
        
<ConnectionConsumer("IBar Consumer")> _
        
Public Sub SetProvider(ByVal bar As IBar)
            
Me._bar bar
        
End Sub

        
' <summary>
        ' Renders the Data
        ' </summary>
        ' <param name="writer"></param>
        
Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            
If (Not (_bar) Is Nothing) Then
                
writer.Write(("Integer Data:" + _bar.IntegerData.ToString))
            
Else
                
writer.Write("IBar is null")
            
End If
        End Sub
    End Class
End Namespace

As you can see creating a consumer is extremely easy, just expose a public method that has no return value, that receives one object and that is tagged with the ConnectionConsumer attribute.
Once you do that, just make sure to save a reference of the object passed and use it later, say at PreRender, or Render methods, to ensure everything is connected correctly.

Create the Provider WebPart


Create the FooProvider class
  • Select the Code folder node
  • Right click it and select the option Add New Item
  • Select the option Class
  • Type FooProvider as the name and replace the content as follows
C# Code
namespace CarlosAg.Samples {
    
using System;
    using 
System.Web.UI.WebControls;
    using 
System.Web.UI.WebControls.WebParts;

    
/// <summary>
    /// Simple Interface that Shares a String property
    /// </summary>
    
public interface IFoo {
        
string StringData { get;}
    }

    
/// <summary>
    /// WebPart that serves as an IFoo Provider.
    ///     Prompts the user using a TextBox
    /// </summary>
    
public class FooProvider : WebPart, IFoo {
        
private TextBox _dataTextBox;

        
/// <summary>
        /// Public property to allow users to set the Data
        /// </summary>
        
[Personalizable(PersonalizationScope.User)]
        
public string Data {
            
get {
                
return TextBox.Text;
            
}
            
set {
                TextBox.Text 
= value;
            
}
        }

        
/// <summary>
        /// Text Box to allow end users to change the data
        /// </summary>
        
protected TextBox TextBox {
            
get {
                EnsureChildControls()
;
                return 
_dataTextBox;
            
}
        }

        
/// <summary>
        /// Creates a new instance of the class
        /// </summary>
        
public FooProvider() {

        }

        
/// <summary>
        /// Overrides the CreateChildControls to create the TextBox
        /// </summary>
        
protected override void CreateChildControls() {
            Label _label 
= new Label();
            
_label.Text "Enter a Number:";
            
_label.Font.Bold = true;
            this
.Controls.Add(_label);

            
_dataTextBox = new TextBox();
            
_dataTextBox.AutoPostBack = true;
            
_dataTextBox.TextChanged += new EventHandler(_dataTextBox_TextChanged);
            this
.Controls.Add(_dataTextBox);
        
}

        
/// <summary>
        /// Event raised when the user changes the text
        /// </summary>
        
private void _dataTextBox_TextChanged(object sender, EventArgs e) {
            Data 
TextBox.Text;
        
}

        
// ***********************************************
        // Here starts the connection related code
        // ***********************************************
        /// <summary>
        /// Exposes the Provider Connection Point
        /// </summary>
        
[ConnectionProvider("IFoo Provider")]
        
public IFoo GetProvider() {
            
return this;
        
}

        
/// <summary>
        /// Implementation of the interface
        /// </summary>
        
string IFoo.StringData {
            
get {
                
return this.Data;
            
}
        }
    }
}

VB.NET Code

Imports System
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Namespace CarlosAg.Samples

    
' <summary>
    ' Simple Interface that Shares a String property
    ' </summary>
    
Public Interface IFoo
        
ReadOnly Property StringData() As String
    End Interface

    
' <summary>
    ' WebPart that serves as an IFoo Provider.
    '     Prompts the user using a TextBox
    ' </summary>
    
Public Class FooProvider
        
Inherits WebPart
        
Implements IFoo

        
Private _dataTextBox As TextBox

        
' <summary>
        ' Creates a new instance of the class
        ' </summary>
        
Public Sub New()
            
MyBase.New()
        
End Sub

        
' <summary>
        ' Public property to allow users to set the Data
        ' </summary>
        
<Personalizable(PersonalizationScope.User)> _
        
Public Property Data() As String
            Get
                Return 
TextBox.Text
            
End Get
            Set
(ByVal value As String)
                TextBox.Text 
value
            
End Set
        End Property

        
' <summary>
        ' Text Box to allow end users to change the data
        ' </summary>
        
Protected ReadOnly Property TextBox() As TextBox
            
Get
                
EnsureChildControls()
                
Return _dataTextBox
            
End Get
        End Property

        
' <summary>
        ' Implementation of the interface
        ' </summary>
        
ReadOnly Property IFoo_StringData() As String _
                            
Implements IFoo.StringData
            
Get
                Return Me
.Data
            
End Get
        End Property

        
' <summary>
        ' Overrides the CreateChildControls to create the TextBox
        ' </summary>
        
Protected Overrides Sub CreateChildControls()
            
Dim _label As Label = New Label
            _label.Text 
"Enter a Number:"
            
_label.Font.Bold = True
            Me
.Controls.Add(_label)
            _dataTextBox 
= New TextBox
            _dataTextBox.AutoPostBack 
= True
            AddHandler 
_dataTextBox.TextChanged, AddressOf Me._dataTextBox_TextChanged
            
Me.Controls.Add(_dataTextBox)
        
End Sub

        
' <summary>
        ' Event raised when the user changes the text
        ' </summary>
        
Private Sub _dataTextBox_TextChanged(ByVal sender As Object, _
                            
ByVal As EventArgs)
            Data 
TextBox.Text
        
End Sub

        
' ***********************************************
        ' Here starts the connection related code
        ' ***********************************************
        ' <summary>
        ' Exposes the Provider Connection Point
        ' </summary>
        
<ConnectionProvider("IFoo Provider")> _
        
Public Function GetProvider() As IFoo
            
Return Me
        End Function
    End Class
End Namespace

Creating the page

Now we will create the page where we actually use both webparts.

Create the Page
  • Select the Project node
  • Right click it and select the option Add New Item
  • Select the option Web Form
  • Type Sample.aspx as the name for the Page
  • Replace the content of the Page so that it looks as the following code 

Code

<%@ Page Language="C#" %>
<%@ Register TagPrefix="sample" Namespace="CarlosAg.Samples" %>
<html>
<
head runat="server">
    <
title>Transformer Sample</title>
<
/head>
<
body>
    <
form id="form1" runat="server">
        <
asp:WebPartManager ID="WebPartManager1" Runat="server" />
        <
asp:WebPartPageMenu ID="WebPartPageMenu1" Runat="server" />
        <
hr />
        <
asp:WebPartZone ID="WebPartZone1" Runat="server" PartTitleStyle-BackColor="#99ccff">
            <
ZoneTemplate>
                <
sample:FooProvider Title="Foo Provider" Runat="server" ID="fooProvider1" Data="23" />
                <
sample:BarConsumer Title="Bar Consumer" Runat="server" ID="barConsumer1" />
            <
/ZoneTemplate>
            <
PartStyle BorderColor="SlateGray" BorderStyle="Solid" BorderWidth="4px" />
        <
/asp:WebPartZone>
        <
br />
        <
asp:ConnectionsZone ID="ConnectionsZone1" Runat="server" HeaderStyle-BackColor="#99ccff" />
    <
/form>
<
/body>
<
/html>

If you try running the page now, you should be able to see both webparts working, you can use the WebPartPageMenu to switch to connect mode, and you will notice that no connection can be established, since as we said they are incompatible.

Creating the Transformer

Now we will create the actual Transformer class that will allow us to connect both webparts.

Create the FooToBarTransformer class
  • Select the Code node
  • Right click it and select the option Add New Item
  • Select the option Class
  • Type FooToBarTransformer as the name for the Class
  • Replace the content so that it looks as the following code 

C# Code
namespace CarlosAg.Samples {
    
using System;
    using 
System.Web.UI.WebControls.WebParts;

    
[Transformer(typeof(IFoo), typeof(IBar))]
    
public class FooToBarTransformer : Transformer, IBar {
        IFoo _foo
;

        
/// <summary>
        /// Transforms from IFoo to IBar
        /// </summary>
        
public override object Transform(object providerData) {
            _foo 
(IFoo)providerData;
            return this;
        
}

        
/// <summary>
        /// Implements IBar funtionality
        /// </summary>
        
int IBar.IntegerData {
            
get {
                
if (_foo.StringData != null) {
                    
try {
                        
return int.Parse(_foo.StringData);
                    
}
                    
catch { }
                }
                
return -1;
            
}
        }
    }
}

VB.NET Code
Imports System
Imports System.Web.UI.WebControls.WebParts

Namespace CarlosAg.Samples

    <Transformer(
GetType(IFoo), GetType(IBar))> _
    
Public Class FooToBarTransformer
        
Inherits Transformer
        
Implements IBar

        
Private _foo As IFoo

        
' <summary>
        ' Implements IBar funtionality
        ' </summary>
        
ReadOnly Property IBar_IntegerData() As Integer _
                
Implements IBar.IntegerData
            
Get
                If 
(Not (_foo.StringData) Is Nothing) Then
                    Try
                        Return CInt
(_foo.StringData)
                    
Catch ex As System.Exception
                    End Try
                End If
                Return 
-1
            
End Get
        End Property

        
' <summary>
        ' Transforms from IFoo to IBar
        ' </summary>
        
Public Overrides Function Transform(ByVal providerData As ObjectAs Object
            
_foo = CType(providerData, IFoo)
            
Return Me
        End Function
    End Class
End Namespace

If you try running the page again, you will still see you are not able to connect them. The reason for this is that in order for transformers to work you have to "publish" them in the web.config or machine.config.

Modifying the web.config

Open the web.config and add the webParts section that is shown below so that it is inside the system.web section.

Web.Config
<?xml version="1.0" ?>
<
configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <
system.web>
        <
authentication mode="Windows" />
        <
webParts>
            <
transformers>
                <
add name="Foo To Bar Transformer" 
                     type
="CarlosAg.Samples.FooToBarTransformer"  />
            <
/transformers>
        <
/webParts>
    <
/system.web>
<
/configuration>

Running the Sample

To run the sample just browse to Sample.aspx.
Select the Connect WebParts option in the drop down menu and select the Change button


Click the Connect Verb from the Popup Menu that will appear in the Title of the Provider WebPart.
At this point the WebPart framework will realize that this WebPart can only be used as a provider and will enable only a link to connect to a consumer.
Click the Link "Create a Connection to a Consumer".
The WebPart framework will look for all compatible consumer connections and will look for possible transformers as well.
It will realize that the FooToBarTransformer is available and so it will display as a possible connection the Foo -> Bar.
Select the Bar consumer from the drop down, and click Connect.
At this point the webparts are connected, so type some numbers in the text box and press Tab, or move the focus out of the text box so the autopostback works and will update the second webpart.

Summary

Connections are a very powerfull feature in WebParts. Transformers give the ability to connect webparts that otherwise would be incompatible.
In this tutorial we just scratched the surface of Transformers, there are other methods that you could use to create a User interface to prompt for additional values or parameters for your transformer as well as some methods to load and save custom state that could be use during the transformation.


Carlos Aguilar Mares © 2015