Recently, while working on some code for an ASP.NET project at work. We needed a tracking util to take basic metrics on user activity (page hit count etc) we would track them in Session
, then save the data to DB via Session_End
in Global.asax
.
I began hacking away, the initial code worked fine, updating the DB on each page load. I wanted to remove this DB hit on each request though and just rely on Session_End
to store all the data.
All of the tracking code is encapsulated in the Tracker
class, including properties that essentially wrap the Session variables.
The problem is that when I executed Tracker.Log()
in the Session_End
method, the HttpContext.Current.Session
in the Tracker code was failing with a NullReferenceException
. Now, this makes sense since HttpContext
always relates to the current request, and of course in Session_End
, there is no request.
I know that Global.asax
has a Session
property which returns a HttpSessionState
that actually seems to work fine (I ended up injecting it in to the tracker)..
But I am curious, how the hell can I get the same reference to the HttpSessionState
object used by Global.asax
from outside of Global.asax
?
Thanks in advance guys, I appreciate the input. :)
The
Session_End
event is raised only when thesessionstate mode
is set toInProc
in theWeb.config
file. If session mode is set toStateServer
orSQLServer
, the event is not raised.use
Session["SessionItemKey"]
to get the session value.I think you already answered your own question: usually the Session property in Global.asax and HttpContext.Current.Session are the same (if there is a current request). But in the case of a session timeout, there is no active request and therefore you can't use HttpContext.Current.
If you want to access the session from the method called by Session_End, then pass it as a parameter. Create an overloaded version the Log() method, which takes a HttpSessionState as a parameter, then call Tracker.Log(this.Session) from the Session_End event handler.
BTW: you are aware that you can not rely on the session end event in any case? It will only work as long as you have the session state in-process. When using SQL server or StateServer to mange the session state, the session end event will not fire.
Okay, I am in the same problem to track the session activity. Instead of using session_end event, I have implemented the IDisposable interface and destructor to my sessiontracker class. I have modified the Dispose() method to save the session activity to DB. I invoked the method obj.Dispose() when a user clicks the logout button. If user closed the browser by mistake, then GC will call the destructor while cleaning the objects (not immediately but for sure it will call this method after sometime). The destructor method internally execute the same Dispose() method to save the session activities into DB.
-Shan
Global.asax implements HttpApplication - which is what you are talking to when you call this from within it.
The MSDN documentation for HttpApplication has details on how you can get hold of it in an HttpHandler for example, and then get access to the various properties on it.
HOWEVER
Your application can create multiple instances of HttpApplication to handle parallel requests, and these instances can be re-used, so just picking it up somehow isn't going to guarentee that you have the right one.
I too would also add a note of caution - if your application crashes, there's no guarentee that session_end is going to be called, and you'll have lost all the data across all sessions, clearly not a good thing.
I agree that logging on every page is probably not a great idea, but perhaps a halfway house with some asynchronous logging happening - you fire details off to a logging class, that every now and then logs the details you are after - still not 100% solid if the app crashes, but you're less likely to lose everything.
Remember that Session_End runs when the session times out without activity. The browser doesn't originate that event (because it's inactive), so the only time you actually will get the event is when using the InProc provider. In EVERY OTHER provider, this event will never fire.
Moral? Don't use Session_End.
To answer the original question better:
Background
Every single page request spins up a new
Session
object and then inflates it from your session store. To do this, it uses the cookie provided by the client or a special path construct (for cookieless sessions). With this session identifier, it consults the session store and deserializes (this is why all providers but InProc need to be Serializable) the new session object.In the case of the InProc provider, merely hands you the reference it stored in the
HttpCache
keyed by the session identifier. This is why the InProc provider drops session state when theAppDomain
is recycled (and also why multiple web servers cannot share InProc session state.This newly created and inflated object is stuck in the
Context.Items
collection so that it's available for the duration of the request.Any changes you make to the
Session
object are then persisted at the end of the request to the session store by serializing (or the case of InProc, theHttpCache
entry is updated).Since
Session_End
fires without a current request in-fly, theSession
object is spun up ex-nilo, with no information available. If using InProc session state, the expiration of theHttpCache
triggers a callback event into yourSession_End
event, so the session entry is available, but is still a copy of what was last stored in theHttpContext.Cache
. This value is stored against theHttpApplication.Session
property by an internal method (calledProcessSpecialRequest
) where it is then available. Under all other cases, it internally comes from theHttpContext.Current.Session
value.Your answer
Since the Session_End always fires against a null Context, you should ALWAYS use this.Session in that event and pass the HttpSessionState object down to your tracing code. In all other contexts, it's perfectly fine to fetch from
HttpContext.Current.Session
and then pass into the tracing code. Do NOT, however, let the tracing code reach up for the session context.My answer
Don't use
Session_End
unless you know that the session store you are using supportsSession_End
, which it does if it returnstrue
fromSetItemExpireCallback
. The only in-the-box store which does is theInProcSessionState
store. It is possible to write a session store that does but the question of who will process theSession_End
is kind of ambiguous if there are multiple servers.