Identifying the User
As I wrote in my previous article, identifying a user involves generating some kind of unique identifier and then including this identifier on each request from the user to the application running on the server. A unique identifier can be generated using any number of algorithms. It could be based on a random number, the current date and time, a Globally Unique Identifier (GUID), a UserID if the user is required to log on to your application, a record ID if you store state information in a relational database, or any combination of these.
To keep my sample code as simple as possible for example purposes, it generates a unique identifier using the server's current date and time, and then appends it with a five-digit random number. This value serves the same purpose as the ASP Session SessionID, but I'll refer to it as the UserID so as not to confuse the two. Theoretically, my algorithm could generate a UserID already in use, and so perhaps it should include code to check to see if a state file with this identifier already exists. If so, it could continue generating numbers until a unique value is found.
An even more robust approach is to use a Globally Unique Identifier (GUID) for the UserID. A GUID is a large, statistically unique, 128-bit number generated from the address of the Ethernet card in the current machine, the current time, and I think some voodoo magic. The GUID-generating algorithm should never produce duplicate numbers because it is capable of creating 10 million numbers unique to your computer's network card every second for almost 4,000 years. See Article ID Q176790 on the Microsoft Web site for sample Visual Basic code that can be used to generate a GUID.
Begin coding by opening the PublishersWebUI project created in the "Develop Scriptless Web Apps Using Visual Basic" article. Add a class module to the project, set its Name property to clsUserState, and set its Instancing property to 1 Private. Open the class module's code window, and add the following to implement the UserID property:
Private mUserID As String Public Property Get UserID() As String UserID = mUserID End Property Public Property Let UserID(ByVal lUserID As String) mUserID = lUserID ' Generate a new UserID if none exists. If mUserID = "" Then Randomize mUserID = Format(Now, "yyyymmddHhNnSs") & Format(Rnd * 99999, "00000") End If End Property
UserID is exposed from the class by using Property Procedures so that when the UserID property is set, a new value can be generated if an empty string is passed. We should expect this to happen only when a user first makes a request to the application.
Now that we have a way to generate a unique identifier, we need to modify the other parts of the code so that this value is always passed back and forth between the browser client and our application running on the server. I like to pass this value as a URL query string parameter. This requires that it be included on all hyperlinks in our application's HMTL code. Because the UserID value cannot be determined when the HTML code is created, our application must dynamically modify all HTML sent to the browser that includes a link back into this same application to include this user's UserID. This is known as "URL rewriting" by Java Server Page (JSP) developers.
One easy way to do this is to include a placeholder "token" for the UserID when authoring the HTML hyperlinks. For example, the search form for this sample application posts the form's contents back to the application. So it must be modified to include the following with the uid (UserID) token:
<form method="POST" action="PubWebUI.asp?uid={uid}&action=SearchProc">
All HTML (either from HTML files or generated by the application) that link back into our application need to contain this token. If even one link is missed, the user's state information will be lost when the user links to that page.
The next step is to modify the application code to replace the {uid} token with the actual value just before any HTML is sent. Recall that the sample application sends all user requests through a single WebMain method. To minimize the chances that HTML will be sent without the token being replaced, we can alter the code so that the only place an HTML string is sent back to the browser is at the end of the WebMain procedure. All other procedures simply populate a module scope string variable (for example, mHTMLSendString) with the HTML they want sent. The WebMain procedure handles changing the uid token to the actual value just before sending mHTMLSendString to the browser.
Again, taking advantage of the fact that all requests pass through the WebMain procedure, we can alter WebMain to attempt to retrieve the uid value from the query string and hand it back to the clsUserState object. Remember that the clsUserState.UserID is coded as a property procedure, and that is we attempt to set the UserID property value to an empty string, it will assume this is the first request for the user and generate a new UserID value. The centralized WebMain procedure is also a great place to restore state information as soon as the user's request is received, and to save it once the user's request has been processed.
Here's how the sample application's WebMain procedure looks with the previously mentioned modifications in bold type:
Private mHTMLSendString As String ' The HTML that is sent back to the browser. Private mUserState As clsUserState ' The state information for one user. Public Sub WebMain() On Error GoTo ErrHandler ' Try to restore this user's state information. Set mUserState = New clsUserState mUserState.UserID = gAspReq.QueryString("uid") Call mUserState.GetState Select Case gAspReq.QueryString("action") Case "SearchSend" Call SearchSend Case "SearchProc" Call SearchProc Case "DetailProc" Call DetailProc Case Else ' Otherwise send a default page. Call SearchSend End Select ' Rewrite any URLs in the HTML string to include the User ID. mHTMLSendString = Replace(mHTMLSendString, "{uid}", mUserState.UserID) Call gAspResp.Write(mHTMLSendString) ' Save this user's state information. Call mUserState.SaveState Exit Sub ErrHandler: Call gAspResp.Write(Err.Description) End Sub
Note that the GetState and SaveState methods for clsUserState are discussed as follows.
In the previous version of this sample code, each sub procedure was responsible for calling gAspResp.Write to send the HTML string. In this version, instead of calling gAspResp.Write, these procedures must be changed to instead put the HTML to be sent into the module scope mHTMLSendString variable so that it can be sent by WebMain. For example:
Private Sub SearchSend() Dim lHTML As String lHTML = LoadHTML("Search.htm") lHTML = Replace(lHTML, "{Filter}", mUserState.Filter) lHTML = Replace(lHTML, "{PublishersTable}", "") HTMLSendString = lHTML ' Instead of Call gAspResponse.Write(lHTML) End Sub