Page #59 (Chapter 8 - Maintaining State in IIS Applications)

Chapter 8 - Maintaining State in IIS Applications

Visual Basic Developers Guide to ASP and IIS
A. Russell Jones
  Copyright 1999 SYBEX Inc.

Selecting a State Maintenance Option
WebClasses have two options for maintaining state. You select which option you want to use by setting the StateMaintenance propertyof the WebClass. The StateMaintenance property takes one of two values, called (not very intuitively) wcRetainInstance and wcNoState.
The wcRetainInstance setting is seductive; when you select it, you can maintain state by setting properties and variable values within your WebClass—just like a standard stand-alone VB program. The wcRetainInstance setting tells the WebClassManager object not to destroy your WebClass instance between requests. Therefore, the WebClass maintains values you set until it times out or you explicitly destroy it by calling the ReleaseInstance method. The wcNoState option doesn't mean you won't maintain state, it just means that you can't maintain state in WebClass variables—you'll have to use one of the other methods explained in this chapter.
You should never use the wcRetainInstance option unless your site has a small number of simultaneous users. When you select this option, IIS (or the WebClassManager) stores your entire WebClass in a Session variable between requests. That means that IIS must handle all that user's subsequent requests to the WebClass on the same execution thread as the first request. IIS serializes multiple requests that require the same thread, which means that as your user population grows, the chances rapidly increase that people will have to wait while IIS services a prior request that is using the same thread. If you require any degree of scalability, don't use the wcRetainInstance method.
When you select the wcNoState method, you avoid the thread problem and you still have five ways to maintain state in your application. Each has advantages and disadvantages. You can maintain state by using:
  Session variables
  Cookies
  QueryString variables
  Hidden form variables
  Database tables or files
I'll discuss all these options and show you when each might be appropriate. For each method, I'll use this example: You have a database table containing demographic information about registered users—UserIDs, sign-ons, names, etc. When a user signs on, you use the sign-on value that the user entered to look up the user's information in the database.
Now assume your application needs access to the user's demographic data for each page in the application. The question is, how do you store and retrieve the information? The only information you always know about a user is their SessionID, because the ASP engine stores the SessionID cookie on the browser when the session begins. To be able to retrieve the user's information, you need to somehow make—and keep—an association between the user's SessionID and the information related to that user.
Maintaining State with Session Variables
Using Session variables is the easiest method for maintaining state. You can store any VB data type in a Session variable. You'll recall that the Session object is an associative array much like a Scripting Dictionary object. A Session can hold a practically unlimited number of name-value pairs. Each Session variable name must be unique. The value associated with that name is a Variant; therefore, you can store primitive data types, such as strings or numeric values, and COM objects, such as Dictionary or custom ActiveX objects.
So, you can store the UserID in a Session variable. Now, whenever the user makes a request, you can use the value of the Session("UserID") variable to look up the user's information in the database:
Set R = conn.execute("SELECT * FROM Users " & _
     "WHERE UserUserID"), , _
     adCmdText)
But wait! Retrieving database data is an expensive operation in terms of time and server resources. You may not want to perform a database lookup for each user request. You retrieved the data once during the sign-on request—you could store the entire row in Session variables during the sign-on operation. That way, you wouldn't need to go back to the database for each subsequent request. Instead, you can create a Session variable for each field in the table row for that user, for example:
Session("UserID") = rs("UserID")
Session("UserSignon") = rs("UserSignon")
Session("UserLastName") = rs("UserLastName")
Session("UserFirstName") = rs("UserFirstName")
Session("UserEMail") = rs("UserEmail")
' etc…
Now you have access to the user data from anywhere in the application without returning to the database. In essence, you've cached the demographic data for this user in the Session object.
This is absolutely the easiest way to cache data for an individual user of your application, and probably the one you'll use most often. There are only a couple of problems with it (you knew this was coming, didn't you?). Think about what the ASP engine must be doing while it retrieves the variable values for each user. Imagine that the Session object is a Dictionary; each key is a SessionID and each value is itself a Dictionary. The keys of this sub-dictionary are the names you give your Session variables, and the values are your Session variable values (see Figure 8.1). I don't know exactly how the Session object stores data, but it must be with a method similar to this.
The point is that using Session variables at all forces the server to access a single object for each request. Therefore, the use of Session variables must force the server to serialize requests, at least during retrieval of the sub-dictionary that contains the Session variables associated with that user's SessionID.
WebClasses Require Sessions
Why use Sessions at all? You can disable them, either for your entire server, or for a specific site, by using the IIS administration program. Unfortunately, if you disable Sessions, the WebClassManager will be unable to create the WebClass object, and your WebClass will fail to initialize. You must leave Sessions enabled for sites that use WebClasses (Sessions are enabled by default when you create an IIS application).
Sessions can be enabled or disabled at either the Web level or the virtual directory level, so you'll need to make sure they're enabled at both levels. To enable Sessions, start the IIS administration program. The check box to enable or disable Sessions is in the Application Configuration dialog box. Right-click the Default Web Site entry in the left-hand pane, select Properties, then click the Home Directory tab. Click the Configuration button, then click the App Options tab (see Figure 8.2). Make sure the Enable Session State check box is checked and then repeat the process to enable Sessions for each virtual directory that uses WebClasses.
Sessions are a mixed blessing, but they're something you'll have to live with if you want to use WebClasses because WebClasses don't work if you turn off Sessions. Microsoft recommends that you should use another method besides Sessions to maintain state if you want to build Web sites that will scale to large numbers of users. Why they've made Sessions a requirement for WebClasses is a mystery. You can use ASP without Sessions, but not WebClasses. Maybe in the next version…
Consider a site where you want to use more than one server to service requests. If you use Session variables, you need some method to route requests from one browser to the same server for each request. If you don't have such a method (and the ASP engine doesn't currently provide one), the Session information either won't be available or will be different on each server.
Commercial routers are available that let you use Sessions on sites with multiple servers; you should consider one of these if you build a site with heavy use. For smaller sites, it's relatively easy to write your own multi-server router. Suppose you have a single, publicly available address for your site—for example, http://www.mySite.com. You have four servers, only one of which has a public IP address corresponding to the mySite.com IP address. The other three servers' IP addresses are not mapped to the mySite.com address. Together, all four servers constitute a Web farm. The public server receives all initial requests for your site.
The first time a new request arrives, the public server uses a script to route the request to one of the three Web farm servers. That server then provides a SessionID. Subsequent requests by the same browser bypass the public server and go directly to the Web farm server. The script ensures that each server gets an equal number of requests. You do need to use relative paths to ensure that graphics and other resources are available on all the servers. Because you need to balance the load, you may want to ensure that people can't bookmark pages on a specific server by redirecting any users without SessionIDs (new sessions) back to the public server for routing.
Load balancing is beyond the scope of this book, but you could easily write a simple round-robin scheme. Depending on your setup and your application requirements, that may be sufficient, but typically, you'll want to at least poll the servers to find out if they're running, which one currently has the smallest load, etc. You will probably need fail-over protection and may even want to bring additional servers online as needed.
Finally, although it may not always be an issue, you need to be aware of the memory requirements when you use Session variables. Storing a few small strings and numbers for each user won't use much memory, but storing entire recordsets—even as arrays—will use up large amounts of memory in a hurry. If the server runs out of RAM memory, it will begin spooling the data out to virtual memory—and that will definitely adversely affect your application's response time and scalability.
Maintaining State with Cookies
You don't have to use Session variables at all (but you do have to have Sessions enabled). Instead, you can use the same method that the ASP engine uses to identify users —cookies. Remember that a cookie provides a way to store information on the client. The browser associates the cookie with a given site and sends the cookie information along with each request.
From the ASP server-side scripting point of view, a cookie is a collection of keyed values—much like a Dictionary object. You use the Request object to retrieve cookie values and the Response object to create or alter them. Therefore, in the example, you would store the UserID in a cookie after you have authenticated the user during sign-on:
Response.Cookies("UserID") = rs("UserID").Value
To retrieve the value in subsequent requests, use:
aUserID = Request.Cookies("UserID")
Even better, a cookie can have multiple keyed values, called subkeys. You can group the user information in a single cookie:
Response.Cookies("User")("UserID") = rs("UserID").Value
Response.Cookies("User")("Signon") = rs("UserSignon")
Response.Cookies("User")("LastName") = rs("UserLastName")
Response.Cookies("User")("FirstName") = rs("UserFirstName")
Response.Cookies("User")("UserEMail") = rs("UserEmail")
' etc.
To retrieve a subkey value from a cookie, use this syntax:
aUserID = Request.Cookies("User")("UserID")
If you're not sure whether a cookie has subkeys, you can check by using the HasKeys property. The HasKeys property returns True when the cookie contains subkeys:
If Request.Cookies("myCookie").HasKeys then
   ' do something
End If
After the server receives a request, it parses the cookies into some kind of collection object, which understands the For…Next syntax. To process all the cookies in a request, you can use a loop like this:
With Request
     For Each aKey in .Cookies
         If .cookies(aKey).HasKeys Then
             For Each subKey in .Cookies(aKey)
            Response.Write subKey & "=" _
                  & .cookies(aKey)(subkey) & "<BR>"
             Next
         End If
     Next
End With
If you don't need to store much information between requests, cookies are an excellent way to maintain state because they don't take up any memory on the server. You can even write "permanent" cookies that store data on the client's hard drive. Permanent cookies, also called persistent cookies, make it possible to store state even between sessions.
To make a cookie permanent, use the Expires property. When you set the Expires property to a date later than the current date, the browser will store the cookie on the user's hard drive. For example, to create a cookie that expires in one week:
Dim nextWeek As Date
NextWeek = dateAdd("d", 7, now())
Response.Cookies("User").Expires = NextWeek
Unfortunately, the maximum size of cookies differs somewhat from browser to browser. To be safe, you shouldn't count on more than 4,096 bytes (4K) of data. In addition, although cookies don't take up server space, they do take up bandwidth. This may not be an issue if you're storing only a few values, but suppose you have 100 clients sending 4K of data for each request, each requesting four pages per minute. If you change the cookie slightly at each request to maintain state, you're sending over 1.5MB of information per minute over the network. With 1,000 clients, that increases to 15MB per minute—and that doesn't even take any other data into account, such as the content of the pages themselves. That may not bring your network to its knees, but remember that your application may not be the only one on the network, or even on the Web server. Also, because cookies are sent as strings, the Web server must parse the strings into the Request.Cookies collection, and that also consumes resources.
By default, the browser sends all cookies set by an application back to that application. If your application has more than one directory, you can specify which cookies the browser should send to each directory in your application by adding a path to the cookie. The browser compares the path of the request to each cookie and sends the cookie only if it matches the path. To add the path, use the Path property:
Response.Cookies("myCookie").Path = "/myPath"
Response.Cookies("myCookie") = "someCookie"
In the preceding example, the browser would send the myCookie cookie only to requests for pages in the /myPath virtual directory.
For cookies that have an expiration date, you can set the domain as well as the path. This lets you create a cookie in one application that the browser will send to a different application in another domain.
In the background, transparent to developers, the Response object writes an HTTP header to tell the browser to save the cookie. You can use the Response object's AddHeader method to bypass the automatic cookie-management and cookie-storage functions. To add a cookie, use this syntax:
Response.AddHeader "Set-Cookie", "<name>=<value> " _
     & [; <name>=<value>]...[; expires=<date>][; " _
     & domain=<domain_name>][; path=<some_path>][; secure]
Here's an example:
Response.AddHeader "Set-Cookie", "myCookie=someCookie"
The result is the same as if you had used the Cookies collection—the browser stores the cookie.
The browser stores cookies in a Cookies subdirectory of your Profile directory. On Windows NT, your profile is in the Profiles subdirectory of the $systemRoot$ path—usually called WinNT. On Windows 95/98, your profile is in a single subdirectory of the $systemRoot$ path—usually called Windows. When you add a cookie using the Response.Cookies collection, you can set the Expires parameter using any valid date format. When you use the HTTP method, you need to specify the date in the format ddd, dd-mmm-yyy hh:mm:ss GMT.
Use the VB Format command to format the date properly:
Dim GMTDate as string
GMTDate = Format$(Now, "ddd, dd-mmm-yyyy hh:nn:ss") _
     & " GMT"
You can look in the appropriate Cookies directory to check that your application sent the cookie in the correct format. If it did, the browser will create a file like username@domain.txt that stores the cookie information.
The browser stores and transmits cookies as text files, so you need to encrypt the cookie if the information is sensitive, such as sign-on and password information. You can manually encrypt the information or tell the browser to automatically encrypt the data. Cookies have a Secure attribute that tells the browser to store the cookies in encrypted form and send them only to sites that use Secure Sockets Layer (SSL) encryption.
Maintaining State with QueryString Variables
Not all browsers accept cookies. There's been a great deal of press coverage (mostly negative) about how unscrupulous Web operators are stealing your privacy by storing unwanted information on your computer. Therefore, some of the more paranoid individuals have improved their privacy by turning off cookies. For clients that won't accept cookies, you can't store anything in the Session object because the ASP engine won't find the Session.SessionID cookie and will try to create a new one for each request. In this case, of course, you can't use cookies to maintain state either. Instead, you can pass information through the QueryString collection.
Remember that the data contained in the QueryString collection is actually a delimited text string appended to the URL. On the server, you receive the data in the Request object as the Request.QueryString collection. As is typical with Web communications, the raw data is in key=value form just as it is in cookies, form variables, etc. Ampersands separate the key-value pairs. A question mark separates the entire query string from the URL. The following URL example contains two QueryString values, LastName and FirstName.
http://myServer/mySite.com?LastName=Doe&FirstName=John
Most browsers support up to about 1,024 characters in QueryString data. The server parses the QueryString data into a collection, so you can retrieve it using keys or indexes (1 based, not 0 based). The QueryString collection supports standard For Each…Next syntax. Using the QueryString from the previous example:
Response.Write Request.QueryString("LastName") ' prints Doe
Response.Write Request.QueryString.Count ' prints 2
Response.Write Request.QueryString(2) ' Prints John
That makes it extremely easy to retrieve the data sent by the browser. You can add QueryString parameters to HTML you send to the browser in two ways:
  Concatenate strings to produce a valid URL.
  Use the URLData property of the WebClass.
Concatenating strings is a straightforward process—you just need to remember to separate the first key=value pair from the file portion of the URL with a question mark and all subsequent pairs with ampersands. For example, suppose you have a recordset of user names. You want to display the names as links. When an administrator clicks one of the names, you want to display a form to edit the user's information. For each link, you'll want to return the user's ID.
While Not R.EOF
     With Response
         .Write "<a href=somepage.asp?2%">
         R("UserID").Value & ">" & R("Name") & "</a>"
Now, when the administrator clicks the link, you'll be able to pull the UserID from the QueryString collection to display the form to edit the user's information:
Dim anID
anID = Request.QueryString("ID")
' display form based on anID
When you show the form, you'll need to keep track of the ID in the form page too, and possibly in several subsequent pages; that's the application user's state—editing the user with the ID called anID. All that concatenation can become painful when you're trying to keep track of multiple variables. Imagine trying to append enough information to keep track of 20 or 30 critical variables in an application!
Luckily, VB can help. As you saw in Chapter 7, "Controlling Program Flow," each WebClass has a URLData property that it can append to every URL generated by the URLFor method. VB also appends the URLData to every link in an HTML template that contains a WCU parameter when you call the WriteTemplate method. Letting VB format the strings is much more convenient than formatting the URL strings yourself.
The URLData can be in any format you desire. Because VB returns the URL, it does not have to follow the key=value format imposed by QueryString data.
Using URLData or QueryString data to maintain state has some undesirable characteristics. First, the user (and other observers) can see the data because it appears in the address bar. Therefore, you should never use these methods to send any private information unless you also apply your own encryption scheme to the data. Second, if you use QueryString data and your data contains spaces or other non-alphanumeric characters, you must apply the Server.URLEncode method to the query string before appending it to a URL. You don't need to do that for URLData; the WebClass method automatically replaces the appropriate characters with their encoded equivalents.
  Note The Server.URLEncode method replaces characters with a percent sign followed by the hex value of the character. For example, a space (ASCII 32) is %20.
Maintaining State with Hidden Form Variables
Yet another way of maintaining state on the client is to use hidden form variables. If you do this, you need to create a <form> tag and insert the hidden form variable for each page requiring you to pass state back to the server. You also need to ensure that the client submits the form using the Post method. That means that you will either have to have a Submit button for each page or will need to write client-side script to submit the form when a navigation event—such as a click on a link or button—occurs.
For example, to pass a variable called ID with a value of 2817 from one page to another, the first page could contain:
<form name="frmHidden" method="Post">
     <input type="hidden" name="ID" value="2817">
     <input type="submit" value="Submit">
</form>
When the user clicks the Submit button, the form will send the value ID=2817 to the server. You can retrieve the value by using the Request.Form collection, then use the value in a form on the next page.
To submit the form from a link, use client-side script such as this:
<script language="javascript">
     function doSubmit() {
         document.frmHidden.submit();
   }
</script>
<form action="mypage.asp" name="frmHidden" method="post">
     <input type="hidden" name="ID" value="2817">
     <input type="submit" value="Submit">
</form>
Somewhere on the page, you would put a link back to the ASP page that instantiates your WebClass:
<a href='mypage.asp' onClick='doSubmit();'>Click Here</a>
If you wanted to continue the sequence, you would retrieve the ID value in the WebClass instantiated by the mypage.asp script by using Request.Form("ID") and then send the ID forward to the next page.
The process of setting and retrieving the ID variable would continue as long as you needed the variable value on the server to maintain state.
Hidden form variables are in some ways better than cookies or QueryString variables. Hidden form variables have no size restriction and they're not visible in the browser's address field; but they are visible to users savvy enough to use the View Source feature of the browser. You should not use hidden form variables for values that you don't want the user to see (for example, answers to test questions).
Maintaining State in a Database
This method is probably the most scaleable way to maintain state—and it will become especially important if Microsoft changes WebClasses so that they don't have to have Sessions enabled. Storing state in a database is definitely slower than the other methods described in this chapter when your application has only a few users, but as the number of users increases, using a database to store state quickly outstrips using Session variables. It's also easier to script than using QueryString or form variables, especially when you have a large application for which you must store many more state variables.
Another major advantage of storing state in a database is that you can maintain state not only during a session, but also across sessions. The only other way to maintain state across sessions is to use persistent cookies. When you maintain state in cookies, the data resides on the client, whereas when you maintain state in a database, the data resides on the server. Maintaining cross-session state on the server is better. People frequently change computers, delete cookie files, or sign on to their computers using other sign-ons, so client-side state is less certain.
To store state in a database table, you must set up the table properly. By that, I don't mean that you must have a column for every variable, but that you must be able to identify the data row or rows that belong to an individual or session. You do this by setting a single identifying cookie that acts as a primary key to the table, then retrieving the data using that cookie value when each request starts. You can create and set your own cookie value—for secured sites, you will probably want to use the user's unique ID or sign-on. For cross-session state maintenance, you'll need to have each user sign on with an ID and password. For single-session state maintenance, you can use the SessionID cookie as long as you don't care about storing state for an individual user, just an individual session.
Here's how to store state in a database on a single-session basis:
  1. In the Session_OnStart event in the global.asa file, you create a new row(s) for the new SessionID or sign-on.
  2. In the BeginRequest event code for each WebClass in your application, you read the cookie, then retrieve the appropriate row(s) from the database.
  3. During page processing, you add or remove state information as appropriate.
  4. In the EndRequest event code for each WebClass, you update the database with any changed state information.
  5. In the Session_OnEnd event in global.asa, you delete the row(s) from the database.
To store state between sessions, you defer step 1 until after the user has signed on and you create a new row only if the user doesn't already have data. You would eliminate step 5 unless an administrator deletes the sign-on.
You'll need a scheme to delete or archive obsolete state data—data you've stored for individuals who never return to your site. For many public sites, this is the bulk of the data collected; for internal sites, it may occur only when an employee leaves the company. If you use persistent cookies to connect people to their data, you will probably also want a way for a user whose cookie has been lost to reconnect to their data. That's why many sites ask you to provide special personal information—so they can ask you about it if you lose or delete the site cookie.
Summing Up State Maintenance Options
Unless you're migrating an existing application into WebClass form, there's no particular advantage to using any methods other than Session variables or databases to store state. WebClasses force you to have Sessions enabled; therefore, you may as well take advantage of them—you don't gain anything by ignoring them and moving state information to the client except some free memory on the server. You can use both methods at the same time—database tables to store information between sessions and Session variables to store information during a session. That way, you get the best of both worlds: the persistence of databases and the in-memory access to Session variables.
If Microsoft changes WebClasses so that you can use them without having Sessions enabled, you may find that you gain performance and scalability if you expend the effort now to save state entirely in database tables.



Visual Basic Developer[ap]s Guide to ASP and IIS
Visual Basic Developer[ap]s Guide to ASP and IIS
ISBN: 782125573
EAN: N/A
Year: 2005
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net