Maintaining State in Cookies

Before Microsoft introduced the concept of Session variables, Web programmers had to use cookies to manage state. Because Session variables slow down the rate at which your server can serve pages and because browsers let users turn off (refuse) cookies, you may find yourself in a situation where you can't use them. Here's how to fulfill the form series requirements with cookies:

■ For each page, send a sequence cookie containing the page sequence that the browser will return during the next request to the site.

■ If the sequence cookie is blank, set it to the first page in the sequence and redirect to that page. That way, no matter which page in the sequence users request first, they'll always see the first page first.

■ If the sequence cookie is not blank and the request does not match the cookie, redirect to the page specified by the cookie rather than the page requested by the user. This prevents the user from seeing the pages out of sequence.

■ When the user has completed the last page, set the sequence cookie to done or to some other recognizable value. This prevents the user from revisiting the pages after completing the sequence.

You can write the logic into each page to manage the cookies, but placing the logic in a class makes the code much simpler. The class should handle creating and reading the cookies, comparing the current request to the expected page request (the next one in the series), and redirecting the user. The cookieSequence.cs file in the Chl7 folder of the CSharpASP application contains the definition for the CookieSequencer class.

You've already seen how to set and read cookies, so I won't spend much time there, but the Cookie-Sequencer class has a couple of interesting features. First, it has a custom constructor that overrides the default constructor inherited from Object. The CookieSequencer class contains a private array that holds the master definition of the sequence.

private String[] sequence = {"cookiesequencel.aspx", "cookiesequence2.aspx", "cookiesequence3.aspx", "cookiesequencedone.aspx"};

The class obtains a reference to the current HttpContext and uses that to set references to the ASP.NET Request and Response objects, which it needs to retrieve and set cookie values and write content to the browser:

// defined at class level private HttpRequest Request = null;

private HttpResponse Response = null;

// get the current context

HttpContext context = HttpContext.Current; Request = context.Request;

The class also retrieves the lowercase value of the Request.ServerVariables item called script_name and saves the name of the current script:

// get the current script name currentScriptName =

Request.ServerVariables["SCRIPT_NAME"].ToLower(); currentScriptName = currentScriptName.Substring (currentScriptName.LastIndexOf("/") + 1);

Note IE sends the script_name variable consistently. For some browsers, you must use the url variable instead, which contains the same value. You can test the value quickly using the showServerVariables.aspx file in the Chl7 folder of the CSharpASP application on

Next, the class attempts to retrieve the sequence cookie. If it's blank, the code creates the cookie and redirects to the first page in the sequence:

// get the sequence cookie, if any cookie = Request.Cookies["sequence"]; if (cookie == null) { // first visit cookie = new HttpCookie("sequence"); cookie.Value = sequence[0]; Response.Cookies.Add(cookie); validCookie = true;

// more code here

The class ensures that the cookie contains some valid value by checking the value against each item in the defined sequence. This isn't strictly necessary, because this code never executes unless the page creates a CookieSequencer object, but it doesn't take much time, and it ensures that test pages or pages removed from the sequence at some point don't affect the sequence itself. If the request matches any of the pages in the sequence, the loop sets the validCookie variable to true, sets a variable named currSequence to the machine index value, and exits the loop. // make sure you have a valid value for (int i = 0; i <= sequence.GetUpperBound(O); i++) { if (cookie.Value == sequence[i]) { validCookie = true; currSequence = i; break;

If the page request is valid (appears in the defined sequence), the code checks to make sure that the cookie value matches the page request. If so, the request and the cookie match, so you're done and the code exits, letting the page code execute. If not, the page request is invalid in some fashion; perhaps the user is requesting a page out of sequence or a programmer inadvertently created a Cookie-Sequencer object on a page not defined in the sequence. In that case, the method assumes the cookie is correct and redirects to the page defined by the cookie.

if (validCookie) {

// redirect to appropriate page Response.Redirect(cookie.Value, true);

// set the cookie to the first page cookie.Value = sequence[0]; currSequence = 1; // append the cookie Response.Cookies.Add(cookie); // redirect to first page Response.Redirect(sequence[0], true);

The result of this code is that any page that creates a CookieSequencer object becomes, correctly or incorrectly, part of the sequence and forces the user to see the next appropriate page in the sequence.

CookieSequencer has only two other methods. The setCookie method sets the value of the sequence cookie. The NextPage method increments the currSequence index variable and calls the setCookie method, thus forcing the next request to redirect to the next page in the sequence. If the currSequence index variable value equals or exceeds the number of items in the sequence, CookieSequencer assumes that the user has completed the sequence and redirects to the last page. Listing 17.1 contains the complete CookieSequencer class code.

Listing 17.1: The CookieSequencer Class Manages the Cookies Required to Force a User to Traverse a Specific Sequence of Pages (CookieSequence.aspx.cs)

using System; using System.Web;

namespace CSharpASP.ch17 { /// <summary>

/// Summary description for CookieSequencer. /// </summary>

public class CookieSequencer { private HttpRequest Request = null; private HttpResponse Response = null;

private String[] sequence = {"cookiesequencel.aspx", "cookiesequence2.aspx", "cookiesequence3.aspx", "cookiesequencedone.aspx"}; private int currSequence = 0; private String currentScriptName = ""; public CookieSequencer() {

// get the current context

HttpContext context = HttpContext.Current;

HttpCookie cookie = null;

bool validCookie = false;

Request = context.Request;

Response = context.Response;

// get the current script name currentScriptName =

Request.ServerVariables["SCRIPT_NAME"].ToLower(); currentScriptName = currentScriptName.Substring

(currentScriptName.LastIndexOf("/") + 1); // get the sequence cookie, if any cookie = Request.Cookies["sequence"]; if (cookie == null) { // first visit cookie = new HttpCookie("sequence"); cookie.Value = sequence[0]; Response.Cookies.Add(cookie); validCookie = true;

// make sure you have a valid value for (int i = 0; i <= sequence.GetUpperBound(0); i++) {

if (cookie.Value == sequence[i]) { validCookie = true; currSequence = i;

if (validCookie) {

// redirect to appropriate page Response.Redirect(cookie.Value, true);

// set the cookie to the first page cookie.Value = sequence[0]; currSequence = 1; // append the cookie Response.Cookies.Add(cookie); // redirect to first page Response.Redirect(sequence[0], true) ;

public int CurrentSequence { get {

return(currSequence);

private void setCookie(int sequenceIndex) { String s="";

s = sequence[sequenceIndex]; Response.Cookies["sequence"].Value = s;

public void NextPage() {

if (currSequence + 1 < sequence.GetLength(0)) { currSequence++; setCookie(currSequence);

Response.Redirect(sequence[currSequence]);

Response.Redirect(sequence[sequence.GetUpperBound(0)]);

public void showCookies() {

foreach(String aKey in Request.Cookies.Keys) {

Response.Write(Request.Cookies[aKey].Name + "=" + Request.Cookies[aKey].Value + "<br>");

Four Web Forms work with this class—three in the sequence, and one to display the results when the user finishes the sequence. Because all three Web Forms in the sequence contain similar code, I'll show only one here. Each Web Form requests a single bit of information. The first page requests a last name; the second, a first name; and the third, a U.S.-formatted telephone number. Each page uses a TextBox for the user input, and three Validator controls: one RequiredFieldValidator to ensure that the user enters something in the text field, one RegularExpressionValidator to check the input, and one ValidationSummary, which displays the validation feedback.

Each form also contains a button to post the form. Each Web Form instantiates a CookieSequencer instance. As you've seen, simply creating the object ensures that the user can see the page only in the correct sequence (see Listing 17.2).

Listing 17.2: Code for the CookieSequencel.aspx Web Form (CookieSequence1.aspx.cs)

private void Page Load(object sender, System.EventArgs e) { CookieSequencer cookieSequencer = new CookieSequencer(); // NOTE, you'll never process this page if the

// cookie is not valid if (IsPostBack) { Page.Validate(); if (Page.IsValid) {

// save the last name

HttpCookie ln = new HttpCookie("lastname"); ln.Value = txtLastName.Text; Response.Cookies.Add(ln); cookieSequencer.NextPage();

Here's how to test the sequence. First, request the CookieSequence1.aspx file into your browser and enter a last name (see Figure 17.1).

Figure 17.1: The first page of the CookieSequence Web Form sequence

Click the button or press Enter to submit the form.

Tip Whenever you have a page with a form containing a single text field, with or without a submit button, IE submits the form automatically when you press Enter. You can leave the field without submitting it by pressing the Tab key.

The Web Form posts back to itself and validates the input. When the input is valid, the code calls the CookieSequencer.NextPage method, which redirects the user to the next page in the sequence.

After entering a last name, you'll see the CookieSequence2.aspx page. Move back up to your browser's address field and change CookieSequence2.aspx to CookieSequence1.aspx. The server accepts the request. The CookieSequence2.aspx page creates a CookieSequence object, which compares the request to the value of the sequence cookie. Because they don't match, it immediately redirects the browser back to the CookieSequence2.aspx page.

The same thing happens if you request the CookieSequence3.aspx page. When you reach the end of the sequence, any request for any page in the sequence redirects you to the CookieSequenceDone.aspx page. After submitting valid data for a page in the sequence, you can still browse back to that page using the Back button, (because the browser caches the page), but any data you submit will not be accepted, because the CookieSequencer class automatically redirects you to the next page before processing occurs.

Try using your browser's Back button to back up, change the information in one of the forms, and click the button to resubmit the form. Doing so doesn't change the data you already submitted; in other words, the solution also solves the problem of repeated posts for the same page. Again, the reason reposting doesn't alter the results is that the CookieSequencer object redirects the request before the page processes any information.

While you can prevent people from backing up at all with some extra work, it's very difficult to override the browser's cache completely. In other words, you have to write client-side code to prevent people from revisiting a visited page. In this case, it doesn't matter if people revisit a page earlier in the sequence by backing up—they can't change the data anyway.

As you can see, the results are good, and you don't have to have Sessions enabled for this method to work, which makes it a good choice for a large Internet application. In this particular instance, because the information is short, cookies work well. If the information were longer, you'd have to use a database table or a file to store the information between requests.

Was this article helpful?

0 0

Post a comment