Friday, April 23, 2010

Detecting Session Timeout in ASP.Net WebForms

[Edit]
Disregard all this crap. It's simply not possible to detect session timeout using cookieless sessions. Detecting a post with a redirect does in fact trigger the condition in Application_EndRequest below (in my defense, it seemed to work in a test project and was deceiving).
[/Edit]

Everyone is starting new projects with MVC these days, but for all those maintenance projects in ASP.Net WebForms, here is what I've settled on as the best way to detect session timeout. I've had 1 or 2 other methods in production that didn't cause problems, but were not quite correct.

I was quite surprised when initially investigating this issue to see how many ways people had come up with to do it.

I had a completely different version of this up on this post for quite awhile that didn't work when using cookieless sessions.

Here's the assumption: if a user's session has timed out, but they've issued an HTTP POST, that indicates a session timeout. If you're not using cookieless sessions, that's all there is to it; check this somewhere in a page base class:
Request.HttpMethod.Equals( "POST" ) && (Session.IsNewSession)
The problem with cookieless sessions is, you never get to that condition on a page. In either case, Session.IsNewSession by itself is completely useless because that may be true on an initial GET (not a session timeout condition). With cookieless sessions, sometimes it will be true and the request will be a GET (by the time it gets to your page) when the user posts after a session timeout. This is what happens when using cookieless sessions: the user posts up, ASP.Net detects a session timeout and issues a 302 telling the browser to go to the same place, but use GET. Thus, on any page, there is absolutely no way to know whether a timeout condition has occurred.

Consequently, the way I handled this problem is by detecting the above stated condition (user's session has timed out, current request is a post) in Global.asax. A Response.Redirect or Server.Transfer works fine with this. The problem that will occur with this approach is that the flag for session timeout will be set, but before the user with the timeout round trips back to the server with a GET, another user will hit Session_Start and receive an incorrect session timeout condition. Also, the user who timed out might, for whatever reason, not make the round trip back to the server. In this case, again, a user issuing his first request will see a session timeout. I'm willing to accept that, on rare occasion, this happens. Knowing users, he will simply try again, get the page he requested, and think little of it.

Global.asax code:
     public  class  Global  : System.Web.HttpApplication 

     {

 

         private  bool  _sessionTimedOutCookieless = false ;

 

         void  Application_EndRequest( object  sender, EventArgs  e )

         {

             /// This condition occurs when using cookieless sessions (but perhaps not with "AutoDetect" - that mode is evil) 

             /// The redirect will occur within the context of this same application instance, so the private variable will still 

             /// be alive. 

             if  ( Request.HttpMethod.Equals( "POST"  ) && (Response.StatusCode == 302) )

             {

                 _sessionTimedOutCookieless = true ;

             }

         }

 

         void  Session_Start( object  sender, EventArgs  e )

         {

             if  ( _sessionTimedOutCookieless || (SessionTimedOutWithCookies()) )

             {

                 _sessionTimedOutCookieless = false ;

                 Session.Add( "timeout" , true  );

             }

         }

 

         private  bool  SessionTimedOutWithCookies()

         {

             return  Request.HttpMethod.Equals( "POST"  ) && (Session.IsNewSession);

         }

 

     }

 

 




In a base page class:

         protected  void  Page_PreInit()

         {

             if  ( Session["timeout" ] != null  )

             {

                 _timedout = true ; // do something about this wherever 

                 Session.Remove( "timeout"  );

             }

         }