You’re four pages into a five-page form and the phone rings. It’s someone important, so 20 minutes go by before you return to your form. When you return, you click to go back one page and, uh oh, the data’s gone. Damn session timeouts!
We’ve all been there. In fact, I recently ran into the same thing on a multipage form. Though it didn’t surface during acceptance testing, some users in the field were filling out the forms over the course of hours, not minutes. The application’s data loss wasn’t winning a lot of fans.
Well, the options for retaining data across pages, and over time, led down a few obvious paths. Cookies looked promising; the appeal of retaining a nondatabase-oriented replica of the field data was definitely a plus. The drawback, however, centered on user behavior: some field users were notorious for “tinkering” with their browser settings. The Dictionary object allows users to reference data with a name (as opposed to an index reference of an array), which was perfect for form fields NAME/VALUE pairs, but it was failing with the sessions. And, of course, the option of building a server-side component to handle the data persistence was looking mighty appealing1.
Around this time (mid-March), Microsoft released the specs on the new MSDN online, particularly their use of a nifty Dictionary object replacement called the LookupTable. This new object provides hash table functionality similar to the VBScript dictionary; however, it is populated from text files. It’s also theadsafe in Application or Session scope. All of a sudden, I had access to a component that would combine the physical storage appeal of cookies with the speed and reference ability of the dictionary object.
For more information on the LookupTable Component, visit the following site:
http://msdn.microsoft.com/msdn-online/msdnchronicles2.asp
So, I went to work and, inside of a day, I had a working prototype. And thanks to the diligent efforts of Jyothi Nagaraj and Catherine Okite, two developers in my group, we were able to apply these enhancements to the production application in a few short weeks.
What follows is basically that prototype: a sample three-page form that illustrates the fundamentals – instantiating the LookupTable object(s), populating the text files from a form page, and retrieving text file values into a lookup object. For simplicity, I have not included field validation or database posting.
Figure 1 : LookupTable Example - Sample Workflow
First Things First: global.asa
We need to create the object in global.asa – in this case, one object for each page of the form. Why one per page? Managing the files and objects is made a lot easier this way, especially with a lot of back-and-forth navigation.
Wait a minute, I thought you said we’d avoid sessions?
Bear with me for a moment. We do, in fact, avoid the session timeout pitfall. But, we have to use sessions here since each user should fill out a separate and unique form (with a corresponding separate and unique LookupTable). To that end, we also need to generate a unique number that will identify one person’s form from another. You might use the actual session ID or an auto-incrementing component. I use a simple counter in global.asa (see sample code for one method of generating this number).
Trust me. This session stuff will become clear once we get to the form processing.
The Form
So, we launch the form from the page below. Notice that we populate the confirmation number from session but attach it to a hidden form field. Even if the person walks away for five hours before posting the form, the confirmation number is retained as part of the form. This is the last time we’ll use sessions. I’ve even added a session.abandon to prove it.
Default.asp
<%
Option Explicit
dim sConfNum
sConfNum = session("sConfNum")
%>
<HTML>
<HEAD><TITLE>Form Example - Launch Page</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF">
<FONT SIZE=+2><B>Take Me to the Sample Form</B></FONT><BR>
<FORM ACTION="frmMain.asp" METHOD=POST>
<INPUT TYPE=HIDDEN NAME="sConfNum" VALUE="<%= sConfNum %>">
<INPUT TYPE=SUBMIT VALUE="Launch Form >>">
</FORM>
<% session.abandon %>
</BODY>
</HTML>
The form itself is nothing special. It’s a simple three-pager that passes confirmation number, page number, navigation direction (sDir), and next/previous page for navigation (sPgNav) as hidden fields. Notice, however, the VALUE references for the input fields. We’re referencing the LookupValues in the newFRMx objects corresponding to the field names. The LookupTable, like Dictionary, doesn’t fail on blank or null values. So, we’re safe to reference lookups that haven’t yet been filled with data. You can see the entire page in the sample code.
The fun begins once you submit the form. First, we retrieve our hidden page fields:
dim sPg, sPgNav, sConfNum, sDir
sPg = request("sPg") ‘what page did we come from
sPgNav = request("sPgNav") ‘what page are we going to
sDir = request("sDir") ‘are we going next or previous
sConfNum = request("sConfNum") ‘what is the confirmation number
In this example, we’re only saving when proceeding to “next,” so we must check the direction found in the sDir variable.
if sDir = "FORWARD" then ‘ did we hit NEXT
call WriteChanges(sConfNum,sPg) ' generate text file
else
call LoadValues(sConfNum,sPgNav) ‘ go get text file
end if
The WriteChanges subroutine generates our text file from which our LookupTable object will be populated. We pass in both the confirmation number and current page number to generate a text file that will have the following name: pgX_YYYYY.txt (where X = page number and YYYYY = confirmation number). In the example code, a directory called txtForms holds all text files.
Sub WriteChanges(sConfNum, sPg)
Dim objFSO, objFile, strFile, item
strFile = "pg" & CStr(sPg) & "_" & sConfNum & ".txt" ‘create page name
strFile = Application("strFormsPath") & strFile ‘generate full path
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile(strFile,2,True) ‘open for writing and
‘create as needed
for each item in request.form
objFile.WriteLine item & "," & request(item) ‘name,value pairs
next
objFile.Close
call LoadFormValues(sConfNum,sPg)
End Sub
The last line of the WriteChanges subroutine calls the second subroutine to load the values from the newly created text file.
call LoadFormValues(sConfNum,sPg)
This subroutine is similar to WriteChanges because it works from the text file and application path. Since we have separate LookupTable objects for each page, however, the subroutine must make sure it’s matching the appropriate Lookup (in this case newFRM1) and text file (pg1_ConfNumber.txt).
Sub LoadFormValues(sConfNum,sPg)
Dim objFSO, frmPage, frmPath, frmResult
frmPage = "pg" & CStr(sPg) & "_" & sConfNum & ".txt" ‘get filename
frmPath = Application("strFormsPath") & frmPage ‘get path
Set objFSO = CreateObject("Scripting.FileSystemObject")
if objFSO.FileExists(frmPath) then ‘is it there?
select case sPg ‘if so, load lookup
case "1"
frmResult = newFRM1.LoadValues(frmPath,0) case "2"
frmResult = newFRM2.LoadValues(frmPath,0)
case "3"
frmResult = newFRM3.LoadValues(frmPath,0)
end select
end if
End Sub
The LookupTable.LoadValues method is used to load the text file. The second parameter (0) indicates that this is a text file with string/string key/value pairs on each line (you can use string/string, string/integer, integer/string, or integer/integer as you’d like). By adding 10 to this second parameter, you may also instruct the LookupTable to ignore duplicate keys.
frmResult = newFRM1.LoadValues(frmPath,0)
Finally, we redirect to the next (or previous) page in our form.
And we’re done. When we navigate back to page one, the values are loaded directly from the LookupTable object. It’s fast, simple code, and if something crashes and burns, there’s still a text file on the server that holds the form data.
The Magic
And of course, the nice thing about having a server-resident text file with your form data is that, assuming you provide the confirmation number, your form data can be retrieved. The following variation on the LoadFormValues function provides a “Return to Saved Form” option.
FrmGet.asp
<%@ LANGUAGE="VBSCRIPT" %>
<%
Option Explicit
Dim objFSO, frmPage, frmPath, frmResult, sConfNum, i
%>
<!--#include file="frmValues.inc"-->
<%
sConfNum = Trim(request("sConfNum"))
for i = 1 to sMaxPage
frmPage = "pg" & CStr(i) & "_" & sConfNum & ".txt"
frmPath = Application("strFormsPath") & frmPage
Set objFSO = CreateObject("Scripting.FileSystemObject")
if objFSO.FileExists(frmPath) then
select case i
case "1"
frmResult = newFRM1.LoadValues(frmPath,0)
case "2"
frmResult = newFRM2.LoadValues(frmPath,0)
case "3"
frmResult = newFRM3.LoadValues(frmPath,0)
end select
end if
next
response.redirect "frmMain.asp?sPg=1&sConfNum=" & sConfNum
%>
Don’t Look Now: More Benefits
One of the interesting elements about the LookupTable that Microsoft discusses in their MSDN implementation is the ability to wrap XML in the text files. For our purposes, this is fodder for future discussions. But, given the success of the implementation, we’re eager to investigate additional functionality offered by the LookupTable component.
Brian Reagan has worked in IT consulting and database systems design for
over a decade. He's spent the last five years focused on web development,
delivering high-profile internet sites and web-enabled corporate
applications. Now, he's driving enterprise architecture and deployment at
EMC Corporation, where he manages the IS Web Services group. At night he's
focused on changing his newborn's diapers.
ASP Session Manager works by inserting 2 lines of code at the top of your ASP script and have the session serialize itself to any DBMS. There are three licenses available, ranging from a single binary to an enterprise ISP license with source code.
Web farm getting out of control? You need scalability! SA-SessionPro is a simple, yet
very powerful means of making your ASP application persistent--share and save
information on a per-user, per-application, per-server, or per network basis. SA-Session is
like the built in Session object that comes with the IIS Server, except it allows developers
more flexibility. It has the ability to store information without expiration and the ability to
preserve session state across multiple machines in a web farm.
SessionFarm is an Active Server Pages component that allows you to manage session state on multiple servers in a web-farm. SessionFarm utilizes the built-in IIS Session object which means you don't have to learn new methods
and objects. The IIS Session object is stored on either a file-share or a SQL database using optimized stored procedures.
SessionFarm requires no rewriting of your existing code and works by supplementing the existing IIS Session.
For code samples, see http://www.groat.com/sessionfarm/implement.asp.
A seemingly innocent Inquiry has sparked a member of the 15Seconds discussion list to answer with a sermon on the evil nature of Session Variables.
[Read This Article][Top]
Hidden frames allow users to maintain almost any kind of data, and maintain state easily and reliably. Mark Burnham's article uses a shopping cart scenario to show simple ways to read and write data, and call functions from a hidden frame. Visible frames can be loaded with virtually anything, but these hidden frames will always be there, holding data until it's needed. [Read This Article][Top]
Mark Burnham offers a quick and easy way to check if your browser accepts cookies. If it does, then you're clear to use session variables when writing ASP scripts. Just follow the sample code to learn how to copy a form and compare SessionIDs. [Read This Article][Top]
In this issue we will discuss two built in states of the Internet Information Server, session and application. We will also continue where we left off with the Nov 08, 1997 - Sharing Cookies Across Domains Issue, and show how to maintain session state across multiple servers in a web farm. Also discussed, will be user state and the use of personalization to maintain user state. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.