Whenever I introduce the new cross-page posting features in ASP.NET 2.0 to students, one of the first questions that usually comes up is whether it is possible to post multiple pages to a single target page. A common scenario might be that you have several ways of collecting information from a user, but one final register / checkout / complete transaction page that should be shared by all source pages.
This is certainly easy enough to set up, the tricky part comes in accessing the previous page's type in the target page to extract the form values. For example, here's a sample source page that collects a client's name, age, and marital status. When submitted it will issue the POST request to Target.aspx (by setting the action of the form to that page) since we have specified a PostBackUrl property on the button that submits the page:
Enter your name: <asp:TextBox ID="_nameTextBox" runat="server" /><br />
Enter your age: <asp:TextBox ID="_ageTextBox" runat="server" /><br />
<asp:CheckBox ID="_marriedCheckBox" runat="server" Text="Married?" /><br />
<asp:Button ID="_nextPageButton" runat="server" Text="Next page" PostBackUrl="~/TargetPage.aspx" />
To make the values on this page accessible to the target page, it is common practice to define public properties in the code-behind file for the first page that return the values of the controls that will have already been populated from the POST body during the cross-page POST (ASP.NET evaluates the POST body using a new instance of the source page and executes its page lifecycle up to and including PreRender).
public partial class SourcePage1 : System.Web.UI.Page
{
public string Name
{
get { return _nameTextBox.Text; }
}
public int Age
{
get { return int.Parse(_ageTextBox.Text); }
}
public bool Married
{
get { return _marriedCheckBox.Checked; }
}
}
This works great for the case of one source page, as the target page can now cast the PreviousPage property to SourcePage1 and extract the values - or even better, use the PreviousPageType directive and strongly type the PreviousPage class to be SourcePage1, removing the need to cast at all:
<%@ PreviousPageType VirtualPath="~/SourcePage1.aspx" %>
And in the code behind file (assuming the presence of a Label with an ID of _messageLabel):
if (PreviousPage != null)
{
_messageLabel.Text = string.Format("<h3>Hello there {0}, you are {1} years old and {2} married!</h3>",
PreviousPage.Name, PreviousPage.Age, PreviousPage.Married ? "" : "not");
}
Now, if we introduce a second source page, also with the ability to collect the name, age, and marital status of the client, we run into a problem because each page is a distinct type with its own VirtualPath and the target page will somehow have to distinguish between a post from page1 and one from page2. One way to solve this is to implement a common interface in each source page's base class, so that the target page assumes only that the posting page implements a particular interface, and is not necessarily of one specific type or another. For example, we could write the IPersonInfo interface to model our cross page POST data as follows:
public interface IPersonInfo
{
string Name { get; }
int Age { get; }
bool Married { get; }
}
In each of the source pages, we then implement the IPersonInfo on the code-behind base class, and our target page can now write:
IPersonInfo pi = PreviousPage as IPersonInfo;
if (pi != null)
{
_messageLabel.Text = string.Format("<h3>Hello there {0}, you are {1} years old and {2} married!</h3>",
pi.Name, pi.Age, pi.Married ? "" : "not");
}
It would be even better if we could use the PreviousPageType directive to strongly type the PreviousPage property to the IPersonInfo interface, using the TypeName attribute as follows:
<%@ PreviousPageType TypeName="IPersonInfo" %> <!-- NOTE does not work -->
Unfortunately, the TypeName attribute of the PreviousPageType directive requires that the specified type inherit from System.Web.UI.Page (which seems too constraining if you ask me). You can introduce a work-around to get the strong typing by defining an abstract base class that implements the interface (or just defines abstract methods directly) and inherits from Page, as follows:
public abstract class PersonInfoPage : Page, IPersonInfo
{
public abstract string Name { get; }
public abstract int Age { get; }
public abstract bool Married { get; }
}
However this requires that each of the source pages you author change their base class from Page to this new PersonInfoPage base, which may be asking too much if you already of a common Page base class you are using in your system. Here's an example of a code behind class for a source page using this new base class:
public partial class SourcePage1 : PersonInfoPage
{
public override string Name
{
get { return _nameTextBox.Text; }
}
public override int Age
{
get { return int.Parse(_ageTextBox.Text); }
}
public override bool Married
{
get { return _marriedCheckBox.Checked; }
}
}
and the code in the target page can now be written using the strongly typed PreviousPage property as follows:
<%@ PreviousPageType TypeName="PersonInfoPage" %>
and the corresponding code behind:
if (PreviousPage != null)
{
_messageLabel.Text = string.Format("<h3>Hello there {0}, you are {1} years old and {2} married!</h3>",
PreviousPage.Name, PreviousPage.Age, PreviousPage.Married ? "" : "not");
}
After looking at all of the options, I would be inclined to use the interface-based technique, and just not bother with the PreviousPageType strong typing. You already have to check to see whether the PreviousPage property is null or not, and casting it to the interface using the 'as' operator in C# is about the same amount of work as checking for null. I was really hoping that I could specify an interface in the TypeName property, but since ASP.NET checks for a type that inherits from Page, that just won't work. It is nice to have the ability to post between pages again (this was not really possible in ASP.NET 1.1), and combined with a shared interface, it's another useful state propagation technique to keep in your toolbox.
Posted
Oct 14 2005, 08:37 AM
by
fritz-onion