| Programmer to ProgrammerTM | |||||
|
|||||
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
| |||||||||||||||||||
| The ASPToday
Article February 20, 2001 |
Previous
article - February 19, 2001 |
||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
| ABSTRACT |
| ||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||
| Article Discussion | Rate this article | Related Links | Index Entries | ||||||||
| ARTICLE | |||||||||||
One of the issues in web development is how to create a coherent application with several stand-alone HTML pages. Since HTTP is a stateless protocol, each request to the server is taken as an independent request.
ASP's intrinsic session objects provide an easy way to maintain the state of the application for each user, which requires no special programming. The state is maintained by using a sessionid cookie in the client (browser) and its related state information in web server memory.
However, we have been warned about the inability of session objects to work in web farms. Though, using a local director with a "sticky bit" set on maintains session in web farm, it is does not provide true load balancing. There are several out-of-box solutions to overcome this issue, such as software artisan's SA-Session Pro. These solutions are developed in COM and use database or NT files to store the session variables. All the servers in the farm use the same database or the file to retrieve the state information.
One of the most nagging questions in all these solutions is, "is it scalable ?" ASP Intrinsic session objects work well in single server co-hosting, but fail in web farms. Database-driven COM session objects work in web farms, but have severe restrictions in co-hosting. The third party co-hosting provider will not allow the COM DLLs to be registered in their machine. Co-hosting can even be forced on big-sites when they move in for mirror-site in other regions.
The scalability can be achieved by splitting the application into n-tiers, and using an independent language to develop each tier:

User-Interface Visual Control Objects are plain ASP files, which use the Common Business Object for processing. Common Business Objects and Data Access layers can either be ASP files or COM DLL.
The next obvious question will be, "Can we write a business logic object in pure script as the co-host will not allow COM". Amazingly, YES is the answer - we can do it in VBScript Version 5 and above. You can define class with properties and methods in ASP/VBScript like this:
<%
Class myClass
Private myVar1
Property Get var1
var1 = myvar1
End Property
Property Let var1(value)
mstrSessionID = Trimvalue
End Property
Public Function myFunc()
'blah blah.
End Function
End Class
%>
If say, later, there is a machine that can run COM objects, we can 'port' the CBO code to visual basic with some minor modification and compile scripts. Compiled COM objects provides more security as even a read access (like the recent IIS bugs) to the web server will not expose the business logic.
To implement the scalable session objects, three flavors are discussed here. Flavor 1 uses ASP/VBScript classes and database for storing the session variables. This can be employed in web farms. Flavor 2 is also written in ASP/VBScript but it uses memory for storing the session variables. This implementation is apt for single-server. Flavor 3 is developed using VB/COM and database for persisting the session variables. This flavor will provide good performance in Web farms.
Common Business Object (clsSession.asp) defines a class cSession with properties
Class_ Initialize: This function will be called when the object is created. The function tries to retrieve the sessionid from the client. If the sessionid is not found or the corresponding session variables are not found in the server, it creates a new session.
Private Sub Class_Initialize()
InitializeVars
if RetriveSessionID Then
if Not Load(mstrSessionID) Then
CreateNewSession
End if
Else
CreateNewSession
End if
End Sub
Private Sub InitializeVars()
If IsEmpty(mstrSessionID) Then
mstrSessionID = ""
End If
If IsEmpty(mstrLogIP) Then
mstrLogIP = ""
End If
mstrStatus = ""
flgDirty = false
Set mcolSessionVars = Server.CreateObject("Scripting.Dictionary")
End Sub
Private Function RetriveSessionID()
RetriveSessionID = false
mstrSessionID = Getstring("SessionID", 38, false)
if Not Len(mstrSessionID) = 0 Then
RetriveSessionID = True
End if
End Function
RetriveSessionID uses Getstring function to read the sessionid from client. Getstring is a locally defined function, which searches the given key in querystring, form post and in cookie. This function is defined in i_util.asp
Private Function CreateNewSession()
Dim flgStatus flgStatus = CreateSessionID
flgStatus = flgStatus and StoreSessionID
if flgStatus Then mstrStatus = con_Active
mdtLogDate =Date & " " & Time
mstrLogIP = Request.ServerVariables("REMOTE_ADDR")
End if
Save
CreateNewSession = flgStatus
End Function
Private Function CreateSessionID()
CreateSessionID = False
mstrSessionID = GetUniqueId
mstrStatus = con_Active
flgDirty = True
CreateSessionID = True
End Function
CreateNewSession will create a new sessionid, save the sessionid in client and then to the database. UniqueID function is defined in daSession.asp. The Save function in turn calls daSave, which is defined in daSession.asp
Con_Active is a constant defined in the same file, while FlgDirty is used to track the changes and to prevent unnecessary database transactions.
Public Function Abandon() mstrStatus = con_InActive mdtLogOutDate = Date & " " & Time flgDirty = True Save End Function
Abandon function will be called to delete the session - it'll first update the status flag and LogOutDate, and then save the information:
Public Function Load(strSessionID)
Load = daLoad(strSessionID)
End Function
Public Function Save()
if flgDirty and mcolSessionVars.Count > 0 Then
Save = daSave
Else
Save = False
End if
End Function
The Load and Save functions help in persisting the data across the pages. Save will check for flgDirty to prevent useless repeated updates. As shown in the architecture diagram Common Business Objects does not know about the information storage and retrieval process. This functionality is handled in the separate layer (file) called Data Access Layer. The daLoad and daSave are functions defined in another file, daSession.asp. The advantage is, if we need to use memory instead of the database for persistence, or if there is a change in database design, then we need to change the file daSession.asp only.
In typical n-tier architecture, Data Access Layer and Business Layer communicate through a well-defined structure like ADODB.Recordset, or as a flattened string, or as XML. Here, for performance reasons we'll allow the Data Access layer to directly access the private variables of the Business layer. The file is included as shown:
<%
Class cSession
%>
<!--#include file="daSession.asp"-->
<%
End Class
%>
CAUTION: daSession.asp should be included before the End Class statement.
This layer is responsible for data storage and retrieval. It has two major functions - daLoad and daSave, which will be called from Common Business Object
The database has two tables, SessionHdr and SessionDet, with the following structure:
Table name: SessionHdr
| Column Name | Data Type | Comments |
|---|---|---|
| SessionID | Char(38) | To store the session ID. Primary key |
| LogIP | Char(16) | The IP address of the client. For tracking purposes. Non-Null |
| LogDate | DateTime | Session Creation Date time |
| LogOutDate | DateTime | Log out DateTime. Typically the time when user clicks Logout in the web page |
| Status | Char(1) | Status of the Session A - Active I - Inactive This can be used to time-out the session. In a typical timeout, Logout Date will be set to NULL and status set to 'I' |
Table name: SessionDet
| Column Name | Data Type | Comments |
|---|---|---|
| SessionID | Char(38) | Foreign Key. Links SessionHdr table and SessionDet table. |
| Name | Char(50) | Name of the session variable to be stored. Ex, 'Username' |
| Value | Char(100) | Value of the session variable. Ex, 'Joe' |
Private Function daLoad(strSessionID)
daLoad = false
Dim rs 'as recordeset
Set rs = server.CreateObject("ADODB.Recordset")
rs.Open Replace(Application("qry_Session_Active_PK"), _
":1", strSessionID), _
conDB, adOpenForwardOnly, adLockReadOnly
if not rs.EOF then
mstrSessionID = trim(rs.Fields("SessionID")) & ""
mstrLogIP = trim(rs.Fields("LogIP")) & ""
mdtLogDate = trim(rs.Fields("LogDate")) & ""
mstrStatus = trim(rs.Fields("Status")) & ""
Set mcolSessionVars = _
Server.CreateObject("Scripting.Dictionary")
Do While Not rs.eof
mcolSessionVars.Add trim(rs.Fields("name")), _
trim(rs.Fields("value"))
rs.movenext
Loop
flgDirty = False
daLoad = true
end if
rs.Close
Set rs = nothing
End Function
Private Function daSave()
Dim tstrKey daSave = false
Dim rs 'as recordeset
Set rs = server.CreateObject("ADODB.Recordset")
rs.Open Replace(Application("qry_SessionHdr_PK"), _
":1", mstrSessionID), conDB, _
adOpenDynamic, adLockOptimistic
if rs.EOF then
rs.addnew rs.Fields("SessionID") = mstrSessionID
End if
rs.Fields("LogIP") = mstrLogIP
rs.Fields("LogDate") = mdtLogDate
rs.Fields("LogOutDate") = mdtLogOutDate
rs.Fields("Status") = mstrStatus
rs.update
rs.Close
'Save Detail...
For each tstrKey in mcolSessionVars.Keys
rs.open Replace(Replace(Application("qry_SessionDet_PK"), _
":1", mstrSessionId), ":2", tstrKey), _
conDB, adOpenDynamic, adLockOptimistic
if rs.eof then
rs.addnew rs.fields("SessionID") = mstrSessionID
End if
rs.fields("name") = tstrKey
rs.fields("value") = mcolSessionVars.item(tstrKey)
rs.update rs.close
Next
Set rs = nothing
daSave = true
End Function
If the same sessionid key is found in the header table, then the record is updated, else a new record is added. Again similar action is performed on the detail table. The query is stored in ASP Intrinsic Application Object. For scalability, Use stored procedures.
With this groundwork, coding front pages will be easy.
Visual control object (ASP file), an Interface Utility, will create the session and initialize and provide a routine for accessing the client information. It also controls the database connection (through a separate include file)
The location of Common Business Object and its implementation should be transparent to this object. If we are using ASP/VBScript for Common Business object then we should include the files.
<!-- #include File = "i_db_conn.asp" --> <!--#include file = "clsSession.asp" -->
The i_db_conn.asp file will establish the connection to the database and provide the service through the variable conDB
Dim objSession CreateSession() Private Function CreateSession() Set objSession = New cSession End Function
CreateSession will create the new cSession object and provide the service through objSession.
Our ASP front end to try out this code will look like this:

Once we have entered some session data, we can retrieve it:

If any of the files need to use a session, they have to include i_util.asp and start using objSession as they use for the ASP Intrinsic session object. ObjSession need not be declared in the User Interface page.
For example, to store a data in the session:
ObjSession("myVar") = "myval"
To retrieve:
Response.write ObjSession("myVar")
To abandon:
ObjSession.Abandon
The object-oriented concept has concealed the implementation details and provides an easy programming model.
It is sane to ask, "If I'm running my site on co-host, why should I use a database for state maintenance" The architecture also allows you to scale down the program to maintain the state without the database, yet the front end programs continue to use objSession without any change. This is the key advantage of splitting the application into n-tier.
The Data Access Layer can be changed to store and retrieve data using ASP intrinsic session object. Common Business Object will have to amend include file name if there is a change in data access layer file name otherwise there is no change.
The new data access layer will look like this:
Private Function daLoad(strSessionID)
daLoad = false
mstrSessionID = Session.SessionID
mstrLogIP = Request.ServerVariables("REMOTE_ADDR")
mdtLogDate = Date
mstrStatus = "A" 'Always active
Set mcolSessionVars = Server.CreateObject("Scripting.Dictionary")
For iCount = 1 to Session.Contents.Count
mcolSessionVars.Add Session.Contents.Key(iCount), _
Session.Contents.Item(iCount)
Next
flgDirty = False
daLoad = true
End Function
Private Function daSave()
Dim tstrKey daSave = false
For each tstrKey in mcolSessionVars.Keys
Session(tstrKey) = mcolSessionVars.item(tstrKey)
Next
daSave = true
End Function
All other files remain unchanged.
This is the interesting part. Since ASP/VBScript and Visual Basic share the almost same syntax, it'll be easy to convert the ASP/VBScript code to Visual Basic.
ASP Intrinsic objects can be directly accessed from VB using OnStartPage function. However, it is often tough to debug the program. Therefore, we can avoid using ASP intrinsic objects in the VB/COM object and do some tinkering in our Visual Control Objects (i_util.asp) during instantiation.
Steps to create the COM component
Some common module files are also added (code is copied from ASP files). Next, compile the program.
In i_util.asp, change the Instantiating routine to the following:
Private Function CreateSession()
Set objSession = Server.CreateObject("Session.clsSession")
objSession.SessionID = GetString("SessionID", 38, False)
objSession.LogIP = Request.ServerVariables("REMOTE_ADDR")
objSession.Init
Response.Cookies("SessionID") = objSession.SessionID
Response.Cookies("SessionID").Expires = DateAdd("d", 1, Date)
End Function
As the COM object cannot read the cookie, we provide the session id value retrieved from the cookie and then initialize the object. Init function will check the validity of sessionid and creates a new id if necessary. The sessionid is stored in the cookie after calling the init function. In the previous implementations, the clsSession directly accessed the cookie value and stored the session id.
We have created a session object that is scalable from co-hostable without database to using in web-farm. The scalability is achieved by using the object-oriented design concept's property of implementation concealment and near common syntax of ASP/VBScript with visual basic. In all the flavors, the user-interface files are not changed. They have to include i_util.asp and use the objSession object for state maintenance.
However, when using VBScript for class definitions and object implementation, there will be a performance hit. The solution can be effectively used for sites, which starts in co-hosting environment and has immediate plans for dedicated/multiple hosting.
The security part of session is not handled in the program. Sessionid is directly stored on to cookie, which will expose the key of the database table. With some tweaking, it'll be possible to read other client's session values. It is strongly advised to encrypt the SessionID before storing on to the cookie. This will involve additional coding in Data Access Layer functions.
Using ASP Intrinsic session (flavor 2) will face a performance hit, as data is stored in two places, in ASP intrinsic session and clsSession. The idea is to use this a stepping-stone for an easy expansion at a later stage.
The full source code is provided. Please look at readme.txt file for implementation details. You'll need ServerObject's Guidmaker for unique id creation. The component is a free download and is available in http://www.serverobjects.com/products.htm#free?WROXEMPTOKEN=711612ZW37kEJTrwI1rvrPIrYD. You can also use database provided unique id.
|
| |||||||
| |||||||||||||||
|
| ASPToday is brought to you by
Wrox Press (http://www.wrox.com/). Please see our terms
and conditions and privacy
policy. ASPToday is optimised for Microsoft Internet Explorer 5 browsers. Please report any website problems to [email protected]. Copyright © 2001 Wrox Press. All Rights Reserved. |