WebParts and Cross-Page Connections

A quick introduction to concepts and terminology about the connections feature within the ASP.NET Whidbey Web Parts Framework, followed by a discussion of building support for cross-page connections using the extensibility mechanisms, along with associated code to download and play with...

I was asked why we didn't have cross-page connections support built in into ASP.NET Whidbey like Sharepoint by someone who happens to be a Sharepoint consultant. I've had similar discussions with Sharepoint folks while we were working on the design and architecture. So I thought it was time to put together a prototype for this functionality built on top of the Web Part framework we are providing. See this article for an introduction to Web Parts. I'll assume some general idea about ASP.NET Web Parts here.

Simply said, a connection is a mechanism for sharing or transferring data from one Web Part (called the provider) to another Web Part (called the consumer). The scenarios are plentiful. A couple of classic examples include sharing the zip code from a map part with a weather part, and sharing the selected date from a calendar part with an events list part. For the purposes of this post, I have an authors list part and a books list part, and the connection is used to share the selected author to implement a master detail relationship. The Web Part framework allows end-users to choose a consumer part and connect it to a compatible provider part or vice versa and their choices are saved in their personalization state.

Connections are probably the most complicated feature in terms of internal implementation within the Web Part framework (just trust me on this one). But the complexity is well worth it. The feature open up the possibilities for end-users to define interesting ways of sharing data... something that has so far been an ability of the application developer primarily. Our goals in this area were to provide an extensible platform, and at the same time, make it super-simple for Web Part and page developers to participate and use this mechanism. I hope you'll see that as well, once you've had a chance to read (this somewhat long post) and look through the sample code.

Introduction
Let me start with some key related terminology and concepts in addition to the above:

  • Connection Interface: The contract shared between the provider and consumer. We provide some core interfaces in the framework (like IField, IRow and ITable), but one of the nice things about our framework is the ability to define custom ones. There's also the concept of secondary interfaces, which typically aren't used to share data, but instead used to indicate additional functionality or semantics associated with the data (I'll rely on these extensibility mechanisms here). These interfaces are used to determine compatibility between providers and consumers.
  • Connection Point: A connection point is the definition of one end point of a connection. The connection point describes the data being provided and consumed in end-user terminology, as well as the associated interface contract.
  • Transformers: I won't get into this are much further here. For completeness however, a transformer is a piece of code that can be used to connect an otherwise incompatible provider and consumer pair. For example, the user can connect a provider with a row of data to a consumer requiring a single value, by selecting the desired column in a transformer that presents IRow data as IField data.
  • Connection Lifecycle: Connections are activated, i.e. data from the provider is retrieved and handed to the consumer during the PreRender phase of the page lifecycle. This is important to keep in mind, especially, if you have connection semantics associated with post-back events (like I do in this prototype).

One more bit of introductory material... specifically how Web Parts participate in connections. At the surface, this is as simple as writing a method and adding a piece of metadata. The framework takes care of the rest. For example:

// In the provider Web Part
[ConnectionProvider("Selected Author")]
public IField GetFieldConnectionData() { ... }

// In the consumer Web Part
[ConnectionConsumer("Selected Author")]
public void SetAuthorID(IField provider) { ... }

I should add that support for connections can be added regardless of whether the part derives from WebPart, or is implemented as a user control, or is in the form of an arbitrary custom control. The connection methods with appropriate metadata are all that are needed.

Cross Page Connections Prototype
With that introduction, let's now focus on what I set out to do: cross-page connections. A fundamental assumption that the Web Part framework makes is that the end-points of a connection are Web Parts on the same page. With cross-page connections, that assumption is invalidated.

The general idea with the prototype is to provide a couple of proxy Web Parts - one representing the consumer on the first page, and another representing the provider on the second page. For the purposes of this prototype, the data being transferred will be represented by the stock IField connection interface, and will be transferred via a query string variable on the URL. The proxies are the ones with knowledge about cross-page connections - not the actual Web Parts, and not the Web Part framework either, and things just work - the power of extensibility!

The consumer proxy will provide personalizable properties that allow the end-user to indicate the URL of the target page, and the query string variable. With this, the consumer proxy Web Part has all the data it needs - the field, and the target URL to redirect to. However it needs to know when to perform the redirect. The idea is there is some well-defined user action within the provider Web Part (for example, selection via a mouse click) that should trigger the navigation. However, this needs to be encapsulated into more generalized interface. So I have defined IAction in this prototype (which I believe belongs in the core framework... perhaps in v-next).

public interface IAction {
    event EventHandler ActionTriggred;
}

The data being transferred is still represented as an IField. Therefore, IAction is exposed as a secondary interface. Exposing secondary interfaces requires a bit more work on the part of the developer - specifically creating derived ConnectionPoint classes on both the provider and consumer end to add and look for the secondary interface respectively, when the framework tries to pair up and determine the possible set of compatible connection end points. The derived ConnectionPoint classes are associated via metadata. For example, in the prototype you'll see:

// In the provider Web Part
[ConnectionProvider("Selected Author", typeof(ActionProviderConnectionPoint))]
public IField GetFieldConnectionData() { ... }

// In the consumer Web Part
[ConnectionConsumer("Selected Author", typeof(ActionConsumerConnectionPoint))]
public void SetAuthorID(IField provider) { ... }

So here is what happens at runtime, and should help as you read the sample code:

  1. Startup: The framework creates the set of possible connection points. It uses the custom connection point classes specified in the metadata. The ActionProviderConnectionPoint adds the IAction interface to the set of secondary interfaces.
  2. Creating connections: When the user attempts to create a connection, the ActionConsumerConnectionPoint inspects the set of secondary interfaces available from a given provider to determine if it provides the IAction capability. If it does, it is a compatible provider.
  3. Connection activation: The framework retrieves the IField data from the provider and hands it to the consumer proxy. The consumer proxy hangs on to the IField provider it is handed by the framework. In addition, it looks for the IAction implementation, and subscribes for the ActionTriggered event.
  4. User selects an author: The action is triggered. The consumer proxy's event handler is invoked, which retrieves the field from the provider it is bound to, and performs the redirect passing the field of data on the query string.
  5. Target page: The connection between the provider proxy and real consumer is also created and activated in much the same way on the target page. The consumer requests the field of data, and the provider proxy looks up the field of data from the query string and hands it out.

Here's a picture that attempts to sketch what is happening:

Code to Download
You can download the sample code including a mini-portal web site and Authors (the provider) and Book List (the consumer) web parts. Here's a version that goes against some older bits (such as the beta), and here's a version that goes against the November CTP.

To use the sample, login (first creating yourself an account if needed), and then connect the Authors Web Part and consumer proxy on Default.aspx as well as customize the consumer proxy to specify MyPage.aspx as the target page, and "author" as the query string field. On MyPage.aspx, connect the provider proxy and the Books Web Part, and customize the provider proxy to indicate "author" as the query string field. All of this will be maintained in your personalization state.

Summary
I hope this was an interesting read, albeit long. Hopefully it makes you curious about what is possible with the new Web Part framework as well.

To tie back to why isn't this functionality simply in the framework... as you can see some of this is complicated, maybe even beyond the scope of more simple end-users. I believe however, that the portal developer or a portal tool can simplify some of this to provide a more integrated experience, and hide the proxies under the covers. I think cross-page connections have more to do with the overall user experience of sharing data, than the underlying implementation; hence, they're not built in into the platform.

Anyway, I'd love to hear thoughts on this prototype, the general idea, and suggestions for any other topics you'd like to see covered.

Posted on Thursday, 1/6/2005 @ 2:02 PM | #ASP.NET


Comments

18 comments have been posted.

Dimitri Glazkov

Posted on 1/7/2005 @ 6:15 AM
Interesting! Can I generalize a bit? As usual, I am picking at a particular thing in the article, not its topic. Basically, I see IAction as a step to formalize user interaction with the application -- exressly defined transition from one state to another. If in v1.1 and I believe in v2.0, these transitions is pretty much ad hoc and not advertised as such in the development paradigm, this code seems to have a more prononunced notion of a state machine architecture. A couple of the frameworks (struts, Maverick) already make an attempt at that, but I don't believe any of them got it right. What do you think of this whole state machine idea yourself?

Brian's Blog

Posted on 1/11/2005 @ 7:46 AM
Thank Nikhil. Keep the great control focused content coming!

TeunD

Posted on 1/13/2005 @ 2:38 AM
Nice solution. I wondered why you chose to use the standard IField interface and than add a new IAction interface for firing the event. You could also have IActionField inherit from IField and have the proxy class expect the IActionField. This way, the proxy can only be connected to web parts that actually will fire the ActionTriggered event. The UI will show the proxy part only for parts that know how to use it. What is your take on that?

Nikhil Kothari

Posted on 1/13/2005 @ 7:57 AM
IAction seems to have raised interest here.

I think we're just starting to try and distill core functionality into interfaces in ASP.NET in the form of interfaces, and there is obviously more we could do. For example, in Whidbey, we introduced IEditableText to encapsulate the core functionality of all textbox-like controls. This is a first step in allowing components to talk to each other, as we move to enable a more declarative framework, where intent is specified declaratively by placing and connecting components, rather than written in procedural code as it always has been. Connections in WebParts are a just formalization of the same - drawing lines between components directly by app developers, and now end-users. In fact, connections force you to think about what is the core functionality and behavior you want to encapsulate in an interface that can be used in drawing lines.

My thoughts on the whole state machine idea... interesting this came up... I am actually looking at some of this while doing some Orcas thinking presently. I am not sure it needs to permeate ASP.NET in a manner that forces everyone to think of right off the bat to get anything done, but it does have a lot of relevance where the application functionality is most naturally modelled in form of a state diagram, especially in workflow scenarios.

Why I chose to create IAction and not IActionField? Part of defining these interfaces is to build a set of building blocks that can be reused in more than one scenario. If I chose to do IActionField, I might have also needed to create IActionRow, IActionTable, IAction??? leading to interface explosion. Not only is this more costly (test, documentation, design etc.), but it defeats the purpose of connections - you have to have consumers understand each interface to be usable. For example, I could build more generalized proxies in an extension of this prototype that are more general purpose in terms of the data being transferred, as long as IAction is surfaced as a secondary interface.

Nikhil Kothari

Posted on 1/13/2005 @ 8:01 AM
One additional note to address TeunD's comment - the UI will show only compatible Web Parts when trying to create connections. I don't need to create IActionField for that. The compatibility test includes testing for secondary interfaces.

In the sample code, look at the implementation of ActionConsumerConnectionPoint. It checks for the prescence of IAction. So basically, no loss of experience for the end-user even when choosing to use secondary interfaces.

Dimitri Glazkov

Posted on 1/13/2005 @ 11:33 AM
Cool. That's why I am looking into state machines myself. Graphically speaking, if you have a state diagram, you can easily identify connection points of a control by drawing a line around the states that occur inside of the control. The connection points are where transitions intersect the line. Now, if each control advertises connection points, the state machine can be put together by wiring the connection points, which can be done either declaratively or in a GUI. This kind of development opens up a whole lot of different possibilities, including nested state machines and what's more important, is more intuitive and in line with how Web applications are perceived. I am planning a more detailed article on that (between the day job and taking care of the mini-Dimitri) on my blog. I'll let you know when I put it up.

Steve

Posted on 2/11/2005 @ 10:04 PM
Seems the .net framework is going to continue to be complex and instead of making things easier is going to add such complexity, everyone will need a phd in .net to write code that runs well.

Sorry to be synical, but the dev team at Microsoft will hopefully make these things easier to work with - Whidbey seems like a less friendly version of .NET will tons of overly complex controls :(

sekhar

Posted on 3/31/2005 @ 12:56 AM
Hi,

This is sekhar

Please let me the details of How to download that code and how to run that code
.
If it is possible please send me the code this mail id: redputta@in.ibm.com

Thanks and Regards
Sekhar

Sheetal Jain

Posted on 4/5/2005 @ 11:50 AM
Great Article Nikhil

However this technique may not work if you dont know upfront which other .aspx page you want to connect to. For example - in my webpart I let user connect to other web part on fly - so upfront I dont know which webpart page the user is going to drop the webpart - so the only reliable way I could think of is to use QueryString -

by passing some token I let the receiving(consumer) web part know that who is passing the data - and the good part is it all happens on client side so fewer refreshes - thanks to AJAX :)

Doug McClean

Posted on 5/9/2005 @ 7:23 AM
I like the idea of IEditableText, but how about just IEditable<T> { T Value { get; set; } }?

Nikhil Kothari

Posted on 5/29/2005 @ 1:06 PM
I like the generics-based approach actually. Perhaps IValue<T> and IEditableValue<T> ... another item for the v-next bucket.

Michael Murray

Posted on 7/7/2005 @ 7:40 AM
Thanks for sample - It doesn't build under Whidbey Beta2 though due to OM changes (IField etc.) could you posted an updated version?

Regards - Michael

mohamed

Posted on 10/26/2005 @ 5:10 AM
Please send me steps to deploy this sample

Ashwin

Posted on 11/28/2005 @ 5:12 PM
Hi Nikhil, I am fairly new to Web part development, but I have developed two webparts in .net 1.1 that implement the IRowProvider and the IParametersInConsumer interfaces respectively. I have them on two different pages. I am trying to achieve cross page connection, but when I try to connect the two through FP2003, I reach a dialog box that says "There are no Web Parts on the page compatible with the selected action. Please choose another action, or another page for the connection.". But when I put the two webparts on teh same page, they connect right away without any problems. Is there something I need to keep in mind when I attempt cross page connections ? Thank you for any help you could extend in this matter.

Ashwin

Posted on 11/30/2005 @ 8:28 AM
Ok, I finally solved the issue here. When working with cross page connections, there are a few methods that MUST be overridden. The articles below would help.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spptsdk/html/smpxCrossPageComm_SV01031438.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spptsdk/html/CreateConnectableWP_SV01003714.asp

The GetInitEventArgs method was the culprit in my case. I had not implemented it in the IParametersInConsumer interface since it was not a required overrride. But in order to achieve cross page connections, this MUST be overridden. According the MS, the GetInitEventArgs()method only needs to be implemented for interfaces that can participate in a transformer which are the following:

ICellConsumer, IRowProvider, IFilterConsumer, IParametersOutProvider, IParametersInConsumer

Hope this helps someone else.

Eva

Posted on 2/22/2006 @ 10:55 PM
Hello Nikhil,
Fantastic, just what I've been looking for! :-) Could I please ask you for information on exactly how to deploy this project? I thought I did just that, created a web site for it, extended in Sharepoint, but I think the aspx pages should go to a _layouts dir somewhere cos 'as is', I get 404 errors on any aspx pages I attempt to load.

Thank you for your help in advance.

Eva

Cider

Posted on 3/30/2006 @ 11:54 AM
Hello Nikhil,

Thanks for the wonderful article. It is something that I had been looking for.

I ran you sample code and 'am getting the following errors:

1) Element 'DisplayModeDropDownList' is not a known element. This can occur if there is a compilation error in the Web site.
2) The server block is not well formed.
3) Could not load type 'System.Web.UI.WebControls.WebParts.IField'.
4) Could not load type 'ServerWidgets.IActionProvider'.
5) Unknown server tag 'sw:DisplayModeDropDownList'.

Could you please guide me how I can fix the above errors.

Thanks a lot for your help.

Cider

Ed DeGagne

Posted on 5/23/2006 @ 12:56 PM
Nik,

Same issue as Cider here, any ideas on how to fix?
The discussion on this post has been closed. Please use my contact form to provide comments.