"HttpContext.Current.Session" vs Global.asax "this.Session" - asp.net

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. :)

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 the AppDomain 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, the HttpCache entry is updated).
Since Session_End fires without a current request in-fly, the Session object is spun up ex-nilo, with no information available. If using InProc session state, the expiration of the HttpCache triggers a callback event into your Session_End event, so the session entry is available, but is still a copy of what was last stored in the HttpContext.Cache. This value is stored against the HttpApplication.Session property by an internal method (called ProcessSpecialRequest) where it is then available. Under all other cases, it internally comes from the HttpContext.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 supports Session_End, which it does if it returns true from SetItemExpireCallback. The only in-the-box store which does is the InProcSessionState store. It is possible to write a session store that does but the question of who will process the Session_End is kind of ambiguous if there are multiple servers.

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.

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.

The Session_End event is raised only when the sessionstate mode is set to InProc in the Web.config file. If session mode is set to StateServer or SQLServer, the event is not raised.
use Session["SessionItemKey"] to get the session value.

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

Session is available in your Global.asax file, during the Session_Start event. Maybe wait until this point to do stuff?

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.

Related

ReadOnly Session is writable

In out project we need to execute a few AJAX calls in a row asynchronously, so the next call doesn't have to wait for previous to return. And even though client issues calls asynchronously, server processes them sequentially due to Session lock.
Since we don't need to modify Session in these calls, we marked page that AJAX calls with EnableSessionState="ReadOnly"via #Page directive. And it worked, calls became truly async and no longer depend on each other timing. But what we found out - in that backend code Session is writable despite being marked as ReadOnly. We can assign values to Session and the values persist. Is this a bug or behavior by design?
I'd say this behavior is a "by design" gotcha of in-process session state (which, unfortunately, doesn't seem to be well documented).
Out-of-Process Session State (State Server or SQL Server)
At the beginning of every request, ASP.NET loads and deserializes session data from external storage into memory. Each request gets its own copy of the data, so changes to the data won't affect other concurrent requests in the same session.
if EnableSessionState is set to ReadOnly, then at the end of the request, the data are simply discarded rather than serialized back to external storage.
In-Process Session State
No serialization or deserialization occurs. Instead, there's a single set of session data in memory which lives for the duration of the session. Each request in the same session shares that set of data, and changes to the data are immediately visible to other concurrent requests.
I suppose the ASP.NET team could have made Session read only when EnableSessionState is set to ReadOnly:
this.Session["Customer"] = customer; // Why not throw InvalidOperationException?
But ASP.NET would still have no way of detecting changes to the objects themselves:
Customer customer = (Customer)this.Session["Customer"];
customer.Address = address; // ASP.NET can't detect this.
Therefore, it's your responsibility as a developer to avoid changing session data if you set EnableSessionState to ReadOnly. Otherwise, you may introduce multithreading bugs.

Weird behavior of IReadonlySessionState

I don't know how (and this is what I want to know).
I have an HttpHandler which implements IReadonlySessionState marker interface. My idea was not to update the Session variables in the handler. But we accidentally call code which is saving data in a session and somehow the session is saving data.
Then we moved our project to Azure and decided to use Azure Cache. Because of the change of SessionStateProvider, now the session is not saving the data (and it should not because the handler is readonly).
I want to know if there is any bug in default session provider which is causing the session to be persisted even when manipulated in Readonly Http Handler.
IReadOnlySessionState tells the session state provider that this handler doesn't need to save session state.
The handler is not obligated to refuse to save anything.
The default handler doesn't have any reason to not save session state (since it's in-process), so it always saves.

Controller SessionStateBehavior is ReadOnly and I can update Session Variable

I expect that if controller has attribute SessionStateBehavior.ReadOnly then I can't change session variables inside this controller
but I can change values.
I try this code
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class GLobalController : Controller
{
public ActionResult Index()
{
Session["xxx"] = DateTime.Now.ToString();
return View();
}
see Writing to a read only session in MVC 3+
That post claims the behavior is inconsistent.
I am definitely able to write to Session in Controllers using ReadOnly.
I Would treat it like this:
Required means you are requesting a exclusive lock on Session (i.e. no parallel processing of requests for the same sessionID)
ReadOnly means you are requesting a non-exclusive lock on Session (i.e. your request still has to wait for requests with an exclusive lock to finish, but you can process requests with non-exclusive locks in parallel. However it is up to you to ensure that your code doesn't write to Session. It's not necessarily enforced by the framework)
I realize this is counter to http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstatebehavior.aspx
Read-only session state is enabled for the request. This means that session state cannot be updated.
but it seems you in fact can update session state under some scenarios.
According to Patrick Y. Ng (Software Engineer at Microsoft) who designed and developed the Session State engine of ASP.NET:
Even though EnableSessionState is marked as ReadOnly, in InProc state the user can still modify the session. The only difference is that the session will not be locked during the request. This limitation is by-design. And I'm sorry that it’s not documented in MSDN.
There is much more useful information about session state in this post. It is really worth reading.
This is just my interpretation:
I see that you can add to Session during the action method - after all Session is just a dictionary really. However the session is not saved at the end.
It does seem like it ought to throw an exception, but perhaps since this feature came later to the framework they decided against checking every time.
Results may vary also depending upon what session state storage you are using (inproc / sql server).

What should I do if the current ASP.NET session is null?

In my web application, I do something like this to read the session variables:
if (HttpContext.Current.Session != null && HttpContext.Current.Session["MyVariable"] != null)
{
string myVariable= (string)HttpContext.Current.Session["MyVariable"];
}
I understand why it's important to check why HttpContext.Current.Session["MyVariable"] is null (the variable might not have been stored in the Session yet or the Session has been reset for various reasons), but why do I need to check if HttpContext.Current.Session is null?
My understanding is that the session is created automatically by ASP.NET therefore HttpContext.Current.Session should never be null. Is this assumption correct? If it can be null, does it mean I should also check it before storing something in it:
if (HttpContext.Current.Session != null)
{
HttpContext.Current.Session["MyVariable"]="Test";
}
else
{
// What should be done in this case (if session is null)?
// Is it possible to force the session to be created if it doesn't exist?
}
Yes, the Session object might be null, but only in certain circumstances, which you will only rarely run into:
If you have disabled the SessionState http module, disabling sessions altogether
If your code runs before the HttpApplication.AcquireRequestState event.
Your code runs in an IHttpHandler, that does not specify either the IRequiresSessionState or IReadOnlySessionState interface.
If you only have code in pages, you won't run into this. Most of my ASP .NET code uses Session without checking for null repeatedly. It is, however, something to think about if you are developing an IHttpModule or otherwise is down in the grittier details of ASP .NET.
Edit
In answer to the comment: Whether or not session state is available depends on whether the AcquireRequestState event has run for the request. This is where the session state module does it's work by reading the session cookie and finding the appropiate set of session variables for you.
AcquireRequestState runs before control is handed to your Page. So if you are calling other functionality, including static classes, from your page, you should be fine.
If you have some classes doing initialization logic during startup, for example on the Application_Start event or by using a static constructor, Session state might not be available. It all boils down to whether there is a current request and AcquireRequestState has been run.
Also, should the client have disabled cookies, the Session object will still be available - but on the next request, the user will return with a new empty Session. This is because the client is given a Session statebag if he does not have one already. If the client does not transport the session cookie, we have no way of identifying the client as the same, so he will be handed a new session again and again.
The following statement is not entirely accurate:
"So if you are calling other functionality, including static classes, from your page, you should be fine"
I am calling a static method that references the session through HttpContext.Current.Session and it is null. However, I am calling the method via a webservice method through ajax using jQuery.
As I found out here you can fix the problem with a simple attribute on the method, or use the web service session object:
There’s a trick though, in order to access the session state within a web method, you must enable the session state management like so:
[WebMethod(EnableSession = true)]
By specifying the EnableSession value, you will now have a managed session to play with. If you don’t specify this value, you will get a null Session object, and more than likely run into null reference exceptions whilst trying to access the session object.
Thanks to Matthew Cosier for the solution.
Just thought I'd add my two cents.
Ed
If your Session instance is null and your in an 'ashx' file, just implement the 'IRequiresSessionState' interface.
This interface doesn't have any members so you just need to add the interface name after the class declaration (C#):
public class MyAshxClass : IHttpHandler, IRequiresSessionState
In my case ASP.NET State Service was stopped. Changing the Startup type to Automatic and starting the service manually for the first time solved the issue.
ASP.NET Technical Articles
SUMMARY: In ASP.NET, every Web page
derives from the System.Web.UI.Page
class. The Page class aggregates an
instance of the HttpSession object for
session data. The Page class exposes
different events and methods for
customization. In particular, the
OnInit method is used to set the
initialize state of the Page object.
If the request does not have the
Session cookie, a new Session cookie
will be issued to the requester.
EDIT:
Session: A Concept for Beginners
SUMMARY: Session is created when user
sends a first request to the server
for any page in the web application,
the application creates the Session
and sends the Session ID back to the
user with the response and is stored
in the client machine as a small
cookie. So ideally the "machine that
has disabled the cookies, session
information will not be stored".

asp.net session state

During Session Start, one has access to the Request object. How about Session End, does it still have access to the Request object ? For example I want to count how many browsers are currently connected to my application.
Edit 1 : If Session End doesn't have access to Request Object, what info does it have access to ? Session ID, etc ?
Edit 2 : If Session End cannot be used to track disconnections, how does one track disconnections in ASP.Net ?
Thanks
No, the Request object is not available in Session End.
Note too that Session End only fires when you call Session.Abandon() from code, not when a Session expires due to natural timeout or what-have-you. Consequently, it is not a reliable method to use for tracking disconnections.
Session_End will be fired if one is using InProc.
Session_End will be fired
1) after n minutes of inactivity (n = timeout value), or
2) if someone calls Session.Abandon()
Session_End doesn't get fired if one closes the browser.
Session_End requires session state to be set.
If one needs the original Request.Browser data, one should save it in Session State.
During Session_End, it has access to the Session State.
from the documentation
The Session_OnEnd event occurs when a
session is abandoned or times out. Of
the Server built-in objects, only the
Application Object, Server Object, and
Session Object objects are available.
Remarks
You cannot call the Server.MapPath
method in the Session_OnEnd script. By
default, Session_OnEnd runs as the
Anonymous User, as defined for the
application. In the event that there
isn't an Anonymous user, or the Logon
for the Anonymous user fails, the
OnEnd function will not be called, and
an event will be logged.

Resources