Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Multi-Connected Consumer Web Parts

0.00/5 (No votes)
23 Jul 2008CPOL4 min read 1   203  
Allows consumer web parts to have multiple providers in ASP.NET, WSS 3.0, and MOSS 2007.

Introduction

Have you ever developed a provider / consumer web part pair that would allow a single consumer web part to be connected to multiple provider web parts only to find that ASP.NET / WSS 3.0 / MOSS 2007 only allows a single one-to-one connection? If so, then you have come to the correct location.

Background

A few weeks back, I was developing some new web parts for a SharePoint project I am busy with, and found myself running into a brick wall. The requirement was that when a link was clicked on a list item, some help/information text would be displayed to the right of the list. The requirement also stated that more than one list could be on a single portal page and that the same help/info text web part would be used. I thought this would be easy as I knew the ConnectedConsumerAttribute class had a property called AllowMultipleConnection and all I would have to do is set this value to true; after all, WSS 2.0/MOSS 2003 supported this functionality already. How wrong I was, so after wading through all the forums / blogs / wikis etc., I came to the conclusion that many people were experiencing the same pain as myself. Unfortunately, I have had not been able to find a solution to the problem which has led me to this point. Either the ASP.NET web part implementation is flawed in design, or there is one big defect which needs to be repaired. I think I have come up with a potential solution to the problem (although I thought up several others along the way, this is the slickest).

Using the code

Let me start straight off. This is not a step-by-step how-to to develop a bit of code using VS 2008; I will assume you already know this. I will describe the concepts of the solution using code. My development environment is the following:

  • Vista Enterprise
  • VSTS 2008
  • WSS 3.0 (installed on Vista using Bamboo Solutions install)
  • VS Extensions for SharePoint SVCS 1.2
  • + plenty of other stuff (for this article, the above is required)

NB: Please remember to start VS with elevated credentials if you are using Vista, and don't forget to change the WSS URL in the project debug properties.

The first thing I did was create a new custom attribute class called ConnectedConsumerInterfaceAttribute which I use to mark the relevant method on the consumer web part. This method will be invoked by the provider web part.

C#
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class ConnectConsumerInterfaceAttribute : Attribute
{
   private Type _interfaceType;
   public ConnectConsumerInterfaceAttribute(Type interfaceType)
   {
      _interfaceType = interfaceType;
   }
   public Type InterfaceType
   {
      get
      {
         return _interfaceType;
      }
   }
}

The next thing I did was to subclass the ASP.NET WebPart class and create a ProviderWebPart class. All providers that require to connect to multi-connect consumers will need to inherit from this new class. I added a new protected virtual method called ConnectedConsumerInvoke which has the following signature:

C#
protected virtual void InvokeConnectedConsumer<TInterfaceType>(TInterfaceType graph)

This method has all the magic required to invoke the consumer method based on the interface type and whether it is tagged with the ConnectedConsumerInterfaceAttribute attribute. The method retrieves all the current web part connections using the WebPartManager object. It then loops through each connection until it can find the connections which belong to the current provider web part (which is this). Once the connection provider has been identified as the same object, the connection consumer is added to a list. Once the consumer web part list is fully populated, the code loops through each consumer web part and retrieves all the methods of the web part using Reflection. It then checks to see if the methods are tagged with the ConnectedConsumerInterfaceAttribute attribute. If it is, then the TInterfaceType type is compared to the ConnectedConsumerInterfaceAttribute.InterfaceType, and if the same, the retrieved method is invoked and the graph object is passed in as a parameter. In the code sample attached, I have created a helper class called SPSHelper which does some of the work described above.

C#
public class ProviderWebPart : WebPart
{
   public ProviderWebPart() : base()
   {
   }
   protected virtual void InvokeConnectedConsumer<TInterfaceType>(TInterfaceType graph)
   {
      // Retrieve all the consumer web parts using the SPSHelper class...
      WebPart[] consumerWebParts = SPSHelper.GetConnectedConsumerWebParts(
                                    this.WebPartManager, this, typeof(TInterfaceType));
      // Process all the consumer web parts...
      foreach (WebPart consumerWebPart in consumerWebParts)
      {
         // Retrieve all the methods of the consumer web part...
         MethodInfo[] methods = consumerWebParts.GetType().GetMethods(
                                      BindingFlags.Public | BindingFlags.Instance);
         // Loop through each method and check if any
         // are marked with ConnectConsumerInterfaceAttribute...
         foreach (MethodInfo method in methods)
         {
            // Retrieve all the custom attributes of the method...
            object[] attributes = method.GetCustomAttributes(
                                   typeof(ConnectConsumerInterfaceAttribute), false);
            // Loop through until we find a valid ConnectConsumerInterfaceAttribute
            //                    - there should only be 1...
            foreach (ConnectConsumerInterfaceAttribute attribute in attributes)
            {
              // Check if the interface types are the same...
              if (typeof(TInterfaceType) == attribute.InterfaceType)
              {
                 // Now invoke the method pass in the data...
                 method.Invoke(consumerWebPart, new object[] { graph });
                 // Now need to loop through the rest...
                 break;
              }
            }
         }
      }
   }
}

I then developed two web parts called InputWebPart (provider) and DisplayWebPart (consumer).

C#
public class InputWebPart : ProviderWebPart
{
   private TextBox _txtInputText;
   private ITextData _textData;
   public InputWebPart()
   {
   }
   protected ITextData TextData
   {
      get
      {
         if (_textData == null)
         {
            _textData = new TextDataProvider();
         }
         return _textData;
      }
      set
      {
         _textData = value;
      }
   }
   protected override void CreateChildControls()
   {
      base.CreateChildControls();
      // Add a new text box...
      _txtInputText = new TextBox();
      this.Controls.Add(_txtInputText);
      // Add a new button...
      Button btnSend = new Button();
      btnSend.Text = "Send";
      // Wire up the click event...
      btnSend.Click += new EventHandler(btnSend_Click);
      this.Controls.Add(btnSend);
   }
   private void btnSend_Click(object sender, EventArgs e)
   {
      // this is required to take care of the normal SharePoint behaviour...
      this.TextData.Text = _txtInputText.Text;
      // this is where all the magic happens...
      this.InvokeConnectedConsumer<ITextData>(this.TextData);
   }
   // This is required for SharePoint to allow provider to connect to consumers...
   [ConnectionProvider("Text Data")]
   public ITextData SetTextData()
   {
      return _textData;
   }
}

public class DisplayWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
   private ITextData _textData;
   public DisplayWebPart()
   {
   }
   protected ITextData TextData
   {
      get
      {
         return _textData;
      }
      set
      {
         _textData = value;
      }
   }
   protected override void CreateChildControls()
   {
      base.CreateChildControls();
      if (this.TextData != null)
      {
         Label lblDisplayText = new Label();
         lblDisplayText.Text = this.TextData.Text;
         this.Controls.Add(lblDisplayText);
      }
   }
   // Required for SharePoint to allow multiple connections...
   [ConnectionConsumer("Text Data Consumer", AllowsMultipleConnections = true)]
   // Required for the provider to invoke multiple connected consumers...
   [ConnectConsumerInterface(typeof(ITextData))]
   public void GetTextData(ITextData textData)
   {
      this.TextData = textData;
      this.EnsureChildControls();
   }
}

That is the bulk of the code in the sample. Once you load the sample into VS 2008, it will make more sense. To deploy the solution into WSS 3.0, right click the solution file in the VS Solution Explorer and click the Deploy option in the context menu.

I hope this will make your lives a lot easier when developing web parts in the future.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)