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:
- 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.
- 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.
- 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.
- 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.
- 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.