Preserve Session Variables Across HttpHandlers - asp.net

I have an ASP.NET application with 5 .ashx HTTPHandlers that implement IRequiresSessionState or IReadOnlySessionState.
Upon calling the first handler I create a variable and store it in the session.
When I call the next HttpHandler the variable is not available in the session object.
I am use context.Session.
I have a global.asax where I retrieve the sessionId.
Is there a way to preserve session variables across HttpHandlers or does each Handler get its own session?

HeartAttack has a good discussion of this here:
http://weblogs.asp.net/ashicmahtab/archive/2008/09/18/how-to-use-session-values-in-an-httphandler.aspx
Basically, you need to do something like:
public class MyHandler:IHttpHandler,IRequiresSessionState
HTH's.
This should maintain session state across handlers. That is, when you log in, you only have one session for all of your requests into that domain. I'd suggest verifying that you are really logged in. Cache of course will work because that is common to all sessions but much more expensive on the server because it does not easily go away.

Related

Could we save and load the ASP.NET InProc session (hence releasing the lock) around long running external calls

In ASP.NET when you have 2 AJAX requests on the same web page calling 2 controller actions, if they use the session then one will lock out the other
You can get readonly access to the session which can help, but not if you want to write to the session
You can override the session class, e.g.
https://www.red-gate.com/simple-talk/dotnet/asp-net/single-asp-net-client-makes-concurrent-requests-writeable-session-variables/, but this doesn't really help for the same reason
In my case the controller action calls a long running external server call. While this is happening ideally the session would be released and saved back to memory, and then when the call is finished the session would be read back in, possibly being blocked if another call is still proceeding
NB Whether or not the external server call is called in an async manner makes no difference unfortunately
Is there any way of doing this? Possibly by overriding some internal classes?

Confusion over Startup, Global, Application and Session

I am trying to understand the various ways of storing and instantiating Application (i.e. objects available to every user) and Session level (objects created and available to users only for their session) variables. Also, how does OWIN fit into all of this?
Global.asax.cs - This can contain a bunch of different methods. I believe that Application_Start is only called during the first request. However, there are a few candidates here for methods to populate session level variables (e.g. Session_Start and Application_BeginRequest). What is the standard way of doing this?
There is also the Startup class used by OWIN. I get that OWIN lets you store Application level variables, but why wouldn't you just use the HttpApplicationState Application variable accessible from Global.asax.cs to accomplish this? Also - can OWIN handle Session variables?
"I believe that Application_Start is only called during the first
request."
Only for the first request after calling the web application. For instance, this is the case after deploying, ApplicationPool Recycling, restarting or coming out of sleep.
Let's assume 3 users visit your web application. Application_Start will only be called for one of them, specifically the first one that visits it. Therefore it is not suited for populating user-specific session values.
However, there are a few candidates here for methods to populate session level variables (e.g. Session_Start and Application_BeginRequest). What is the standard way of doing this?
In the past I've worked with Session_Start to initialize user-specific session values (like default values) on numerous projects and never had an issue with it.
I'm really not sure what the question is, as I said in the comments. I'm going to ignore the OWIN stuff since I don't know, frankly.
Firstly, try not to store state at all. Design to pass state back and forth between server and client in models, or the URL, even in the HTML on the client such as in the URLs in the <a> tags your rendering, or (rarely) in cookies, rather than keep things in memory. Stateless designs are way more scalable.
Storing state isn't "usually" done in the Global.asax but then what's usual? I store state as and when I need it, load it or otherwise come by that data. For me in MVC, that's usually downstream of a Controller action, maybe while logging someone in, or reading some data received in a model, like a customer clicking 'add to cart'.
Application state I rarely use, though I store long-lived and shared data within normal fields and properties in long-lived static classes. These die when the app is recycled, but I don't usually care since the apps are designed to work without it, stateless; its usually cached bits of data.
Also, Session_Start only fires when a new browser/agent hits the site. You don't know the user at that point.
The methods in the Global.asax were not specifically designed for 'bootstrapping' state-loading, they're just convenient events for doing whatever you want with. You don't have to use them at all, mine usually just contain logging so I know when sessions start etc.!
I don't know if this helps.
Once you have a plan, come back and ask a targeted question about the OWIN stuff.

ASP.NET MVC: how to prevent a session lock?

I've an application which has some controller's actions calling slow 3rd party web services. These actions are called using AJAX calls from the page.
I can use async controllers to free ASP.NET thread pool, that's great. But what about session? If I use InProc session and a request made to "slow action" the particular user can't make any request to the application because his session is locked by first "slow" call.
In PHP there is a method session_write_close() which I can use as following:
Accept user's request to slow action
Check rights of the user to access controller/action based on session data
Write something to the session if needed
Call session_write_close(). From this point session is closed by this request and any other request from the same user can access it
Make my slow call (maybe in some async way)
I know that I can disable session state on the controller level using [SessionState] attribute, but that's not the solution.
Any ideas?
I think it could be several scenarios.
1) make changes in controller factory and change it to produce contorllers without session or with some custome session implementation
2) try to read this article about sessionless controllers

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".

"HttpContext.Current.Session" vs Global.asax "this.Session"

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.

Resources