Posting across pages

This article was originally published in VSJ, which is now part of Developer Fusion.
The ASP.NET framework takes much of the hard work out of developing web pages that receive data through a forms post. In ASP.NET version 1.0/1.1 you were limited to posting data back to the same page that created the form in the first place (posting to self), but for many workflows which require multiple forms, such as checking out an order or filling in a survey, this can be very clumsy.

Figure 1
Figure 1: Checkout process using Server.Transfer

Figure 1 shows part of a checkout process where the user fills in some address details, and then a payment form is displayed. The sequence in which these forms are displayed is always the same, so the ASP server must process the Address form even though the page it generates will not be displayed. The address form validation logic is all on the client side, and when it is loaded it will always simply redirect (or do a Server.Transfer) to the payment form. Furthermore, the address information can only be communicated from one form to the next using mechanisms such as sessionstate or the query string.

ASP.NET 2.0 innovations

ASP.NET 2.0 introduces the new PreviousPage property to the Page class. This references an object of type Page representing previous page wherever possible, for example when it was invoked using a Server.Transfer. If the current page doesn’t know anything about the previous page (e.g. if the URL was typed into the browser, the user clicked a normal hyperlink, or Response.Redirect was used) then the PreviousPage property will be null.

The PreviousPage property can be used to pick up data from the previous page by using the FindControls method to get data from the controls of that page, instead of all data being transferred between pages in the sessionstate.

To illustrate this, let’s look at an example. The Address.aspx page shown in Figure 1 contains the following control definitions:

<asp:TextBox ID=”AddressTextBox”
	runat=”server”
	TextMode=”MultiLine” / >
<asp:Button ID=”ButtonServerTransfer”
	runat=”server”
	Text=”ServerTransfer”
	OnClick=
		”ButtonServerTransfer_Click” />
The code-behind file contains the following button handler:
protected void
	ButtonServerTransfer_Click(
	object sender , EventArgs e )
{
	Server.Transfer(“~/Payment.aspx”);
}
To retrieve the contents of the text box, the Payment.aspx page implementation could use the following code:
TextBox textBox =
	(TextBox)PreviousPage.FindControl(
	 “AddressTextBox” );
string addressText = textBox.Text;
Notice that to use this method, we have to be sure that Payment.aspx page was invoked using Server.Transfer by Address.aspx, or at least from a page which contains the relevant controls. To add security to this code, we should check the PreviousPage.AppRelativeVirtualPath property to ensure that it is set to “~/Address.apsx”, and we should check that the return from FindControls is not null, and in fact that it is of the expected type.

Where more than one page may originate the post, the PreviousPage.AppRelativeVirtualPath property can be used to select the appropriate behaviour.

The PreviousPage property is only valid on the initial invocation of Payment.aspx. If Payment.aspx posts back to itself, the PreviousPage property will, by definition, be null during the subsequent executions of the page.

This technique is described as “loose binding”. It requires the developer of one page to intimately understand the structure of the other and probe it at runtime, which is a disadvantage if the pages are developed by different people, and can cause problems when page implementation is changed.

Binding by type

An alternative to loose binding is to convert the type of the type of the PreviousPage reference to the type we expect – the type of the class used to display the previous page. The class can then expose public properties (or methods), forming a clearly defined interface between the pages rather than one which is highly implementation dependent.

To continue the example used above, we could add the following code to the Address.aspx code-behind:

public string AddressText
{
	get{ return AddressTextBox.Text; }
}
We also need to add a Reference directive to the Payment.aspx file to bring the relevant class (in this case the Address class that implements the Address.aspx page into scope)
<%@ Reference Page=”~/Address.aspx” %>
The Payment.aspx page can now retrieve the address text as follows:
if ( PreviousPage is Address )
{
	Address addressPage =
		(Address)PreviousPage;
	string address =
		addressPage.AddressText;
}
This technique for retrieving information from within another page object is type safe, and it is easy to detect whether the object referenced by PreviousPage is of the correct type, or indeed to use the type of the PreviousPage object to select the correct behaviour where multiple pages may originate the post.

If you prefer not to use a Reference directive, you can still refer to the Address class using its mangled internal name ASP.Address_aspx, however the nature of the name mangling is an internal feature of ASP.NET that is theoretically subject to change. Furthermore, Visual Studio will not be able to provide intellisense help on the type without a Reference directive. Using the class through a Reference directive is the recommended approach.

Cross-page posting

Up until now we have still been using the model shown in Figure 1, albeit with some additional help from ASP.NET 2.0 to handle the passing of data from one form to the next. Cross page posting is another new feature in ASP.NET 2.0, which enables us to miss out the Server.Transfer stage and post directly to the next page, as shown in Figure 2.

Figure 2
Figure 2: Checkout process using cross-page posting

As you can see, the Address.aspx page is no longer invoked a second time, instead we skip directly to making a post to Payment.aspx.

This is accomplished by setting the PostBackURL property on the submit button on Address.aspx to point it Payment.aspx:

<asp:Button ID=”ButtonCrossPage”
	runat=”server”
	Text=”CrossPage”
	PostBackUrl=”~/Payment.aspx” />
Pressing this button will take us directly to the page Payment.aspx, which can now access the controls (using FindControls) or public properties/methods of the Address class exactly as described above. If the page needs to identify whether it was invoked using Server.Transfer or with a cross-page post, it can use the PreviousPage.IsCrossPagePostBack property.

In fact, functionally the two approaches are not as different as they at first appear. The class representing Address.aspx is still loaded, and its Page_Load function is still executed. From a server point of view, the only stage missed out by using cross-page post is the invocation of the button click handler. In fact, you can obtain a small improvement in performance by ensuring that the Page_Load method of Address.aspx checks IsCrossPagePostback and doesn’t do any unnecessary work if it is set, but this is unlikely to yield a significant improvement over the judicious use of IsPostBack to qualify execution.

One key benefit of using cross-page post is that the browser knows that the page has changed, and can update the address bar, browser history, and actions of the forward, back and refresh buttons accordingly. It is common for applications that use Server.Transfer to experience consistency problems when such browser functions are used, and cross-page posting is a solution to this problem.

The listings below bring together all the techniques described above for passing data from one page to the next.

Address.aspx
<%@ Page Language=”C#” AutoEventWireup=”true”
	CodeFile=”Address.aspx.cs” Inherits=”Address” %>
<html>
	<body>
		<form id=”AddressForm” runat=”server”>
				Address:<br />
			<asp:TextBox ID=”AddressTextBox” runat=”server”
				Height=”66px” TextMode=”MultiLine”
				Width=”198px”></asp:TextBox><br />
			<asp:Button ID=”ButtonCrossPage” runat=”server”
				Text=”CrossPage” PostBackUrl=”~/Payment.aspx” />
			<asp:Button ID=”ButtonServerTransfer”
				runat=”server” Text=”ServerTransfer”
				OnClick=”ButtonServerTransfer_Click” />
		</form>
	</body>
</html>
Address.aspx.cs
using System;
public partial class Address : System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{
	}
	protected void ButtonServerTransfer_Click(
		object sender, EventArgs e)
	{
		Server.Transfer( “~/Payment.aspx” );
	}
	public string AddressText
	{
		get
		{
			return AddressTextBox.Text;
		}
	}
}
Payment.aspx
<%@ Page Language=”C#” AutoEventWireup=”true”
	CodeFile=”Payment.aspx.cs” Inherits=”Payment” %>
<%@ Reference Page=”~/Address.aspx” %>
<html>
	<body>
		<form id=”form1” runat=”server”>
			FindControl:
			<asp:TextBox ID=”TextBoxFindControl” runat=”server”
				ReadOnly=”true”/> <br />
			Use Class :
			<asp:TextBox ID=”TextBoxUseClass” runat=”server”
				ReadOnly=”true”/>
		</form>
	</body>
</html>
Payment.aspx.cs
using System;
using System.Web.UI.WebControls;
public partial class Payment:System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{
		if((PreviousPage == null) ||
			(PreviousPage.AppRelativeVirtualPath
			!= “~/Address.aspx” ) ||
			!(PreviousPage is Address))
		{
			throw new Exception(
				“Must be invoked by Address.aspx” );
		}
		TextBox addressTextBox =
			(TextBox)PreviousPage.FindControl(
				“AddressTextBox” );
		TextBoxFindControl.Text = addressTextBox.Text;	
		Address addressPage = (Address)PreviousPage;
		TextBoxUseClass.Text = addressPage.AddressText;
	}
}

Strong type binding

If an ASP page will be called from one page, and one page only, it is possible to use a PreviousPageType directive so that at design time the compiler will recognise the PreviousPage property through its strong type.

In the example above, the PreviousPageType directive would be as follows:

<%@ PreviousPageType
	VirtualPath=”~/Address.aspx” %>
When using this directive, you no longer need to check or cast the type of the PreviousPage property, and you can access public members of the Address class directly in the code for Payment.aspx:
string addressText =
	PreviousPage.AddressText;
This has the advantage of requiring less code, but the disadvantage that you can now only use cross-page post or Server.Transfer from a single page – there is no longer any way to bind to different source page types and define behaviour appropriate to them at runtime.

Time to forget?

We have already mentioned that using cross-page posting gets rid of the browser history consistency problems associated with using Server.Transfer, and offers scope for improved efficiency on the server site.

Does this mean that we should now forget about Server.Transfer? There are actually many situations in which it will still be appropriate to use Server.Transfer rather than cross-page post:

  • Where you wish to deliberately mask the URL of steps in a process from the user so they always enter at the correct point
  • Where there is complex server side validation to carry out
  • Where the content of the form, or the session state, or both, will determine what the next page is at runtime
The MSDN help for ASP.NET has an excellent topic under the heading “Redirecting Users to Another Page”, which provides a summary of the different methods available, along with a table listing their different merits and capabilities.

Posting to existing forms

I have encountered a number of situations in which I wished to post to an existing form in an application other than the application I am currently developing. There are a number of examples of web sites which encourage the developer to interface with their site by means of a form post, possibly the best known being PayPal.

In Integrated eCommerce with PayPal (VSJ, March 2006) I described the form post interface to the PayPal site. Cross-page posting can be used to simplify this process. The fields necessary for PayPal can be created using HTML Hidden Input controls, and with a button to submit the form:

<%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”PayPalSingle.aspx.cs”
	Inherits=”PayPalSingle” %>
<html>
	<body>
		<form id=”PayPalSingleForm” runat=”server”>
			<input type=”hidden” name=”cmd” value=”_xclick” />
			<input type=”hidden” name=”business” value=”[email protected]” />
			<input type=”hidden” name=”currency_code” value=”GBP” />
			<input type=”hidden” name=”item_name” value=”<%# ItemName %>” />
			<input type=”hidden” name=”amount”
				value=”<%# ItemPrice.ToString(“0.00”) %>” />
			<asp:Button ID=”Buy” runat=”server” Text=”Buy now!”
				PostBackUrl=”https://www.paypal.com/cgi-bin/webscr” />
		</form>
	</body>
</html>

using System;
public partial class PayPalSingle : System.Web.UI.Page
{
	public string ItemName;
	public decimal ItemPrice;
	protected void Page_Load(object sender, EventArgs e)
	{
		ItemName = “Cool Item”;
		ItemPrice = (decimal)5.99;
		DataBind();
	}
}
If you need to use the cart upload (rather than single item) form of the PayPal interface, this can be accomplished by using your shopping cart as the DataSource for a repeater control. In this case, your cart will need to have an item number (starting and 1 and incrementing with no gaps) for each item to populate the item index portion of the tag name.

Pressing your own buttons…

Sometimes you need to automate the process of posting a form by triggering it from javascript or from an event generated client side. To post a form back to itself you can use the built in function to generate a script fragment that will trigger a postback:
string script = ClientScript.
	GetPostBackEventReference(Buy, “”);
Unfortunately, this always triggers post back to the original page, and ignores the PostBackUrl parameter value we set for the buy button.

Triggering a postback to a different page requires the use of PostBackOptions to specify the URL to post to:

PostBackOptions o = new
	PostBackOptions(Buy, “”,
	“https://www.paypal.com/cgi-bin/
	webscr”, true, true, false, true,
	false, “”);
string script = Page.ClientScript.
	GetPostBackEventReference( o );

Conclusions

In this article I set out to demonstrate some of the new features in ASP.NET 2.0 for transferring data between pages without using sessionstate, and also to show how these features can be used to post to existing forms.

A Visual Studio 2005 project containing the project and code files described in this article can be downloaded.


Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works as a consultant in the software industry, and can be contacted at [email protected].

You might also like...

Comments

About the author

Ian Stevenson United Kingdom

Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works a...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“My definition of an expert in any field is a person who knows enough about what's really going on to be scared.” - P. J. Plauger