Charles McElwee

Subscribe to Charles McElwee: eMailAlertsEmail Alerts
Get Charles McElwee: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: ColdFusion on Ulitzer

CFDJ: Article

Extending the Usefulness of ColdFusion Client and Session Variables

Extending the Usefulness of ColdFusion Client and Session Variables

ColdFusion offers developers several different scopes for variables, each with specific characteristics and uses. Application variables are global, shared by all users of an application. Server variables are similar, but if your server hosts several applications, these variables are shared among all the users of all the applications.

Request-scope variables are very handy - especially in the application.cfm template - to set variables that are available to all ColdFusion templates invoked with one HTTP request, including within custom tags that are otherwise shielded from the other variable scopes. Locally scoped variables are general purpose and the most widely used. However, client and session variables are especially interesting. They're most often used to provide users with their own extended attributes for an application. "Personalization" of a Web site greatly enhances the user experience, and client and session variables are ideally suited to this purpose. But both time out, and are tied to a particular client machine. This article demonstrates an easy way to make client variables available to a user on any PC used to access the application.

Session Variables
All Web application servers that I'm aware of (e.g., JSP, ASP, and ColdFusion app servers) support session variables. It's important to understand that a session refers to one user's activity at a site. As long as the server continues to receive HTTP requests from a given client within some configurable time frame, the session variables persist. If no activity is detected within that time frame, the session is deemed to have ended and all session variables expire. After a session variable is set on one page of a ColdFusion application, it becomes available to that user on every other page of that application as long as each HTTP request occurs within the configured timeout period. To make session variables available to your CF application, simply include the session management attribute in your cfapplication tag:

 

<cfapplication name="myApp" sessionmanagement="Yes"
sessiontimeout="#CreateTimeSpan(0,0,5,0)#">

Although session variables may be used in many ways, one of the most useful is in conjunction with a login process. For sites requiring some level of security, session variables are an ideal way to time out a user's session and force a relogin. I don't recommend using them for something like a shopping cart unless they're coupled with a more permanent data storage mechanism. A user called away from his or her desk could potentially lose information that might have been difficult or time-consuming to find and/or enter. This would not endear that Web site to me, and I'd need a very good reason to return. Personally, I keep a shopping cart full of purchases just standing at the checkout at Amazon.com, and anytime I feel flush I can make a purchase, wherever I am. If this data was simply stored in a session variable, that wouldn't be possible.

One strength of session variables is their support of complex data types. Session variables are stored within the default session structure and you can nest your own complex data types within this structure, permitting very powerful data constructs. ColdFusion 5 offers the CFDUMP tag, which greatly aids in debugging templates that use complex data types. If you're still working with an older release, you can download the CF_ OBJECTDUMP custom tag from http://devex.macromedia.com/developer/gallery. Becoming comfortable with the notation required when manipulating complex data types can take some effort, and both tags offer clear, graphical views of your data, including arrays, structures, queries, and simple data types.

Since session variables are one of the three types of shared-scope variables stored in your server's memory (application and server variables are the other two), best practices insist that all references to them be enclosed inside a cflock tag to prevent corruption from concurrent updates. Such corruption can lead to performance problems, memory leaks, and server instability (see ColdFusion Locking Best Practices, TechNote 20370, at www.macromedia.com). Due to the overhead of locking variables as well as the hassle of coding the cflocks, I usually try to find another way to accomplish what I need to do.

If your application uses shared-scope variables, it's a good idea to enable "Full Checking" on a development server by going into the ColdFusion Administrator under Server Settings/Locking. In the interests of performance, I don't recommend using this option on your production server. When enabled, the CF compiler will check that all references to shared-scope variables are properly locked. For instance, it will catch this hard-to-find error:

 

<cflock scope="session" type="readonly" timeout="5">
<cfset Variables.session = session>
</cflock>
<cfoutput>#Variables.session["sessionid"]#</cfoutput>

In this case the Variables.session["sessionid"] is nothing more than a second pointer to the same session variable and must be locked. The cfset statement didn't create a local copy of the session structure. You must use the Duplicate() function to create a new structure, as opposed to a new reference to the same structure. Be careful with the StructCopy() function - it creates a copy of an existing structure by copying the simple data types in the source structure and creating references to the complex data types in the structure, something midway between a cfset and a Duplicate(). An example of the Duplicate() function would be:

 

<cflock scope="session" type="readonly" timeout="5">
<cfset Variables.session =
Duplicate(session)>
</cflock>

Interestingly, the Full Checking setting won't catch this error:

<cfoutput>#Variables.session.sessionid#</cfoutput>

Since this is simply an alternate syntax for the previous statement, there appears to be a bug in the CF compiler.

Also note that the Full Checking setting is incompatible with NAMED locks, as opposed to SCOPED locks. I found that the setting didn't work with the ColdFusion/Flash Component Kit I tested awhile back and has caused problems with early releases of Spectra or any application using named locks. Any use of a named lock will throw an error with Full Checking enabled. Still, it's a useful debugging tool, if not 100% reliable or wholly compatible with existing tools and apps. Scoped locks, introduced in ColdFusion 4.5, are generally preferred over named locks, according to the Best Practices TechNote mentioned earlier, but named locks still have a limited purpose when you work with file operations and custom tags that aren't thread safe. To quote from the CF 5.0 help documents:

Providing the name attribute allows for synchronizing access to resources from different parts of an application. Lock names are global to a ColdFusion server. They're shared between applications and user sessions, but not across clustered servers.

Be sure to check the footnotes of the technote for additional clarification on locking.

Going back to my suggestion to use a session variable in conjunction with a login process, I like to set a session variable with a name like isLoggedIn upon successful login. Then I can easily test whether a user is logged in or not by simply coding:

<cfif isDefined("session.isLoggedIn")>....

Although the Full Checking option inside CF Administrator (discussed below) doesn't object if you fail to place a cflock around this statement, I believe this is another bug. If session. isLoggedIn isn't defined, I know that either the user hasn't logged in yet or the session has timed out. I don't need to test the value of the variable, only whether it exists. You can see a sample of this in the Application.cfm template in Listing 1.

Client Variables
Client variables are unique to ColdFusion and offer several terrific advantages over session variables. Since they're not shared-scope, they don't require locking. Your ColdFusion server, of course, is configured to store client variables in a real database like SQL Server, Oracle, or Sybase; you wouldn't consider deploying a production application in which you store your client variables in the server's registry or MS Access. This permits the database to handle all locking issues for you. A database is also quick and works well in a clustered environment without additional programming.

Further, client variables persist from session to session. A configurable timeout is associated with client variables, but it's generally measured in days or weeks as opposed to the usual timeout period of 15-20 minutes for session variables.

The beauty of client variables is that they're managed by ColdFusion and require only that the client management attribute of the cfapplication tag be set to yes. Behind the scenes, ColdFusion assigns a unique CFID/CFTOKEN to your client machine/browser the first time you access a CF application, and sets these values into a cookie stored on your hard drive with an expiration date far in the future. Using this cookie, ColdFusion then stores your client variables in the specified datastore and handles all updates and retrievals automatically. Interestingly, if stored in a database, ColdFusion manages this by serializing the client variable key/value pairs for storage and deserializing them as needed. As long as your users don't delete their cookies, ColdFusion will know who you are the next time you visit and will retrieve your client variables automatically. You can see samples of client variables in the registry under HKEY_ LOCAL_MACHINE\SOFTWARE\Allaire\ColdFusion\CurrentVersion\Clients (see Figure 1) and in a database (see Figure 2).

Note that when stored in the registry, client variables are essentially stored as name/value pairs. When they're stored in a database, they're stored in two tables named CDATA and CGLOBAL. The former is the one that stores client variables set by the developer; they're stored in a string format in the data column. This string may be viewed as a ColdFusion list, delimited with # signs. Note also that embedded # signs are escaped. The cfid column actually contains both the cfid and cftoken, and the app column contains the application name. These two columns uniquely identify a row in the CDATA table. You can improve the performance of your application by manually creating a composite primary key on these columns; similarly, you can create a primary key on the cfid column of the CGLOBAL table. Also, if your application doesn't use the special system-managed client.hitcount, client.lastvisit, and client.timecreated variables, you can disable global client variable updates in the client variables section of the ColdFusion Administrator for another small performance boost. This setting may be customized for each individual client variable store.

One limitation of client variables is that they support only simple data types. You can't store a structure, query, or array in a client variable, although you can store a character, Boolean, string, number, or list. In practice, I haven't found this to be a drawback. In fact, if you first serialize your complex data type, you can store it in a client variable. I'm not sure why you'd want to, but...

Another limitation of client variables, easily surmounted, is their reliance on the CFID/CFTOKEN stored in the cookie. Users who delete their cookies are essentially deleting their client variables. ColdFusion server will assign a new CFID/CFTOKEN to these users and their client variables will be orphaned and not retrievable. Another aspect of this same issue is accessing the Web application from a second client. Since the client variables are tied to the cookie, which resides on the first client machine, these variables are again not available. They will, however, be purged automatically by ColdFusion after the client timeout period - set in CF Administrator - has passed without a visit from your user.

Serialization and Portable Client Variables
Okay, what is this serialization business? Simply put, it's a method of taking a collection of variables or a single, complex variable and turning it into a string. ColdFusion includes support for WDDX, a type of XML, and provides a set of functions (both ColdFusion and JavaScript) and tags for serialization and deserialization. I'll use the simple CF tags to serialize and deserialize our collection of client variables so I can store them in such a way that I can access them as needed.

While developing these techniques, I worked up the beginnings of a simple e-commerce application that permits visitors to browse and customize their experience (see Figure 3) by setting their preferences. If they want to subscribe to an e-mail newsletter, make a purchase, or save their settings, they must create a valid account (see Figure 4). Once logged in, regardless of the client machine or browser used, their preferences are retrieved (see Figure 5).

I take all my client variables, stuff them in a structure, serialize the structure, and store it in a table row that is identified by a userid. My application will incorporate a login screen that uses an e-mail address and a password to identify a user. I also have a column (named "preferences" in my example) in my User table (see Listing 2) that has a data type of text in SQL Server 7, ntext in SQL Server 2000, or long in Oracle. If you're prototyping using MS Access, you'll use a data type of memo. These data types mirror what ColdFusion uses for the data column when creating the CDATA table. I'll serialize all the client variables and store them in this one column. When users log into my application from a new machine, their preferences will be retrieved, deserialized, and set into client variables.

Why don't I simply create separate columns in the user table for each needed client variable? Because in my experience the client variables needed for an application vary as an application grows and develops. The navigation on the site changes...management decides more personalization is needed...certain features grow old and are no longer used. In each case different or new client variables may be required, and the database would need to be altered or columns added or dropped. Assistance from a DBA may be required, and forms may need to be filled out or procedures complied with. By serializing all the client variables in one string, we retain tremendous flexibility to quickly refine and adapt our site. Once employed, regardless of scope creep or changing requirements, this technique requires no SQL changes, no additional cfset statements to work, just plain-vanilla client variables - all the overhead of permanently saving our client state becomes automatic. The drawback of this approach is that it would be difficult to query these settings, but that may not matter much.

Listing 1 shows a bare-bones Application.cfm file. This file includes a series of cfparam statements to ensure that all of my client variables have values. In my example application these settings are primarily personalization features such as page background color, typeface, and font color. After setting the client variable defaults, Listing 1 ends with a bit of code, which simply sets a request variable isLoggedIn. Note that while session.isLoggedIn may or may not exist, request.isLoggedIn always exists.

Listing 3 consists of an excerpt from the login verification process. It takes an e-mail address and a password from a login form and queries the database for a valid user. If the user is found, session.isLoggedIn and request.isLoggedIn are both set to "y". Note that you must never set session.isLoggedIn to "n". If you find it necessary to end a user's session, you must use StructDelete:

 

<cflock scope="SESSION" type="EXCLUSIVE" timeout="5">
<cfset tmp = StructDelete(Session, "isLoggedIn")>
</cflock>

This is because I'm using the isDefined("session.isLoggedIn") technique described previously. Using this technique, if the session variable is defined, the user is logged in.

After verifying that a valid e-mail address and password were supplied, I retrieve any client preferences that have been saved, as shown in Listing 4. In the case of a newly created user, no preferences have yet been set; this condition is detected by retrievePrefs.preferences NEQ "". I'll return to this template a little later.

Listing 5 is where I save my client variables. This template loops over a list of the client variables, inserting the variables and their values into a structure, in only four lines of code. Next, the CFWDDX tag is used to serialize the structure. A simple cfquery tag is then used to update the preferences column in the user table. If you'd like to inspect the contents of the WDDX packet, you could select it out of the table and display it as:

<cfoutput>#HTMLCodeFormat(retrievePrefs.preferences)#</cfoutput>

The HTMLCodeFormat function is necessary to preserve the XML tags if you wish to display the packet in a browser rather than in a native SQL session. Note that the code in Listing 5, setCustomizations.cfm, must be included as the final bit of code on any page that sets client variables. You have the option of including this code in the OnRequestEnd.cfm template, although you'll want to weigh the processing costs. In my testing the entire set-Customizations.cfm template typically took only 10-20 milliseconds to process. In my example application this code is invoked only by changes to settings the user makes on the customization screen (see Figure 3).

Now that we've saved all our client variables, let's look again at Listing 4, retrieveClientVars.cfm. This template appears in only one place in your application, right after a successful login. It selects the user's preferences, stored as an XML/WDDX packet, and reconstitutes the client variables. This is accomplished with the CFWDDX tag, using the action= WDDX2CFML attribute. The output of this tag is a structure (variables.pref-Struct) that contains the original client variables as name/value pairs. I loop through this structure, using the collection="#variables.prefStruct#" attribute. Each element in the collection is a client variable name that is placed into the clientVar variable via the item= "clientVar" attribute. The heart of this template is:

 

<cfset "Client.#clientVar#" =
StructFind(Variables.prefStruct, clientVar)>

This statement dynamically creates client variables and sets them to the value stored in the structure. Note that this statement is equivalent to:

 

<cfset temp = SetVariable("client.#clientVar#",
StructFind(Variables.prefStruct, clientVar))>

The syntax for creating variables dynamically is a bit tricky in terms of quotation marks and pound signs. I find the former syntax a bit easier to remember and type.

Our entire framework then actually consists of two templates: setCustomizations.cfm and retrieveClientVars.cfm. These templates handle everything related to storing our client variables in a database and retrieving and resetting them on application login, regardless of the client machine we use to access the application or whether we've deleted our cookies. We can add or delete client variables at any time in our application and these templates will find them, save them, and retrieve them without any database changes or special coding of any kind.

You can use this same technique with session variables. This would yield yet another type of variable, a sort of persistent cross-session variable. I'll leave it to your imagination to find a suitable application.

This entire application is available from CFDJ for you to download, inspect, and customize. The download is a 1MB Zip file that includes an MS Access database and installation configuration instructions.

ColdFusion client variables are wonderful things. They have always permitted developers an easy and efficient way to allow users to customize their experience and to save the state of their "session." However, as convenient as they are, they do not follow users from machine to machine - yet this is what most of us expect from something called a client variable. It's not difficult to add this capability to your application. Two small templates, one extra database column, and voilà - you have truly persistent variables!

More Stories By Charles McElwee

Chuck McElwee is a Principal Consultant with e-Tech Solutions Inc., a Philadelphia-based e-business development company and a Macromedia Premier Partner. He has been programming for over 20 years in mainframe, client/server and web environments and holds an Advanced ColdFusion certification.

Comments (2) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Brian 05/02/08 11:12:15 AM EDT

When do client variables expire when they are stored as a cookie? How can I tell? How do I edit it?

Thanks

Brett 01/10/07 11:34:22 AM EST

I was wondering if thier is any updated information related to this article regarding CFMX 7 to take into consideration. i.e. different approaches or changes. Thanks.