In most Web applications, tracking users and their activities from page to page requires a large portion of the overall programming effort. .
Active Server Pages utilizes directory structures and the global.asa file for managing applications.
First, an overview of the Application and Session Objects' scoping, events and properties.
The Application Object provides a mechanism to track sessions and store variables and objects for application-wide use.
Use the Session Object to manage a user's flow from page to page, tracking user input, and caching information.
Managing users as they navigate through an application represents a common and easily handled challenge in the traditional Client Server application development world. In the Web world, in contrast, Internet application developers find managing this challenge or, in essence, maintaining a user's state to be one of the greatest challenges to building a comprehensive Internet-based application. Because HTTP transactions between a browser and a Web server are independent with no persistence or connection state being maintained, even tracking a user as she moves from one page request to another can be a difficult task.
For managing a user from her first page request, Active Server incorporated a model based on Cookies to generate a unique ID for users' browsers. On the first page request, the session OnStart event fires. This event sets the scope of several properties for the life of this unique browser's session. At the same time, the event defines a Timeout property to manage the session length. The Timeout property measures inactivity from the Cookied browser. This chapter explores in detail the ability to track users, but goes way beyond that ability, in exploring the valuable set of functionality extended to programmers through the Session and Application Objects.
![]()
Cookies are a feature of HTML 2.0 that enable an HTML author to write a variable or named piece of information to the client browser. If the browser supports HTML 2.0 or greater, this information is then saved as a file by the client browser program on the user's hard drive, and the browser automatically sends that information with any browser request to that domain. The Cookie has a duration property as well.
In the typical Visual Basic client/server development environment, the Visual Basic program operates as the client, capturing any user input and responding to events such as a mouse movement. These user inputs, ranging from a mouse movement to the pressing of a keyboard button, leave the programmer with absolute control in managing the users choices and experience. The Web world of development has some important distinctions that frame the entire development model.
Since Active Server Pages operates completely at the server, from VBScript execution to storing of variables, the normal Visual Basic model does not apply. The VBScript source code and the objects it invokes and references, only come into play when a user connects to the server by processing a form page or selecting a URL. In short, Active Server Pages is only invoked when a user moves from one page to another. At this point when a browser requests a page, the HTTP request or transaction sends only the simple HTTP transaction, including the page requested along with form or URL fields and some general browser-related information like Cookies.
![]()
For a more detailed description, reference information on the HTTP transaction standard.
The simple HTTP record layout passed between Web server and Web browser creates a challenge for managing an application, one of the key challenges becomes tracking users as they move between pages. This includes maintaining, in an easily accessible way, some unique tracking ID, as well as any information that must be displayed or manipulated such as a shopping basket of selected products in an order processing system. Several standard workarounds have emerged for dealing with this challenge, and Active Server Pages builds on the best of these approaches.
A key challenge of Web programming is tracking each user from one page to the next and making information from their previous pages available on subsequent pages. This challenge can be met by using several basic approaches. But without Active Server Pages, the problem requires complex functions for generating unique IDs and difficult workarounds for keeping track of information or in other words, maintaining variables scoping during a user's working session.
The challenge of tracking a user's session requires generating a unique ID and then ensuring that on every subsequent page that ID and any added user information continues to be available. Two basic techniques prior to Active Server Pages for accomplishing this include:
If you use the second technique, you must ensure that the hidden field or URL variable gets passed to all subsequent pages and created as a new hidden field or URL variable on those pages. This is done to always keep the variable alive as a hidden form field or URL variable.
An enhanced approach to moving hidden fields around involves maintaining a variable's scope by using a database or text file on the server to store this information based on the unique ID. Constant retrieval every time a new page gets requested, and passing the unique ID from page to page as hidden fields and URL variables requires careful planning because all possible page selections and flow must be carefully considered to ensure that unique IDs are not lost.
Generating Unique IDs for Tracking Users
The step of creating a unique ID and saving it as a variable requires more effort than you would expect at first glance. After generating and saving a unique ID, the ID must be carefully managed for it to be available to all subsequent pages during the user's session. Approaches to generating unique IDs generally result from some combination of date and time, user input, and IP addresses or, by using a database to generate a unique ID such as a counter field in Microsoft Access. Unfortunately, all of these approaches require careful planning.
Take the approach of setting up a Microsoft Access table with a counter field and using the counter fields as the unique IDs for users entering the Web page. The first step becomes inserting a new record when the user first hits the page. However, since the insert of a new record will not return the counter field, a query must be done to retrieve that ID field. This is one example of the difficulties. You can't just request the last ordinal record because, of course, within a second or so of the insert command, a second user may have requested the same page and inserted a subsequent record. As a result, you must actually take some value such as date/time and insert that value as well so that you can retrieve the counter value. Even with this approach, you run the risk of having two transactions with same date/time values, down to the second. To further complicate things, you must have the date/time field available as a form field in the HTML document to pass it as a parameter.
Managing User IDs and Scoping with Cookie/Form/URL Variables
Regardless of how you generate your unique ID, the immediate challenge that follows becomes keeping that ID alive or always in scope from one page to the next. Without the Active Server Pages Session Object, one approach involves ensuring that every HTML page passes the ID as either a URL variable or a Form field depending on whether the page request results from a hyperlink or Forms processing. This would take the form illustrated in Listing 10.1
![]()
The empty string tests in Listing 10.1 determine if the variable exists because Active Server script returns an empty string when you attempt to reference an HTML field that does not exist.
Listing 10.1 HIDDEN_VARIABLES.ASP-Scoping Hidden Variables from Page to Page
<% 'check if parameters are from a form or URL '------------------------------------------ IF NOT request.form("router") = "" then 'Parameters are Form Based '------------------------- IF NOT request.form("userid") = "" then userid = request.form("userid") ELSE userid = 0 'New User END IF 'Scope Router Variable for page processing '--------------------- router = request.form("router") '---------------------------------- ELSEIF NOT Request.QueryString("router") = "" 'Parameters are URL Based/Hyperlink '---------------------------------- IF NOT request.QueryString("userid") = "" then userid = request.QueryString("userid") ELSE userid = 0 'New Users END IF 'Scope Router Variable for page processing '---------------------------------- router = request.QueryString("router") ELSE 'Variables not correctly passed, an error has occurred 'set error routing flag '---------------------------------------- router = 0 'Error Routing for Lost Variables 'close IF, all variables set at this point '------------------------- END IF %>
Cookies drive another approach to managing user IDs from page to page. In a Cookies-based model, no hidden information must be moved from page to page because the Cookie provides a variable that the server writes to the browser and the browser stores locally by domain name. As a result, whenever the browser requests a page from a site within the domain name, such as http://www.melnick.com, the browser will pass the Cookie variable along with the rest of the request to the Web server.
Listing 10.2 shows how a Cookie evaluation would simplify the process of scoping user IDs and routing variables as compared to managing the process from page to page. A point here much more important than the complexity of the code comes from the problem that users may not necessarily move in a linear process through your Web pages. Users may bookmark and jump directly to a page or type an alternative URL directly into a browser, or click the Refresh button or the Back button. All of these maneuvers create uncertainty or more errors in the Listing 10.1 approach to managing users. Unlike the the approach with hidden variables being kept alive, with Cookies, the variables are passed regardless of which page the user starts from. Your code loses its dependence on any order of page processing.
![]()
Cookies have received a lot of attention in recent months, and browsers including Microsoft's IE3.0 currently enabled features to limit and potentially disable the use of Cookies.
![]()
In both Netscape 3.0 and IE 3.0, a flag can be set by the user that forces the browser to prompt the user every time a site attempts to write a Cookie. This prompt provides the user with the option of rejecting the Cookie. In addition, third-party programs are currently available to effectively disable Cookies as well. In practice, Cookies have become so prevalent that setting the prompt becomes quite annoying and as a result will probably not be actively used by any but the most vigilant and fearful users.
Listing 10.2 COOKIES_VARIABLES.ASP-Cookies and User IDs
<% 'check if cookie variables are available '------------------------------------------ IF request.cookie("router") = "" then 'Cookie Not Yet Set '------------------------- userid = 0 'New Account router = 0 'New Account '------------------------- 'route to new account area and set cookie '------------------------- ELSE userid = request.cookie("userid") router = request.cookie("router") 'close IF all variables set at this point '------------------------- END IF %>
Without the use of Cookies, or when Cookie use only tracks the user ID and not other supporting information, the process of passing the variables from page to page requires the use of both hidden fields and URL links that add variables into them. As illustrated in Listing 10.3 both Form and URL variables must be utilized, so the user can't move to another page without passing the variables you have assigned.
Listing 10.3 FORMURL_FIELDS.ASP-Scoping Variables Through HTML
<html><body> <FORM ACTION="next.asp" METHOD="post"> <!-------------------------------------------> <!--- hidden fields are added to URL link ---> <!--- so the link passes values ---> <!-------------------------------------------> <a href="profile_top.asp?userid=<%=userid%>& router=<%=router%>">Next Page </a> <a href="profile_display.asp?userid=<%=userid%>& router=<%=router%>">Next Page </a> <!-------------------------------------------> <!--- hidden fields are stored for passing---> <!--- in form submit action ---> <!-------------------------------------------> <INPUT NAME="router" VALUE="<%=router%>" TYPE=HIDDEN> <INPUT NAME="userid" VALUE="<%=userid%>" TYPE=HIDDEN> <CENTER> <INPUT TYPE=SUBMIT VALUE="Continue" NAME="btn"> </CENTER> </FORM> </body></html>
The methods discussed previously provide a framework for how most of the CGI-based programs currently operate with respect to variables, scoping, and user tracking. These techniques are based on a combination of HTML fields, Cookies, and unique ID generation. All these techniques become more stable and more insulated from the developer in the Active Server Pages Session and Application Objects.
Active Server Pages provides an object model, which insulates the developer from all of the challenges relating to tracking users and generating unique IDs. The Session and Application Objects, not only provide support in generating unique IDs and maintaining the scope of variables, but they also implement the beginning of an event-driven model for developers. Active Server Pages defines an application as an execute permission enabled directory served by an Internet Information Server. As a subset of the application, a session results when a browser requests a page from an application directory. The session involves one unique browser, so as other browsers request pages from the same application directory, they invoke additional sessions.
The first user to request a page from a directory that makes up an application invokes both an Application and a Session Object. As subsequent users request pages, they invoke additional Session Objects. The invoking of an Application Object kicks off the Application OnStart event, which executes scripts stored in the global.asa file. In addition, you can append variables and objects to the Application Object as new properties. When a developer's .asp file adds a new Application property, the Web server memory space is used to store the variable for use by future .asp files invoked.
As a browser requests a page or file from the application directory, the Web server checks to see if that browser is involved in an active session. If not, the Web server returns the requested page to the browser and the Web server also writes a session ID value as a Cookie to the browser. By writing this Cookie, the Web server has provided a unique IDfor tracking this user session or browser during the scope of the session. The Web server maintains this ID and also monitors the Timeout property set for this session. If the time-out expires, the Web server abandons all information associated with this session. In addition, at the same time that the Web server writes the Cookie, it processes the OnStart event, executing any scripts stored in the global.asa for the Session OnStart event. Similar to the Application Object, Session Objects can have additional properties appended, enabling the storing of variables and objects for use by .asp files processed during that browser's session.
Developing an application primarily involves establishing a directory that is served by an Internet Information Server and has execute permissions. This directory location contains the source code for the application. The source code includes individual files that follow the naming convention name.asp. Web browsers will request these files similarly to the way files ending in .htm or .html are requested. In addition to general ASP pages, each application can have a single file named "global.asa," which stores scripts for the OnStart and OnEnd events for applications and sessions.
![]()
Though not explicitly restated here, in order to use Active Server Pages, the directory containing the described files must reside on a Windows NT Server with Internet Information Server set up for your directory.
Building an Active Server application requires a planned use of the hierarchical and logical directory trees created when configuring the Internet Information Server. In general, a single-served directory forms the application. All source files, including files ending in .asa and .asp will reside in a single-served directory. For more complex applications, a series of directories might be appropriate. In Table 10.1, the Root directory contains all pages associated with non-registered users. This area might use Session Objects to do extensive tracking on the pages visited to try and build profiles on non-registered visitors. In the Root/Members directory, on the other hand, the purpose of the Session Object might be much more focused on the maintenance of logon status and member permissions. Finally, the Root/Secure/directory would maintain Session information on administrative privileges available and perhaps support the maintenance of a comprehensive audit trail.
Table 10.1 Sample Directory Structure
Directory | Description |
/Root/ | All visiting or non-logged on users request pages |
/Root/Members/ | Log on page and all subsequent pages that require a logged in user |
/Root/Secure/ | Administrative area for site administrators to manage member accounts and system settings |
The key point to remember here is the scope of the Application and Session Objects. Within each of the different directories, the Application Objects and Session Objects are completely different, with different roles, different scopes, and no relationship between the objects in separate directories.
The global.asa file provides the Application and Session Objects with the script, if any, to be invoked on the OnStart and OnEnd events for Application and Session Objects. Scripts for the OnStart and OnEnd events exist within the script tags with the RunAt property set to Server. These script tags can contain functions that can add properties to the Session and Application Objects for use bysubsequent .asp files within the scoped session or application.
The following provides a sample of how a global.asa file might be utilized. In the Application Object, The OnStart event adds properties to the Application Object to initialize a series of variables for maintaining information on how many members and visitors have come to the site since the application started. In contrast, the Session Object deals with a specific user starting a new session. The OnStart event for the Session Object increments application-wide information and initializes user-specific information. In addition, the Session Object OnStart event alters the default Timeout setting and sets the time-out to 30 minutes for this session, which means that after 30 minutes of no page requests the session will be abandoned. Finally, when the Timeout expires, and the Session Object OnEnd event is invoked, this event decrements application-wide tracking information.
ON THE WEB |
http://www.mcp.com/que/aspThe actual source code for the global.asa described can be found on the Web site for this book. |
Source code must be contained in an .asp or .asa file. This requirement stems from the method the Internet Information Server uses to process Active Server Pages. When the Web server receives a request for a file, the Internet Information Server first checks the registered ISAPI filters. Active Server Pages rely on an ISAPI filter to catch .asp files prior to returning anything to the Web browser. These files are then processed by the ISAPI filter, which strips out all "<%%>" tags, compiles VB Script, invokes any components called, while making Application and Session Objects available during the processing of all Active Server scripts. All of this occurs prior to the Web server returning the results to the Web browser.
![]()
ISAPI filters can be viewed by opening the Registry and looking under the IIS related setting in the current control set. Review Chapter 2, "Understanding Windows NT and Internet Information Server," for more information on the Registry.
![]()
.asp and .asa files are just standard text files. This enables you to use any editor to manage your source code. Microsoft's Source Safe, originally designed for managing source code associated with C++ or VB project files, would be an effective tool for tracking version and checkout status on a multi-developer project and Internet Studio could provide script development support as an .asp file development tool.
Any type of file can exist in an Active Server Application directory including HTM/HTML, graphic images, video, sound and .asp/.asa files. The important distinction here becomes that only .asp/.asa files invoke the filter that makes Session and Application Objects available during script processing.
The Internet Information Server also contains features such as Server-Side Include, which you may utilize to further enhance your application management capabilities. This capability enables you to insert .asp or other files into a requested .asp file prior to the Web server invoking the ISAPI filter we discussed. These Server-Side Includes will be processed prior to script execution. The Server-Side Include features extend your ability to store files in different directories while still maintaining a single application directory for purposes of Application and Session Object scoping.
![]()
Be careful using Server-Side Includes during development. The IIS keeps track of the last modification date/time of files, but the IIS caches frequently used files in memory. In other words, a direct page request causes the Web server to check if a cached file has been modified since its last execution. If it has been modified, the Web server recompiles the file. Unfortunately Includes do not follow this checking process and as a result do not get recompiled. In these cases the Web server must be restarted to flush cached .asp files.
In leveraging Application and Session Objects for the development of your application, carefully consider what information and objects should be stored at the application and session level. A good example of the value of Session Objects is the storing of a user's logon status for security. However with a misunderstanding of the Session Object's scoping, a major security hole could be created. The primary Application and Session Object methods, properties, and events include:
Abandon Method | Session |
Timeout Property | Session |
SessionID Property | Session |
OnStart Event | Session/Application |
OnEnd Event | Session/Application |
The most exciting feature of the Application and Session Objects involves the ability to scope the objects beyond a single page and more important, the ability to scope the Session Object to a single user. Specifically, users invoke the Application Object when they request a page from an application directory for the first time since the Web server last started. This Application Object lives on from that moment until all sessions time-out or the Web server restarts. In contrast to invoking the Application Object, users invoke the Session Object when they request a page from a browser that is not currently involved in an active session. The Session Object, unlike the Application Object, will time-out based upon a 20-minute default, or a custom Timeout property, which you can set at runtime.
![]()
Avoid the temptation to store everything at the session level. While at first the convenience of the Session Object can lead to caching everything about the user, remember that all this information must be maintained in the memory space of the Internet Information Server.
Once a user invokes a Session Object, all the Session's properties and methods become available at runtime every time that same user requests an .asp file. A user's session at the Web site now can be managed through the Session Object. As long as error trapping addresses the situation in which a user times-out, you now have complete control of a user's session and the ability to add properties to the Session Object. These properties can include anything from strings and status flags to database RecordSet Objects. The Session Object and its scope now create the first stable method for developers to manage a user's experience at a Web site, as a user moves from page to page or even from your site to another site and back to your site.
![]()
The Internet Information Server (IIS) manages the Session Object by writing a Cookie, or long integer, to the client browser. If IIS restarts, the Session abandons, or if the browser prevents Cookies, the Session Object will attempt to re-initialize on every page request.
The scope of the Session Object must be understood in the context of the Web. The Internet Information Server creates the Session Object by writing a long integer Cookie to the client browser and maintaining the long integer key and related properties such as the Timeout property and last hit date/time in the Web server's memory.
The event model available in the Session and Application Objects represents a beginning to bringing event-driven programming to the Web, but stops short of providing what you may be hoping for. Because the Active Server Pages process at the Web server and not the client, your source code can not respond to the range of events that the client handles, such as mouse movements and keyboard presses. Instead, your code is invoked when the user processes a form or clicks a hyperlink. These events generate a request to the Web server, which invokes your source code.
The Application and Session Objects provide two events each -the OnStart event and the OnEnd event. The client invokes these events when:
When a user invokes an Application or Session event, you can execute functions on the Web server. All source code invoked by Session and Application events must be stored in the global.asa file within an application's directory. The format of this text file follows the model in Listing 10.4.
Listing 10.4 GLOBAL.ASA-Sample App/Sess Event Code
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Application_OnStart END SUB </SCRIPT> <SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Application_OnEnd END SUB </SCRIPT> <SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Session_OnStart END SUB </SCRIPT> <SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Session_OnEnd END SUB </SCRIPT>
The scope of variables used in the global.asa scripts does not extend to the page actually requested prior to the event. This means that to store a variable for use in the current requested page or subsequent pages, you must save the information to an Application or Session Object property. The properties of these objects provide the only means for allowing scripts in .asp file to use the information available in the scripts run during these events. As a result, these scripts become useful primarily for saving information directly to a database or file, or saving information to Application or Session Object properties for use during the scope of the application or session.
Taking some time to understand how to leverage these events provides big benefits in helping your program manage a range of issues from enhancing the users Web experience to tracking site statistics. Don't make the mistake of overlooking the value of these events.
![]()
Session and Application events provide a key mechanism to manage user status control mechanisms such as logon security.
Like events, the methods currently available seem quite limited compared to the event-driven development environments you may currently use in non-Web based programs. However, this represents a powerful beginning for the Web programmer. The only methods currently available to Session and Application Objects include: Application Lock and Unlock methods and Session Abandon method.
The Application Lock and Unlock methods allow you to change values to the properties shared across the application without fear of creating conflict with multiple users potentially changing the same property values concurrently. This Locking control will seem intuitive to database developers working in multi-user environments which share this same risk.
The Abandon method plays a valuable role for managing a session. While during development and testing it can be useful for flushing a working session to begin again, it also has a role in the final application. For example, if a user requires the ability to logon and then perhaps logon again as a different user, the Abandon method could be used to allow the previously stored logon information to be cleanly dumped for a new logon.
These methods provide important functionality in utilizing the Application and Session Objects, but for real functionality you must look to the properties provided and use the capability to add properties to the Application and Session Objects.
At first glance, the list of properties currently available appears quite unimpressive. But the real secret lies behind the built-in properties in the ability to add properties dynamically. Still the two built-in Session properties play an important role in all application development and should not be overlooked. The available properties include: Session SessionID property and Session Timeout property.
The capability to add properties on-the-fly, provides the developer with an approach to maintaining persistence or state. By having a server-based Session Object to manage variables, a user's activities and input can be used during their entire session or visit to your Web application. The capability to build your own variables will be demonstrated later in this chapter and extensively in the case study provided in the appendices of the book.
The Application events and methods provide the infrastructure necessary to maintain application-wide information that can be leveraged for managing all users within an application. Uses range from tracking the number of users currently active to dynamically altering content provided to a particular user, based on the activity of other users at large. These features lay the foundation for building interactive communities and more. To understand the use of these events and methods, the following overviews the specific capabilities provided.
The Application OnStart event can be likened to the initial load event of an application. This is not the loading of a single client, but rather the load event of the multi-user Web-based application. As a result, one use would be to initialize a series of variables that you need to frequently access in your application. The following example in Listing 10.5 opens a database and assigns a recordset of system error messages to the Application Object. As a result of loading this object, now any page processed can reference the recordset during execution and can utilize the recordset to loop through and display a particular error, based on a given situation.
Listing 10.5 SAMP_EVENTS.ASP-Sample Application OnStart Event
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Application_OnStart REM Open ADO Connection to Database Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open("DSNName") RS = Conn.Execute("SELECT * FROM TblSysMessages;") REM Set Recordset to Application Object and Close ADO If rs.recordcount <> 0 then application.lock Set application.ObjErrMsg = RS application.unlock Else Rem Error Condition End If rs.close conn.close END SUB </SCRIPT>
![]()
The loading of the database recordset involved the Server Object and the ADO database Connection Object, which is discussed in more detail later in Part III of the book
The Application OnEnd event can be likened to the close of active forms or an application, however it provides more than that because it manages the environment for not just a single-user system, but for the multi-user Web-based environment. As a result, one use would be to flush all temporary user accounts that may have been created during the course of the day. This type of activity was previously available only by using time stamps and scheduled batch programs, running as services in the background. Now, when all users time-out, a database cleanup (or any other type of cleanup) can take place. The following example runs a SQL statement to purge all partially completed orders taken by a Web-based order processing system.
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Application_OnEnd REM Open ADO Connection to Database Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open("DSNName") RS = Conn.Execute("Delete * FROM Orders where complete_status=0;") conn.close END SUB </SCRIPT>
Similar to database record and page locking, Application locking simply ensures that no other user has simultaneously attempted to update the Application Objects property. This locking feature only applies to the application, and not the Session, Object and should be followed to avoid creating any conflict or lost data.The following section of code shows you a specific use of the Session OnStart event.
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Session_OnStart application.lock application("counter") = application("counter") + 1 application.unlock END SUB </SCRIPT>
Adding properties to the Application Object provides one of the key values of the Application Object model. Application properties added this way are similar to global constants and variables in Visual Basic.
Practical uses of the Application properties include the ability to cache information frequently used to conserve resources. Current Web development environments require either database/file lookups or the passing of information from one page to the next in hidden fields. The first approach requires extensive resources as an application's load grows, and the latter approach becomes difficult as users hit the refresh and back buttons or bounce from page to page through direct typing of URLs.
With the Application Object, information can be saved to an Application Object property as the result of a single lookup. From then on, any user can access that information. For example as a Web-based store opens its doors as a result of the first user request (Application OnStart Event), a store's opening greeting (including number of visitors, date/time, or current sale items) can be saved to an Application property as illustrated in the following code sample.
Application.lock Application("dateinfo") = date Application("timeinfo") = time Application("visitors") = Application("visitors") + 1 Application.Unlock
As a result, all subsequent pages can display that information as part of a standard greeting as shown in the following code.
<HTML> <BODY> Welcome to our store, open for business since <%=Application("timeinfo")%> on <%=Application("dateinfo")%> with <%Application("visitors")%> so far. </BODY> </HTML>
More important than cached activity information, resources can be conserved by limiting the number of times components must be brought in and out of memory on the server. If you run a database intensive site, as many of us do, you may start your application by placing statements in every page to load DLL's into memory.
Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open("DSNName") RS = Conn.Execute(SQL) conn.close
This process, not only loads the Connection Component DLL if it is not currently loaded, but also closes the object, allowing it be taken out of memory. For a frequently accessed site, it may make more sense to load the DLL when the application loads and leave it in memory for frequent use.
Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open("DSNName") Application("conn") = Conn
By loading the Conn Object into the Application Conn property, it can now be referenced by all pages for use.
set db = Application("Conn") set rs = db.execute(sql)
![]()
The preceding examples provide only a starting point for the range of uses the Application Object can play in the ASP application model you develop. Take the time to think through the activity and caching issues that relate to your application before implementing a model for your use of the Application Object.
The Session Object, more than the Application Object, drives your Web-based environment. Look closely at how the deceptively small number of methods and events can completely streamline your method for managing a user's experience as well as your system level tracking and control of that user. The Session Object, like the Application Object, enables new properties to be defined on-the-fly. And more importantly, the Session Object properties, like those of the Application Object, can be referenced on any page, anywhere, and any time during that active session.
The SessionID provides, without a doubt, the pre-built property to watch. This property provides the persistence in a user session you should be looking for, but the OnStart event, OnEnd event, and the Abandon method also play a valuable role in managing your application. The following sections document basic application of the prebuilt events and methods before a more practical discussion of how to put these to work.
The Session OnStart event can be likened to the initial load event of a form or application. It provides a mechanism to identify the first activity of new users and allows the initialization of whatever user information your application requires for managing the user session. At the OnStart event you may reference Application Object properties to track the new user in the context of your multi-user environment, but you will also want to bring into existence any user-specific information you need for managing that user's session. The event kicks off the script setup in the global.asa for the Session OnStart event in the following form.
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Session_OnStart Rem Load User Specific Information Session("NewUserStatus") = 0 Rem Load Application level info Application.lock Application("usercount") = Application("usercount") + 1 Application.unlock END SUB </SCRIPT>
![]()
Though we are discussing the use the global.asa in detail, don't lose site of the fact that you don't need to event create any functions in the global.asa or even a global.asa file at all.
The Session OnEnd event can be likened to the close of active forms or an application. However, this event does not require user action. In fact most often, the OnEnd event will be triggered by user inaction or time-outs. This event most often provides a mechanism for cleanup or closing of open resources. The system will lose all session information after this event, so any session information that you want to save must be saved during this event.
This event can be invoked by the user if he hits a page that executes the Abandon method. The Abandon method, and the Timeout property provide the two mechanisms for terminating a session. An example of clean up that can be done at the end or termination of a session has been illustrated in the following sample of code.
![]()
A crash or stopping of the Web server also terminates events, because the Web server memory space is where all session and application information resides.
<SCRIPT LANGUAGE=VBScript RUNAT=Server> SUB Application_OnEnd REM Clean up user activity information Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open("DSNName") SQL = "Delete * FROM UserActivity where sessionID = " & session.sessionid & ";" RS = Conn.Execute(SQL) conn.close END SUB </SCRIPT>
The Active Server creates the SessionID when a user first requests a page from an Active Server application. The SessionID gets written as a Cookie with a long integer value to the browser and provides the core mechanism the server uses to track session information stored in memory. You should not use the SessionID as a unique ID to track users across multiple sessions because the value's uniqueness is only guaranteed for the current application. This value gives your application the time it needs to generate an ID that can be used across multiple sessions and provides a unique ID for all sessions currently running. You can reference the SessionID on any page in the form
session.sessionID
This value provides a key mechanism for managing users as they move from page to page, and it relieves you from the responsibility of trying to uniquely track individual users during a multiple-page request session.
The server stores the Session Timeout property as a long integer that represents minutes and defaults to 20. The server takes full responsibility for tracking this 20-minute period. Timeout is tracked from the last date/time a page request is received by the browser. The Timeout property can be altered at runtime where you may set it in the Session OnStart event or any subsequent page. In determining how you will manage this property, you should consider the rate of hits by a single user during a session. For sites with long intervals between page requests, such as pages that require research, long review, or large amounts of input, you may want to increase the Timeout where more rapid sessions may require a shorter Timeout. Changing this property takes the form:
session.timeout = 30
![]()
Once the Timeout occurs, all session information is lost and the next page request will be treated as a new session.
The Session Abandon method provides a vehicle for you to force a session to terminate. Uses include the situation in which your user community takes the time to log off or in which you implement a discrete site exit page that invokes the Abandon method. This method takes the form:
Session.abandon
![]()
During development, a page with the Abandon method provides a useful mechanism for restarting sessions. Often in development, a lingering session can make testing difficult.
The Session Object provides a rich environment for managing user sessions. The following sections show you a few examples of how you can put this object to work for developing efficient applications. As described in the first part of this chapter, the challenge of managing a user session has historically required difficult, code-consuming techniques for generating unique IDs and then for keeping session-related information alive from page to page.
Generating a Unique ID to Manage Users
As illustrated in Listing 10.6, the SessionID property takes care of most of the first problem by generating a session ID to keep track of a user's session. However, during this process if you need to track users over a longer life than just one session, you still need to create an ID that guarantees uniqueness for your application. This process generally involves a database for storing this user and his related information. Once you design a database to store user information, you can rely on the wealth of features in databases to generate a guaranteed unique ID. A simple example of generating a unique user ID involves leveraging the counter field of a Microsoft Access database. The following code example in Listing 10.6 uses the current date and the SessionID to insert a record and then queries the table to retrieve the counter value once the record has been created. As a final step, the example sets the new counter value to a new session property for reference on subsequent pages.
![]()
Certain variable status designations such as the logonstatus variable have been subjectively assigned values for tracking that in no way reflect any pre-set or required approach to the tracking process.
Listing 10.6 SESSIONTRACKING.TXT-Managing the Tracking of Users with Session Variables
<% Set Conn = Session("conn") Select Case session("logonstatus") Case 1 ' Already Past finished this insert step msg = "<Center><h2><blink>Please Record your new Member ID: " & session("memberid") & " </blink></h2></center> <h3>Your Ideal Mate Profile has already been saved, please complete the process and relogon in edit mode to alter you profile </h3>" Case 2 ' Proper Status for Insert of new account set rsInsert = Server.CreateObject("ADODB.Recordset") Conn.BeginTrans rsInsert.Open "Members", Conn, 3, 3 ' --------------------------------------- 'Insert Record Using AddNew Method of ADO ' --------------------------------------- rsInsert.AddNew rsInsert("SignOnID") = session.sessionid rsInsert("AdmCreateDate") = Date() rsInsert.Update Conn.CommitTrans rsInsert.Close ' --------------------------------------- 'Look up generated record by referencing SessionID/Current Date ' --------------------------------------- sql = "SELECT Members.SignOnID, Members.memberid, Members.AdmCreateDate FROM Members WHERE (((Members.SignOnID)=" & session.sessionid & ") AND ((Members.AdmCreateDate)=Date()));" Set RS = Conn.Execute(sql) msg = "<h2><Center> Please Record your new Member ID: " & rs("memberid") & " </center></h2>" ' --------------------------------------- ' Set Session Object with memberid value ' --------------------------------------- memval = rs("memberid") session("memberid") = memval session("logonstatus") = 3 rs.close End Select %>
![]()
Time and User IP address can be added to the record inserted into the database for greater certainty of uniqueness.
Using the Session Object for Caching User Information
Once the user has a unique ID, the next use of the Session Object focuses on the ability to cache user information you would have previously stored in a database or text file for constant lookup and editing. The process of querying a file or database every time users hit a page just to make basic information about their sessions or accounts available reflects the status quo for current Internet applications. This includes the lookup of a shopping basket for a user shopping in a web-based store or the lookup of account information for personalizing a user page. While some developers attempt to move that information from one form page processed to the next, this problem creates serious challenges for application design.
A good example of a Session Object property would be storing a system message for display on subsequent pages as well as trapping basic name and last time online-type information. Like the Application Object, properties can range in complexity from integers and string values, to RecordSet Objects. The following example provides for storing personal information and redirection following a successful logon.
<% '----------------------------------------------------- ' Lookup User Info '----------------------------------------------- sql = "SELECT members.admonlinedate, Members.MemberID, members.pass, Members.FName, Members.LName, Members.AdmExpDate, Members.AdmStatus FROM Members " sql = sql & "WHERE (((Members.MemberID)=" & request.form("memberid") & "));" set db = session("conn") set rs = db.execute(sql) '----------------------------------------------------- ' Logon Fail '----------------------------------------------- If rs.eof Then 'No Record Found Bad ID rs.close session("msg") = "<h3><center>No Member ID equaling <em>" & request.form("memberid") & "</em> exists</center></h3>" response.redirect "fail.asp" Else '----------------------------------------------------- ' Success Logon Approved, Load Session and Status '----------------------------------------------- session("logonstatus") = 1 session("memberid") = rs("memberid") session("AdmOnlineDate") = rs("AdmOnlineDate") session("fname") = rs("fname") 'First Name ' Update User Database Record with Last Logon Date sql = "UPDATE Members SET" sql = sql & " Members.AdmOnlineDate = #" & Date() & "#" sql = sql & " WHERE Members.MemberID=" & request.form("memberid") & ";" set rs2 = db.execute(sql) rs.close response.redirect "start.asp" end if %>
As illustrated in the previous code sample, the statement setting the logonstatus property equal to a value creates the logonstatus property as a new Session property. No special statements to dimension the property are required, and once this information gets loaded into the Session Object, it can be referenced on any page requested by the browser that is sending the matching SessionID. The process for referencing the properties only requires a single statement of the form:
session("propertyname")
For managing an application, the Session properties can play the role of tracking the user status and security information. Because Session properties exist at the server without any information passed to the browser except for the SessionID, session properties provide an effective method for managing logon and other statuses. An .asp file with no purpose other than the validation of a user's logonstatus, can be included using the Server-Side Include feature of IIS. This approach to user authentication provides an effective method for trapping any user attempting to request a page he/she doesn't have authority to view. This method relies on the Session properties alone and not on any NT- based security controls as illustrated in the following excerpt of code.
<% Select Case session("logonstatus") Case 0 'New Session No Status session("msg") = "<h3><center> Your are currently not logged in or your logon has timed out </center></h3> Please logon to continue your session, sorry for any inconvenience</h4>" Response.Redirect "logon.asp" Case 1 'Authenticated User Properly Logged On Case 2 'New Member in Sign Up Process first page Case 3 'New Member in Sign Up Process Record Created End Select %>
The process of actually validating a user after she enters a user account and password further illustrates how to manage a site's security and user 2's status through the Session Object. The following example in Listing 10.7 builds on the previous code, which simply adds Session properties after a successful logon, and in this case evaluates all possible results of a user's attempt to log on. The following example relies heavily on the Response Object's redirect feature to route the user, based on the results of the logon validation.
Listing 10.7 LOGONVALIDATE.TXT-Validating and Redirecting Users Requesting .asp Files
<Script Language=VBScript runat=server> Function redirect() Session("msg") = session("msg") & " Please enter a valid Member ID and Password, if you have forgotten your ID try our Search based on First Name, Last Name and your password" Response.Redirect "logon.asp" end function </script> <% '---------------------------------- '_--------------------------------- 'Level 1 Basic Validation Testing '-_-------------------------------- '---------------------------------- 'Test for Already Logged In '---------------------------------- if session("logonstatus") = 1 then 'Already Validated session("msg") = "<h3><center>You are already logged in</center></h3>" Response.Redirect "start.asp" 'Test for Entry of Member ID prior to Running Search '---------------------------------- elseif request.form("memberid")="" then ' NO Member ID Entered session("msg") = "<h3><center>No Proper Member ID Entered</center></h3>" Redirect 'Call Function to Exit Back to Logon Screen 'Run Search '------------------------------ else 'Run Database Lookup sql = "SELECT members.admonlinedate, Members.MemberID, members.pass, Members.FName, Members.LName, Members.AdmExpDate, Members.AdmStatus FROM Members " sql = sql & "WHERE (((Members.MemberID)=" & request.form("memberid") & "));" set db = session("conn") set rs = db.execute(sql) end if '---------------------------- '---------------------------- 'Level 2 Validation Testing '---------------------------- '---------------------------- 'Member ID Entered Now Run Search for Record '------------------------------------------- If rs.eof Then 'No Record Found Bad ID rs.close session("msg") = "<h3><center>No Member ID equaling <em>" & request.form("memberid") & "</em> exists</center></h3>" Redirect 'Call Function to Exit Back to Logon Screen 'Customer Record Found Now Check Password '----------------------------------------- elseif not request.form("password") = rs("pass") then rs.close session("msg") = "<h3><center>Member ID OK but Bad Password Entered</center></h3>" Redirect 'Call Function to Exit Back to Logon Screen 'Password OK now Check Expiration and Status '------------------------------------------- elseif not rs("admstatus") = 1 and rs("admexpdate") > date then rs.close session("msg") = "<h3><center>Not Active or Expired Account</center></h3>" Redirect 'Call Function to Exit Back to Logon Screen '----------------------------------- '----------------------------------- ' Level 3. Success Logon Approved, Load Session and Status '----------------------------------- '----------------------------------- Else session("logonstatus") = 1 session("memberid") = rs("memberid") session("AdmOnlineDate") = rs("AdmOnlineDate") session("fname") = rs("fname") ' Update users last online date sql = "UPDATE Members SET" sql = sql & " Members.AdmOnlineDate = #" & Date() & "#" sql = sql & " WHERE Members.MemberID=" & request.form("memberid") & ";" set rs2 = db.execute(sql) end if rs.close response.redirect "start.asp" %>
![]()
See "The Rest of the Response Object" for more information about the redirect feature, in Chapter 12.
The preceding example in Listing 10.7 uses a script tag to create a callable function for redirecting the user in the event that they fail the logon process at any step.The user is forwarded to the start page with a logged-on status only in the event that she passes all checks including password, account number, currently active status, and valid expiration date.
The Session and Application Objects form the building blocks for good application design. By understanding the features that enable user and application management, you now move towards enabling the specific features of the Web-based application you intend to build. Based on an understanding of the VBScript syntax from earlier chapters you now build upon the remaining Active Server objects including Server, Request, and Response, as well as exploring the specifics of components. These objects provide a complete understanding of the Active Server application development infrastructure, which is at your disposal.
From here, you will progress through the following chapters on the road to mastering the Active Server Page:
© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.