|
|||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionWhere do you store per-client state in a Web application? This question is at the root of many heated debates over how to best design Web applications. The disconnected nature of HTTP means that there is no "natural" way to keep state on behalf of individual clients, but that certainly hasn't stopped developers from finding ways of doing it. Today there are many choices for keeping client-specific state in an ASP.NET Web application, including Session state, View state, cookies, the ASP.NET 2.0 does not offer a penultimate solution for storing client state, but it does introduce three new features that should be considered any time you are looking for a place to store state on behalf of individual users. The first feature, cross-page posting, is actually the resurrection of a common technique used in classic ASP and other Web development environments for propagating state between two pages. This technique was not available in ASP.NET 1.1 because of the way POST requests were parsed and processed by individual pages, but has now been reincorporated into ASP.NET in such a way that it works in conjunction with server-side controls and other ASP.NET features. The second feature is a trio of new server-side controls that implement the common technique of showing and hiding portions of a page as the user interacts with it. The Wizard control gives developers a simple way to construct a multi-step user interface on a single page, and the The last feature, Cross-Page PostingThis version of ASP.NET reintroduces the ability to perform cross-page posts. Once a common practice in classic ASP applications, ASP.NET 1.x made it nearly impossible to use this technique for state propagation because of server-side forms and view state. This section covers the fundamentals of cross-page posting in general, and then looks at the support added in ASP.NET 2.0. FundamentalsOne common mechanism for sending state from one page to another in Web applications is to use a form with input elements whose action attribute is set to the URL or the target page. The values of the source page's input elements are passed as name-value pairs to the target page in the body of the POST request (or in the query string if the form's method attribute is set to GET), at which point the target page has access to the values. Listings 4-1 and 4-2 show a pair of sample pages that request a user's name, age, and marital status, and display a customized message on the target page. Listing 4-1: sourceform.aspx—sample form using a cross-page post<!-- sourceform.aspx -->
<%@ Page language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Source Form</title>
</head>
<body>
<form action="target.aspx" method="post">
Enter your name:
<input name="_nameTextBox" type="text" id="_nameTextBox" />
<br />
Enter your age:
<input name="_ageTextBox" type="text" id="_ageTextBox" /><br />
<input id="_marriedCheckBox" type="checkbox"
name="_marriedCheckBox" />
<label for="_marriedCheckBox">Married?</label><br />
<input type="submit" name="_nextPageButton" value="Next page" />
</form>
</body>
</html>
Listing 4-2: target.aspx—sample target page for a cross-page post<!-- target.aspx -->
<%@ Page language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Target Page</title>
</head>
<body>
<h3>
Hello there
<%= Request.Form["_nameTextBox"] %>, you are
<%= Request.Form["_ageTextBox"] %> years old and are
<%= (Request.Form["_marriedCheckBox"] == "on") ? "" : "not " %>
married!
</h3>
</body>
</html>
This example works fine in both ASP.NET 1.1 and 2.0, and with a few simple modifications would even work in classic ASP. This technique is rarely used in ASP.NET, however, because the form on the source page cannot be marked with runat="server"; thus, many of the advantages of ASP.NET, including server-side controls, cannot be used. ASP.NET builds much of its server-side control infrastructure on the assumption that pages with forms will generate POST requests back to the same page. In fact, if you try and change the action attribute of a form that is also marked with In the 2.0 release of ASP.NET, cross-page posting is now supported again, even if you are using server-side controls and all of the other ASP.NET features. The usage model is a bit different from the one shown in Listings 4-1 and 4-2, but in the end it achieves the desired goal of issuing a POST request from one page to another, and allowing the secondary page to harvest the contents from the POST body and process them as it desires. To initiate a cross-page post, you use the new Listing 4-3: SourcePage1.aspx—using cross-page posting support in ASP.NET 2.0<!-- SourcePage1.aspx -->
Once you have set up the source page to post to the target page, the next step is to build the target page to use the values passed by the source page. Because ASP.NET uses POST data to manage the state of its server-side controls, it would not have been sufficient to expect the target page to pull name/value pairs from the POST body, since many of those values (like Listing 4-4: TargetPage.aspx—target page of a cross-page post<!-- TargetPage.aspx -->
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="TargetPage.aspx.cs"
Inherits="TargetPage" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Target Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="_messageLabel" />
</div>
</form>
</body>
</html>
Listing 4-5: TargetPage.aspx.cs—target page of a cross-page post codebehind// TargetPage.aspx.cs
public partial class TargetPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null)
{
TextBox nameTextBox =
The technique shown in Listing 4-5 for retrieving values from the previous page is somewhat fragile, as it relies on the identifiers of controls on the previous page as well as their hierarchical placement, which could easily be changed. A better approach is to expose any data from the previous page to the target page by writing public property accessors in the code-behind, as shown in Listing 4-6. Listing 4-6: SourcePage1.aspx.cs—exposing public properties to the target page// File: SourcePage1.aspx.cs
public partial class SourcePage1 : Page
{
public string Name
{
get { return _nameTextBox.Text; }
}
public int Age
{
get { return int.Parse(_ageTextBox.Text); }
}
public bool Married
{
get { return _marriedCheckBox.Checked; }
}
}
Once the public properties are defined, the target page can cast the Listing 4-7: TargetPage.aspx.cs—target page using properties to retrieve source page values// TargetPage.aspx.cs
public partial class TargetPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
SourcePage1 sp = PreviousPage as SourcePage1;
if (sp != null)
{
_messageLabel.Text = string.Format(
"<h3>Hello there {0}, you are {1} years" +
" old and {2} married!</h3>",
sp.Name, sp.Age, sp.Married ? "" : "not");
}
}
}
Because this last scenario is likely to be the most common use of cross-page posting--that is, a specific source page exposes properties to be consumed by a specific target page--there is a directive called Listing 4-8: TargetPage.aspx with strongly typed previous page<!-- TargetPage.aspx -->
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="TargetPage.aspx.cs"
Inherits="TargetPage" %>
<%@ PreviousPageType VirtualPath="~/SourcePage1.aspx" %>
...
Listing 4-9: TargetPage.aspx.cs—using strongly typed PreviousPage accessor// TargetPage.aspx.cs
public partial class TargetPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
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");
}
}
}
ImplementationWhen you set the Once the POST request is issued to the target page, the path of the previous page is read and decrypted from the The first time you do access the It is important to keep in mind that the preceding page will be created and asked to run through Listing 4-10: Checking for IsCrossPagePostBack before running code with side effectspublic partial class SourcePage1 : Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsCrossPagePostBack)
{
WriteDataToLogFile();
}
}
}
CaveatsWhile this new support for cross-page posting is a welcome addition to ASP.NET, it does have some potential drawbacks you should be aware of before you elect to use it. The first thing to keep in mind is that the entire contents of the source page is going to be posted to the target page. This includes the entire view state field and all input elements on the page. If you are using cross-page posting to send the value of a pair of Validation is another potential trouble area with cross-page posting. If you are using validation controls in the client page to validate user input prior to the cross-page post, you should be aware that server-side validation will not take place until you access the A common scenario where this may occur is with custom validation controls. If you have set up a custom validation control with a server-side handler for the Listing 4-11: Source page with custom validator<!-- SourcePageWithValidation.aspx -->
<%@ Page Language="C#" %>
<script runat="server">
public int Prime
{
get { return int.Parse(_primeNumberTextBox.Text); }
}
private bool IsPrime(int num)
{
// implementation omitted
}
protected void _primeValidator_ServerValidate(object source,
ServerValidateEventArgs args)
{
args.IsValid = IsPrime(Prime);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Source page with validation</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Enter your favorite prime number:
<asp:TextBox ID="_primeNumberTextBox" runat="server" />
<asp:CustomValidator ID="_primeValidator" runat="server"
ErrorMessage="Please enter a prime number"
OnServerValidate="_primeValidator_ServerValidate">
**</asp:CustomValidator><br />
<asp:Button ID="_nextPageButton" runat="server"
Text="Next page"
PostBackUrl="~/TargetPageWithValidation.aspx"
/><br />
<br />
<asp:ValidationSummary ID="_validationSummary"
runat="server" />
</div>
</form>
</body>
</html>
Listing 4-12: Target page checking for validation<!-- TargetPageWithValidation.aspx -->
<%@ Page Language="C#" %>
<%@ PreviousPageType VirtualPath="~/SourcePageWithValidation.aspx" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null && PreviousPage.IsValid)
{
_messageLabel.Text = "Thanks for choosing the prime number " +
PreviousPage.Prime.ToString();
}
else
{
_messageLabel.Text = "Error in entering data";
_messageLabel.ForeColor = Color.Red;
_previousPageLink.Visible = true;
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Target Page With validation</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="_messageLabel" /><br />
<asp:HyperLink runat="server" ID="_previousPageLink"
NavigateUrl="~/SourcePageWithValidation.aspx"
visible="false">
Return to data entry page...</asp:HyperLink>
</div>
</form>
</body>
</html>
Finally, it is important to be aware that the entire cross-page posting mechanism relies on JavaScript to work properly, so if the client either doesn't support or has disabled JavaScript, your source pages will simply post back to themselves as the action on the form will not be changed on the client in response to the button press. Multi-Source Cross-Page PostingCross-page posting can also be used to create a single target page that can be posted to by multiple source pages. Such a scenario may be useful if you have a site that provides several different ways of collecting information from the user but one centralized page for processing it. If we try and extend our earlier example by introducing 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 Listing 4-13: IPersonInfo interface definitionpublic interface IPersonInfo
{
string Name { get; }
int Age { get; }
bool Married { get; }
}
In each of the source pages, we then implement the Listing 4-14: Generic target page using interface for previous pageIPersonInfo 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 Listing 4-15: Abstract base class inheriting from Page for strong typing with PreviousPageTypepublic abstract class PersonInfoPage : Page, IPersonInfo
{
public abstract string Name { get; }
public abstract int Age { get; }
public abstract bool Married { get; }
}
This technique then requires that each of the source pages you author change their base class from Listing 4-16: Codebehind class for a sample source page inheriting from PersonInfoPagepublic 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; }
}
}
Once all source pages are derived from our Listing 4-17: Strongly typed target page using TypeName<%@ PreviousPageType TypeName="PersonInfoPage" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
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");
}
}
</script>
<!-- ... -->
The effort required to get the strong typing to work for multiple source pages hardly seems worth it in the end. You already have to check to see whether the Wizard and MultiView ControlsThis section covers a new collection of controls in ASP.NET 2.0 that simplify the process of collecting data from the user by using a sequence of steps that are all on a single page. The controls include the new Wizard control as well as the Same Page State ManagementAnother alternative to storing per-client state across requests is to have the user post back to the same page instead of navigating from one page to another. You can achieve the same sequential set of steps for data collection that you can using multiple pages with this technique by toggling the display of various panels, showing only one of several panels at any given time based on the user's progress. Instead of placing input controls on separate pages, you place them all on the same page, but separate them with Figure 4-1: Multipanel page This technique works well because all of the state for all the controls is kept on a single page, and even when the controls in a particular panel are not displayed, their state is maintained in view state, so programmatically it is just like working with one giant form. It is also quite efficient, since the contents of invisible panels are not even sent to the client browser; just the state of the controls is sent through view state. Wizard ControlIn the 2.0 release of ASP.NET this technique has been standardized in the form of the Wizard control. Instead of laying out the Listing 4-18: Sample Wizard control with three steps<asp:Wizard ID="_infoWizard" runat="server" ActiveStepIndex="0"
OnFinishButtonClick="_infoWizard_FinishButtonClick"
DisplaySideBar="False">
<WizardSteps>
<asp:WizardStep ID="_step1" runat="server" Title="Name">
<table>
<tr>
<td>First name:</td>
<td><asp:TextBox ID="_firstNameTextBox" runat="server" /></td>
</tr>
<tr>
<td>Last name:</td>
<td><asp:TextBox ID="_lastNameTextBox" runat="server" /></td>
</tr>
</table>
</asp:WizardStep>
<asp:WizardStep ID="_step2" runat="server" Title="Address">
<table>
<tr>
<td>Street:</td>
<td><asp:TextBox ID="_streetTextBox" runat="server" /></td>
</tr>
<tr>
<td>City:</td>
<td><asp:TextBox ID="_cityTextBox" runat="server" /></td>
</tr>
<tr>
<td>State/Province:</td>
<td><asp:TextBox ID="_stateTextBox" runat="server" /></td>
</tr>
</table>
</asp:WizardStep>
<asp:WizardStep ID="_step3" runat="server" Title="Preferences">
<table>
<tr>
<td>Favorite color:</td>
<td><asp:TextBox ID="_colorTextBox" runat="server" /></td>
</tr>
<tr>
<td>Favorite number:</td>
<td><asp:TextBox ID="_numberTextBox" runat="server" /></td>
</tr>
</table>
</asp:WizardStep>
</WizardSteps>
</asp:Wizard>
<asp:Label ID="_summaryLabel" runat="server" />
Like most controls in ASP.NET, both the appearance and behavior of the The advantage of working with the
Figure 4-2: Wizard control, unadorned, and with SideBar and formatting Listing 4-19: Handler for the Wizard's Finish button click eventprotected void _infoWizard_FinishButtonClick(object sender,
WizardNavigationEventArgs e)
{
_summaryLabel.Text = string.Format(
"<h2>Thank you for submitting your information!</h2>" +
"Name: {0} {1}<br /><br/>Address: {2}<br/>" +
"{3}, {4}<br /><br />Prefs: {5} {6}<br />",
_firstNameTextBox.Text,
_lastNameTextBox.Text, _streetTextBox.Text,
_cityTextBox.Text, _stateTextBox.Text,
_colorTextBox.Text, _numberTextBox.Text);
_infoWizard.Visible = false;
}
MultiView and View ControlsIf you want the ability to toggle among multiple panels on a page but find the Listing 4-20: MultiView with LinkButtons <asp:LinkButton ID="_view1LinkButton" runat="server"
OnClick="_view1LinkButton_Click">
View 1</asp:LinkButton>
<asp:LinkButton ID="_view2LinkButton" runat="server"
OnClick="_view2LinkButton_Click">
View 2</asp:LinkButton>
<asp:LinkButton ID="_view3LinkButton" runat="server"
OnClick="_view3LinkButton_Click">
View 3</asp:LinkButton><br />
<asp:MultiView ID="_infoMultiView" runat="server"
ActiveViewIndex="0">
<asp:View ID="_view1" runat="server">
<table>
<tr>
<td>First name:</td>
<td><asp:TextBox ID="_firstNameTextBox"
runat="server" /></td>
</tr>
<tr>
<td>Last name:</td>
<td><asp:TextBox ID="_lastNameTextBox"
runat="server" /></td>
</tr>
</table>
</asp:View>
<asp:View ID="_view2" runat="server">
<table>
<tr>
<td>Street:</td>
<td><asp:TextBox ID="_streetTextBox"
runat="server" /></td>
</tr>
<tr>
<td>City:</td>
<td><asp:TextBox ID="_cityTextBox"
runat="server" /></td>
</tr>
<tr>
<td>State/Province:</td>
<td><asp:TextBox ID="_stateTextBox"
runat="server" /></td>
</tr>
</table>
</asp:View>
<asp:View ID="_view3" runat="server">
<table>
<tr>
<td>Favorite color:</td>
<td><asp:TextBox ID="_colorTextBox"
runat="server" /></td>
</tr>
<tr>
<td>Favorite number:</td>
<td><asp:TextBox ID="_numberTextBox"
runat="server" /></td>
</tr>
</table>
</asp:View>
</asp:MultiView>
Listing 4-21: LinkButton handlers for MultiView switchingprotected void _view1LinkButton_Click(object sender, EventArgs e)
{
_infoMultiView.ActiveViewIndex = 0;
}
protected void _view2LinkButton_Click(object sender, EventArgs e)
{
_infoMultiView.ActiveViewIndex = 1;
}
protected void _view3LinkButton_Click(object sender, EventArgs e)
{
_infoMultiView.ActiveViewIndex = 2;
}
Profile
FundamentalsThe first step in using | ||||||||||||||||||||||||||||||||||||||