My latest ASP.NET coding endeavor required that I perform a postback and then transfer control over to a different page (primarily for URL concealment purposes) using Server.Transfer. I needed to be able to pass values from the source page to the target page, but didn't really have a good way to do this. A quick gander at the documentation seemed to indicate that the way to do this was to define public properties on the source page, use the Handler property of the Context instance to retrieve a IHttpHandler-based reference to the source page which can then be cast to the source page type, providing you with access to all of the public properties, as shown in the following code block:

MyPage page = ( MyPage ) Context.Handler;
int num = page.MyPublicProperty;

A MSN search (yes, really) revealed several different approaches that people have taken to persist data across multiple pages within the same HTTP context (which essentially is one round-trip to the server). One person encouraged the practice of declaring public dictionaries that are persisted in similar fashion as the code above, others sanctioned the use of server side (session) cookies and some went as far as defining hidden field values and accessing them through the HttpValueCollection exposed by HttpRequest.Form on the target page.

I decided to take some time to go through all of the findings that I had come across and determine whether or not they would be of use. I've provided a brief summary of what I discovered as I went through each of the three different approaches that I found followed by my solution.

Declaring Public Properties

Declaring a public dictionary of values that I wanted to pass from the source page to the target page seemed to make sense, but I was still reluctant about exposing a public property in my Page class, especially when the only purpose that it would serve would be to pass this dictionary across connecting pages on the server. Additionally, this would create a strong coupling between pages that are involved in the transfer, which is something I really wanted to avoid. As an example, suppose I had three pages, A, B and C, each containing a Dictionary typed public property named Values, that were transferred to page Z. The code would look something like this:

Dictionary<int, string> valueDictionary;

if ( Context.Handler.GetType().Name.ToLower().Equals( "a_aspx" ) ) {
  valueDictionary = ( ( A ) Context.Handler ).Values;
} else if
( Context.Handler.GetType().Name.ToLower().Equals( "b_aspx" ) ) {
  valueDictionary = ( ( B ) Context.Handler ).Values;
} else if
( Context.Handler.GetType().Name.ToLower().Equals( "c_aspx" ) ) {
  valueDictionary = ( ( C ) Context.Handler ).Values;
} else
{
  // they shouldn't be here. redirect them somewhere else.

}

// do something with valueDictionary

This kind of coupling would influence a required code change in Z if any of the following scenarios were to happen:

  1. I decided that I wanted to transfer page D to Z.
  2. I no longer wanted one or more of the pages to be transferred to Z.
  3. One of the pages is no longer needed and is deleted from the web project.

Sessions

We have a policy that prohibits the use of sessions, so that was not something that I could use. Even if I could, I wouldn't unless it was as a last resort. Sessions, especially on a high-traffic site, can be expensive and should generally be avoided. They are also pretty much useless in a web farm because there isn't a clean way to persist them across multiple server instances in the cluster unless you have a centralized session server.

Hidden Form Fields

The HttpValueCollection exposed by HttpRequest.Form provides access to a collection of key-value pairs in which the identities of the controls (from Control.ClientID) on the source page are contained. In the case of passing data by way of hidden fields comes the requirement that you be able to retrieve a reference to the control on the server. The best way to do this is to retrieve a IHttpHandler based reference to the source page, casting it to the appropriate page type, invoking the FindControl() method on the source page instance and retrieving the control by the identifier defined on the web form and accessing its value. This presents two distinct problems. First, by doing this, you're creating the same strong coupling dependency as you would by declaring public properties. In addition to that, you will have to ensure that each page that is transferred to this target page has a control with the given ID of choice. Secondly, you're breaking the rules of encapsulation by using FindControl(). This, in the eyes of many, is not recommended practice. Let's not forget the potential performance hit that you could take by forcing a traversal of the control hierarchy.

Solution

I was a little bit frustrated at this point. The first option seemed like the only reasonable option, but intuition pushed me towards the HttpContext class in the .NET Framework Class Library documentation. I figured that since I was operating within a HttpContext that the class might expose a collection of some sort that would allow me to define values that would persist throughout the HTTP request. I stumbled across the Items property which immediately piqued my interest. You couldn't go wrong with a description that says, "Gets a key/value collection that can be used to organize and share data between an IHttpModule interface and an IHttpHandler interface during an HTTP request" could you? A fury of finger movement upon my keyboard resulted in the following code:

public partial class MyPage Page {

  protected override void OnLoad( EventArgs e ) {

    Context.Items[ "SomeKey" ] = 5; 
    Server.Transfer( "target.aspx",
true );

  }

}

public partial class TargetPage Page {

  protected override void OnLoad( EventArgs e ) {

    if ( Context.Items[ "SomeKey" ] == null ) {
      // redirect
    }

    // use value of Context.Items[ "SomeKey" ]

  }

}

Mission accomplished. With this approach, the only thing that would need to be done in order to transfer a page to TargetPage would be to define the appropriate keys in the key/value collection exposed by Content.Items before initiating the transfer.