The other day, I was transacting online with my personal banker while chatting with a friend of MSN Messenger, discussing my plans for the weekend. Suddenly, a thought occurred to me: have you ever tried being online in Messenger from two different computers at the same time? I’ve tried it and was logged out from the first computer (i.e. the latest logon session is retained and the user is automatically signed out from the other session). This article demonstrates the use of the Cache object in ASP.NET to prevent multiple logons from the same user account to a Web-based system.
Thinking along the same lines, I was really curious about what would happen, if my wife logged on to the same banking system at the same time and transacted. Of course, transaction levels would have been defined properly in the application. However, thinking in terms of nuts and bolts, I was wondering what one could do about it. Can something be done about it at all? Is the crux of the problem really session re-duplication? Or do I have to prohibit the user from simultaneous logons? How? Do I have to track IP addresses? What about all the proxy, firewall, subnet funda?
Thanks to the Cache object in the ASP.NET application framework, I was able to prevent a user from logging on to a system from simultaneous locations. Yes, I was able to prevent two sessions from being created for the same user account.
Does that mean that there is no other solution to this problem? One obvious solution to this problem is to have a flag as part of the user object in the Datastore and toggle it whenever the user logs in and logs out. “Better untaught than ill taught,” goes an old saying. Before proceeding to the Cache based solution, I think it is only fair on my part to explain the above-mentioned solution and discuss its advantages and disadvantages. I would like to refer to this approach as Traditional.
Let us create a Boolean datatype in our database, as part of the User table. I have named it isLogged for the sake of clarity. As soon as the login process, for a given user account, succeeds, the value of this flag would be set to True. Now, let me take a minute to define the success of the login process.
During the login process, as part of verifying the credentials against the User table, we also check for the value of isLogged. If the value is already set to True, it clearly means that the user is already logged on to the system. Aha! Gotcha! We redirect the user to an appropriate page clearly explaining the problem and thus login fails. On the other hand, if the value of isLogged is False, we set it True and allow the user to proceed further with the application.
One of the easier ways to implement this logic is to make use of the global.asax. This file has two routines namely Session_onStart and Session_onEnd. I am sure the names are self-explanatory. So we put the code login process in Session_onStart and set the value of isLogged to False in Session_onEnd. This would be our simple and straightforward logout process.
The Flip Side to It
Ok, now what? The traditional approach would work perfectly under normal scenarios (i.e. a user logs onto our site, has some good time minding their own business and logs out). What will happen if he/she doesn’t log out and just closes the browser; or what if he simply moves on to visit another site without logging out?
One of obvious reasons we can’t trust Session_onEnd is that there is no guarantee that the event will be triggered when the browser is closed. Does this mean that the entire purpose of Session_onEnd is defeated? Maybe not. The event will be triggered after the session times out. Normally, this time would be set to about 20 minutes. So there are chances that our user gets frustrated and leaves our website once and for all.
Caching may be defined as the process of keeping information in memory that takes a relatively long time to fetch for quick access the next time it is needed. There are really only two differences between the Application and the Cache objects.
The Cache object is thread-safe unlike the Application object, i.e. one doesn’t need to explicitly lock or unlock the Cache collection before adding or removing an item. However, the objects in the Cache collections will still need to be thread-safe themselves.
Items in the Cache collection are automatically removed if they expire, if memory in the server becomes limited, or one of their dependent objects or files changes. This means one can freely use the cache without worrying about wasting valuable server memory, as ASP.NET will remove items as needed.
Adding an Object to the Cache collection
There are several ways to insert an item in the Cache collection. You can simply assign it to a key name (as you would with the Session or the Application Collection). But this approach is generally not recommended, as it wouldn’t allow you to control the amount of time the object will be retained in the Cache. A better way of inserting an item into the Cache collection is to use its insert method.
A little on these parameters is in the table below:
Cache.Insert Parameters
Parameter
Description
Key
A string that assigns a name to this cached item in the collection.
Item
The object you want to cache.
Dependencies
A “CacheDependency” object that allows you to create a dependency for this item in the cache. If you don't want to create a dependent item - just specify “Nothing” for this parameter.
AbsoluteExpiration
A “DateTime” object representing the time at which the item will be removed from the cache.
SlidingExpiration
A “TimeSpan” object represents how long ASP.NET will wait between requests before removing a cached item.
CacheItemPriority
Specifies how important it is for the cache item to remain in the cache. It can have “AboveNormal”, “BelowNormal”,”Default”, “High”, “Low”, “Normal” or “NotRemovable” as its value.
CacheItemRemovedCallback
The callback delegate provides a means for you to create your own function that is automatically called when the item is removed from the cache.
Usually we won't use all these parameters at once. For example, we cannot set both a sliding expiration and an absolute expiration policy at the same time. If we want to use an absolute expiration, set the slidingExpiration parameter to TimeSpan.Zero.
Absolute expiration is best recommended in situations when you know the information in a given item can only be considered valid for a specific amount of time. On the other hand, sliding expiration is more useful when you know that a cached item will always remain valid but should still be allowed to expire if not being used.
To set the slidingExpliration policy, set the absoluteExpiration parameter to DateTime.Max as shown below.
Getting back on the track of our course, we would be using the cache object with Sliding expiration and Application_PreRequestHandlerExecute of Global.asax to tackle this multiple login issue. As we have seen enough of the theory part, I think its time to look at some source code. For the purpose of testing, let us set the session timeout as 1.
That’s it! Easy, isn’t it? (Don’t forget to include System.Web.Caching in the login page). The username is almost always unique so we could even avoid concatenating the username and password.
We can now compile the application and run it. After logging in, if you try to use the same username/password combination the application won’t authenticate you. The only way out is to wait until the Cache expires (I hope you now understand the reason for setting the session time out as 1). This holds good even if we try to login from two different browsers/machines.
Conclusion
The system requirement for testing this sample code is Windows 2000, XP or 2003 OS with IIS 5 (or 6) installed and .NET Framework 1.0. I have omitted the other necessary evils such as text editors! Happy programming!