The ASP.NET session object is a great way to keep user specific data throughout the lifetime of your web application.
However I have two fundamental problems with it:
1. Sessions out of the box don't work in Web gardens or web farms, you can use the asp.net session service or a SQL database to manage your session state, but that is slower than the default inProcess session state. Even for smaller sites there are problems; every time I update web.config or an assembly in the bin directory I loose my sessions. In addition every time the worker process is restarted through IIS health measures, sessions are gone as well.
2. I need to use user state within my business objects because that's where my authentication and authorization code resides. It's possible to access the session object in your business tier but they don't work if I access it from a console application or nUnit for testing.
Today I started working on a solution and have something working but it needs more testing. First to make things cleaner I abstracted all access to the session and cache objects into a new class in my business layer called 'State' all access to state variables has to go through this class. So I have get, set and remove methods. Each take an enumeration of items rather than a string for the key to keep a nice clean list of all session variables I'm using.
Within this class I can now address my two problems. To solve the loosing sessions problems I bring in a custom database table that stores session variables for me. Not quite as sophisticated as the Microsoft's SQL-Server solution but it works. When I save 'state' I not only store it in the session object but also in the database table. When I try to access 'state' I first check session and if that is null, then I check in the database. If I find the value in the database table I put it into session and return the value. This way the database is only accessed when a value is created/updated or when we lost the session object, otherwise we use the fast in memory session object.
I'm only using this methods for integer and small string values, but not for my 'state' with datatypes of Hashtable or even DataTable. This would involve some serialization to store these types in a database field. In my case these 'bigger' values could be rebuild from normal database data based on the integers stored in state. (I have an integer UserID and hashtables for a security token and a DataTable for navigation data, both can easily be refilled when needed, remember this only happens when we loose session).
I mostly use 'read-only' state. All my session variables are set after a user has been authenticated and after that, they are never updated. This also works fine in web gardens and web farms because the session objects in all the various worker processes are synchronized through the database table.
For session variables that change during their lifetime this approach only works for a single worker process. If you have multiple processes that update state in there own inprocess session object there is no way to notify the other worker processes. One idea is to check the database periodically for updated data.
In my case I use the state variable to identify that user after initial authentication. When the user logs out, I remove the state from the inprocess memory and the database. However if I'm using multiple worker processes, the other ones still have the state data in their local session objects. The only solution I see to this problem is that you also have to delete the Asp.Net_SessionID cookie, so when the user hits another worker process it is no longer associated with it previous session. Of course this means it's all or nothing, you can't remove some state values but not others. For web-farms and web-gardens you should really only use this for write-once state data that should also be deleted together.
What about expiration. My method to remove a session variable also deletes the associated record in the database but it doesn't expire in the same way that in-memory sessions variables do. Deleting all related records when the Session_End event in globals.aspx fires is one option.
This all works because the Asp.Net_SessionID cookie remains in the browser even if you loose your session on the web-server. The new worker process picks up the existing cookie and uses the same ID for the new session instance.
This allows you to restart IIS or even the server and not loose state for the user, great!
The second problem I solved by checking for an existing HttpContext.Current instance, if this doesn't exist I am not in a web application and can't use Session or Cache. In this case I am using using my own Hashtables to store state information. I implemented a singleton pattern for an AppContext class to ensure that I have always only one instance of my Hashtable.
All this shouldn't be to hard to implement, you should even be able to do this in classic Asp or php, there is nothing asp.net specific about this, session variables are widely supported. I don't have any example code for this yet because it's all deeply integrated into my application. Lets see whether there is some interest.