Maintaining State in Files

You can make up your own file format to hold whatever data you want, but just to reiterate how convenient XML files are, the example in this section uses an XML-formatted file to hold the data. The FileSequencer class handles loading and saving the file based on a GUID value stored in a permanent cookie created the first time a user requests any page in the sequence.

Note You must give the account used by IIS read/write permission to the directory where you want to save the files before this example will work. The example saves the XML files in a subdirectory of the csharpASP/chl7 folder named XMLData.

There are several interesting points about using files to maintain state. First, you can see that you need some way to associate a user with his file. This solution uses a permanent cookie. This dovetails nicely with the second point, which is that, unlike the session and cookie examples you saw in the preceding sections, files can manage data not only across requests but across browser sessions and instances, as long as the user requests the sequence from the same server. For Web farms with multiple servers running the same application, where load balancing software apportions requests to the server with the lowest load, the file in which the data is stored must be accessible from any server the user can reach.

To test this, browse to the FileSequencei.aspx page and enter a last name. Then, before you enter anything into the FileSequence2.aspx page, close your browser. At this point, the FileSequencer class has already written the cookie. If you're using IE, you'll find the cookie in the c:\Documents and Settings\<your user name>\cookies folder on your hard drive. Netscape browsers store permanent cookies in a cookies.txt file in a subdirectory of the directory where Netscape is installed. The cookie contains a GUID that matches the name of an XML file written to the CSharpASP\chl7 \XMLData subdirectory on the Web server. The filename will be something like 784a524f-67f0-470a-8588-l7e278cf9a83.xml, although the GUID will be different. If you're testing more than once, look for the latest copy.

Tip To reset the sequence, delete the cookie file from your hard drive, then exit and reload your browser.

Double-click the XML file. It should open in IE. If it doesn't, drag the file onto an open instance of IE to view it. The file's contents look like Figure 17.2 except that the <lastname> element will have a different value.

Figure 17.2: The File-Sequencer object writes an XML file containing the sequence, the current sequence index, and the data entered by the user

The file contains everything you need to maintain the user's state—the sequence index <current>, the sequence of filenames, and the user-entered data. The FileSequencer object code handles almost everything (see Listing 17.5).

Listing 17.5: The FileSequencer Class Handles User Sequencing (FileSequencer.cs)

using System; using System.Web; using System.IO; using System.Xml;

namespace CSharpASP.ch17 { /// <summary>

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

public class FileSequencer { private String m filename; private String m path;

private System.Xml.XmlDocument xml = null; private int currSequenceIndex; private HttpRequest Request = null; private HttpResponse Response = null; private HttpServerUtility Server = null; private String expectedPage; private String requestedPage;

public FileSequencer() {

HttpContext Context = HttpContext.Current; FileInfo fi = null; String sGUID;

HttpCookie sequenceCookie = null;

Response = Context.Response;

Request = Context.Request;

Server = Context.Server;

// get the current page name requestedPage = Request.ServerVariables

["SCRIPT_NAME"].ToLower(); requestedPage = requestedPage.Substring (requestedPage.LastIndexOf("/") + 1);

// create the output filename m_filename =

_Request.ServerVariables["SCRIPT_NAME"].ToLower(); m filename = Server.MapPath(m filename); m filename = m filename.Substring _ (0, m_filename.LastIndexOf("\\"));

// get the sequence cookie sequenceCookie = Request.Cookies.Get("sequencefile"); if (sequenceCookie != null) {

sGUID = sequenceCookie.Value;

m_filename = m_filename + "\\XMLData\\" + sGUID + ".xml";

// read the xml document if it exists fi = new FileInfo(m filename); xml = new XmlDocument(); try {

// ensure the directory exists m path = m filename.Substring

DirectoryInfo di = new DirectoryInfo(m path); if (!di.Exists) { di.Create();

// write the default state of the document xml = new XmlDocument(); xml.LoadXml("<?xml version=\"1.0\" " +

"encoding=\"UTF-8\"?><sequenceinfo>" + "<current>1</current>" +

"<sequence>filesequence1.aspx</sequence>" + "<sequence>filesequence2.aspx</sequence>" + "<sequence>filesequence3.aspx</sequence>" + "<sequence>filesequencedone.aspx</sequence>" + "</sequenceinfo>"); // write the GUID as a permanent cookie sequenceCookie = new HttpCookie("sequencefile", sGUID);

// set the expiration date sequenceCookie.Expires = new DateTime

(2010, 12, 31, 23, 59, 59); Response.Cookies.Add(sequenceCookie);

xml.Load(m filename);

// get the current sequence currSequenceIndex = XmlConvert.ToInt32

(xml.SelectSingleNode("//sequenceinfo/current") .InnerText); // get the name of the expected page expectedPage = xml.SelectSingleNode ("sequenceinfo/sequence[" +

currSequenceIndex.ToString() + "]").InnerText; if (expectedPage != requestedPage) { Response.Redirect(expectedPage);

catch (Exception e) {

Response.Write(e.Message); Response.End();

private String getGUID() {

return System.Guid.NewGuid().ToString();

public void addNode(String nodename, String nodevalue) { XmlNode N = null;

N = xml.SelectSingleNode("sequenceinfo/" + nodename); if (N != null) {

N.InnerText = nodevalue;

N = xml.CreateElement(nodename); N.InnerText = nodevalue; xml.DocumentElement.AppendChild(N);

public void NextPage() { if (currSequenceIndex < xml.SelectNodes("//sequence").Count) { currSequenceIndex++;

xml.SelectSingleNode("sequenceinfo/current"). InnerText = currSequenceIndex.ToString();

xml.Save(m filename);

Response.Redirect(xml.SelectSingleNode ("sequenceinfo/sequence[" + currSequenceIndex.ToString() + "]").InnerText);

public void showFileVars() {

// just show the XML file contents Response.ContentType = "text/xml"; Response.Write(xml.InnerXml);

As with the other sequence management classes you've seen in this chapter, the constructor handles the sequence operation. The code creates references to the Request, Response, and Server objects and retrieves the current filename in the same way as the preceding code examples.

Next, the code creates an XmlDocument object and checks for the GUID cookie. If it finds the cookie, it constructs a filename and attempts to read the file. If it doesn't find the cookie, it creates a new GUID. The XmlDocument object updates nodes in memory during page execution but uses the XmlDocument.Save method to persist the altered XML to disk when the page calls the FileSequencer object's NextPage method. You can find the full code for the FileSequencer.cs class on www.sybex.com and in Listing 17.5. // get the sequence cookie sequenceCookie = Request.Cookies.Get("sequencefile"); if (sequenceCookie != null) {

sGUID = sequenceCookie.Value;

The code uses the sGUID variable to construct a filename and write the initial file values:

// read the xml document if it exists fi = new FileInfo(m filename); xml = new XmlDocument(); try {

// ensure the directory exists m path = m filename.Substring(0, m_filename.LastIndexOf("\\"));

DirectoryInfo di = new DirectoryInfo(m path); if (!di.Exists) { di.Create();

// write the default state of the document xml = new XmlDocument();

xml.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<sequenceinfo><current>1</current>" + "<sequence>filesequence1.aspx" +

"</sequence><sequence>filesequence2.aspx</sequence>" + "<sequence>filesequence3.aspx</sequence>" + "<sequence>filesequencedone.aspx</sequence>" + " "</sequenceinfo>"); // write the GUID as a permanent cookie sequenceCookie = new HttpCookie("sequencefile", sGUID); // set the expiration date sequenceCookie.Expires = new DateTime(2010, 12, 31, 23, 59, 59);

Response.Cookies.Add(sequenceCookie);

xml.Load(m filename);

// get the current sequence currSequenceIndex = XmlConvert.ToInt32

(xml.SelectSingleNode("//sequenceinfo/current").InnerText); // get the name of the expected page expectedPage = xml.SelectSingleNode("sequenceinfo/sequence[" +

currSequenceIndex.ToString() + "]").InnerText; if (expectedPage != requestedPage) { Response.Redirect(expectedPage);

catch (Exception e) {

Response.Write(e.Message); Response.End();

Note Internet Explorer shows the XML file using its default XSLT stylesheet. Netscape browsers show only the content. To show the markup in Netscape browsers, you will need to create a stylesheet, "borrow" the IE default stylesheet, or use the Server.HTMLEncode method to encode the markup so that it appears properly. If you want an indented look, use an XMLTextReader (hint: see the Depth property).

To finish the test, open a new browser instance and browse to the FileSequence1.aspx page again. This time, the code redirects you to the FileSequence2.aspx page.

The code in the FileSequence(x).aspx pages is much the same as in the previous examples in this chapter. Each page creates a FileSequencer object, which prevents the page from completing unless it's the proper page in the sequence. Here's the Page_Load event code for the first page in the sequence:

private void Page Load(object sender, System.EventArgs e) { FileSequencer fileSequencer = new FileSequencer(); // The FileSequencer object redirects the request // if it's not a valid request in the sequence if (IsPostBack) { Page.Validate(); if (Page.IsValid) {

// save the last name fileSequencer.addNode("lastname", txtLastName.Text); fileSequencer.NextPage();

The only new code here is the FileSequencer.addNode method, which creates or updates the <lastname> node in the XML data file.

So, using files to maintain state extends your state management technique across browser instances on the same machine. That's considerably more robust than forcing the user to start over again if he accidentally closes the browser or loses his connection. However, that's still not robust enough, because cookies only identify machines and individual browser types, not users. The application would break if any of the following conditions occurred:

■ A user moved from one machine to another. The cookie resides only on one machine. The user would have to start the sequence over.

■ A user opened a different type of browser and browsed to one of the sequence pages. Browsers from different manufacturers don't usually share cookies. The user would have to start the sequence over.

■ There are multiple people using the same computer under the same username or account. For example, a shared computer in a manufacturing control room might easily have multiple users. In that case, the application works properly for the first user but not for subsequent users, because the local machine sends the same cookie for each user.

■ A user erases the cookie file either intentionally or unintentionally.

While cookies work to identify a machine, even across sessions, they won't work by themselves to identify a user and are useless across machine and browser boundaries. Nonetheless, permanent cookies are often good enough for applications where there's no security involved, you don't really care who the user is, and the work or task performed by the application is not critical. The biggest problem for most of the failure scenarios is that you lose the connection (the cookie) between the machine and the data.

That loss leads to yet another potential problem—or advantage, depending on how you look at it. Files are more or less permanent on a backed-up server. Therefore, they tend to accumulate rapidly. If you test the FileSequence sample several times, perhaps with different browsers, from different client machines, or by erasing the cookie between tests, you'll see that the program has no way to delete the XML files holding the data. Even though the files exist, no user will ever be able to use them again. Whenever you create a scheme that uses files, you must consider the problem of how to archive or destroy older files. Finally, because files use relatively slow IO, reading and writing too many files can become a performance bottleneck for busy sites.

Was this article helpful?

0 0

Post a comment