Session storage has been around for so long that we take it for granted. However, working with the Session object has some nuances you don’t see documented in books. In this Q&A post I’d like to go over some issues you need to be aware of when relying on Session for short-term storage.
To limit this discussion to a reasonable scope, let’s assume we use only in-proc sessions. Storing session data in an out-of-proc state service or SQL Server is a different can of worms. Also, we won’t be talking about cookie-less sessions. They are a very strange beast.
One final disclaimer. I’m focusing on 1.x and realize that 2.0 has some enhancements some of which I will mention.
Q: Where is session ID stored?
A: When you first hit an ASP.NET-powered site, a non-expiring cookie with the name of ASP.NET_SessionId is issued (in 2.0 you can set a different cookie name in web.config). This cookie contains a randomly generated 20-something-character string which is the session ID and is sent with every request.
Here’s a very important point: the cookie will stay alive until you close your browser. In other words, the session ID will remain the same even if you navigate away to another site, say to check team standings at the Olympics, and then come back: if it’s the same browser window, the cookie will still be there and the session ID will be the same.
It’s possible to ditch a session by calling Session.Abandon() which will finish processing the page and only then clear out the session data. A subsequent page request causes a brand new session object to be instantiated. The session cookie is a staunch one—it stays intact even when Session.Abandon() causes one session object to die and another one to be born.
You can also let a session expire. The timeout interval is set in the <sessionState> section of web.config. When a session times out, it flushes all session data, the session object dies and a new one is born on a subsequent page hit. This is similar to the scenario with Session.Abandon() above. Even in this case the session cookie stays intact.
So here’s the conundrum: a session, essentially, is the time between the first page hit and the time when the browser window is closed. The fact that you can’t disable session timeout makes no sense to me. Basically, a user keeps his/her browser window open, but the session data may disappear due to a timeout. I wish there was a way to just let the session live for as long as the user has the browser running. Otherwise you end up with a predicament explained in the following section.
Q: Is there a way to tell if the session timed out or was abandoned?
A: There’s no simple answer to this. HttpSessionState has a handy property, IsNewSession which indicates whether the session was created with the current request
. In other words, if you called Session.Abandon() or let it time out, your old session data will be gone, you will receive a brand new session object and Session.IsNewSession will be true the first time you hit a page.
I don’t know if it’s possible to tell which of the two things happened. Bob Boedigheimer shows how to at least detect session timeouts. I even rolled his code into a base class property to guard for this edge case.
The important point here is that you can never assume that if you’re stored something in session, it’ll be there the next time you go looking for it. A user may simply walk away from the computer and cause your session to expire. Again, these session timeouts seem nonsensical to me, but I don’t know if it’s possible to turn them off. Most likely not.
What you need to remember is to use code similar to Bob’s to detect if the session is gone. At this point you have two options:
- re-read data and store it back in session, or
- redirect to some page which politely notifies of the timeout and suggests to go back and do whatever the user was previously doing.
Q: Does presence (or absence) of global.asax affect the session ID?
A: Yes. Suppose you create a brand new web project without global.asax. After all, it’s perfectly normal to leave this file out if you don’t need it. Without global.asax your session cookie will be killed at the end of each page request, and a new one created on every subsequent request. This is to say the session ID will change with every page hit.
Now, suppose you add a blank global.asax. Does this help? No. The session ID will still change until you add an empty Session_Start() handler. If you dig long and hard with Reflector, you’ll see why the mere presence of this particular event handler guarantees that the session cookie will remain intact once it’s issued on first page hit.
Q: I don’t want global.asax in my project. Is there something I can do about the flaky session ID?
A: If you don’t want to drag around global.asax and need to "lock in" a session ID, your landing page needs to store something in session.
Here’s an example. Page a.aspx has no dealings with the session. Page b.aspx, on the other hand, stores something in session at least once. As long as you hit a.aspx, the session ID will change. As soon as you navigate to b.aspx, the session ID will "lock in" and remain constant until you close the browser.
To learn about the mechanics of it point your Reflector to the SessionStateModule class. MSDN has more on this, too:
When using cookie-based session state, ASP.NET does not allocate storage for session data until the Session object is used. As a result, a new session ID is generated for each page request until the session object is accessed. If your application requires a static session ID for the entire session, you can either implement the Session_Start method in the application’s Global.asax file and store data in the Session object to fix the session ID, or you can use code in another part of your application to explicitly store data in the Session object.
Q: The session cookie seems to be very resilient. Still, is there a way to discard it and obtain a new session ID?
A: The session cookie is a die-hard indeed. Microsoft’s Knowledge Base article How and why session IDs are reused in ASP.NET shows a dirty hack which forces the session ID to change. Don’t try this at home.
Q: When is Session_OnEnd called?
A: The runtime invokes Session_OnEnd, which you declare in global.asax, when you call Session.Abandon() or the session times out.
This leads to an interesting implication: does the session end immediately when the browser is closed (and, consequently, the session cookie goes away)? No, there’s no reliable way to detect when the user closes his/her browser.
The session manager stores session data in HttpCache with a sliding expiration. This expiration timeout is set in the <sessionState> of web.config. When the browser window is closed, the session is bound to expire because there’s no way to go back to using the session ID that just went away (unless you spoof the session, but we won’t discuss a movie-plot terrorist threat).
When the timeout is up, HttpCache will remove the session state object and invoke a callback which, essentially, translates into calling Session_OnEnd. Note that this happens way after the browser window is closed, but it does happen. Remember, you cannot access HttpContext is this handler, but the outgoing Session object is still alive. If you need to do some clean-up, Session.SessionID is still available.
Further Reading
There’s a great article by Michael Volodarsky at MSDN entitled Fast, Scalable, and Secure Session State Management for Your Web Applications which I highly recommend as a crash course in session management. Pay attention to the idea of "flattening" session data and breaking it down to storing only primitives. The discussion of partitioning and securing session state is very educational too.
One of the most important points of this article follows below:
Note that if you turn off session state in your application, the HttpContext.Session property will throw an exception if the page or handler tries to access it. If you mark the page as read-only, updates to the session in the out-of-process modes will not be preserved across requests. In InProc mode, however, updates will be preserved because they are made to the live objects that stay memory-resident across requests.
In other words, if you retrieve an object from Session and change any of its properties, you modify a live object and these changes are "committed" to the session even if the session is marked as read-only!
Conclusion
This improvised Q&A session is something I’ve been dealing with lately. I spent many hours Reflect’ing and experimenting, and figured I’d share my observations. Session state is a very helpful storage, but it will break you if you abuse it.