ASP.NET Multiple Login Check with "Session Pinging"

We're using a variation of the multiple login (we call it "Session Pinging" - all the required logic resides in the global.asax.cs file) to detect expired sessions on our ASP.NET site. We sometimes have problems where users get to the final stage of an ordering process (in wizard-style forms or shopping carts where we use session to store the user-submitted data) and then walk away from their computers for a little while. NOTE: This is a revision of an older article by John Hodgkinson.

(maybe to get the credit card from supervisor or spouse or whatever - away long enough to expire session). Then the user comes back to their computer, continues filling out the form and clicks submit. Now we have a problem because session has expired and some of user submitted data from earlier steps is lost. We only have a portion of the information we need to process the order. This makes our accounting department hot and also the user mad because we allowed them to submit their order and now have to call them to find out what they were ordering online.

On Session_Start we initialize the session ping by calling PingSession_Init(). PingSession_Init() checks if a "SessionPing" cookie exists on the local users computer, if not, create the cookie and also create and store application-scoped data based on the users session id. Storing the information in the application cache using the session id of the user makes the cached data unique only to the user making the request even though it's stored in the server's memory. The name of the cookie doesn't matter and will live only as long as the browser is open. The use of the cookie helps make sure that when session does expire we know not to re-initialize the entire process again otherwise the expired session would not be detected.

For all page requests on our site we check and make sure the session is valid in the Application_PreRequestHandlerExecute event. If the session has expired, we detect the requested page url (so they can easily re-visit where the where at or going when session expired) and then redirect the user to a custom error page with a back-type link using the captured page request information. In the case of a wizard-style form, we also have logic in place to make sure users are sent back to step 1 (not included since this is local to our application architecture).



If the session is valid, we also timestamp the request and update a session ID based "last accessed" application variable. On our site we have an application cache viewer where we can monitor application data on our site. This step can be removed to decrease page load time as it isn't critical but included to display that the unique application variables based on session id are truely unique.


In the onSubmitClick events of the code-behind pages of our web forms, we also call the PingSession procedure (if you like you can store PingSession in a globally-scoped site library for easy site access and also so you don't have to maintain multiple copies of the procedure) to make sure that Session is still valid before submitting an order and saving the data to the db otherwise the page will be processed before an expired state is detected.

public void btnFormSubmit_Click(object Sender, System.EventArgs Arguments)
{
//ping session, check if expired
PingSession();

//your additional form process logic below:
}

And below is the global.asax PingSession logic:
protected void Session_Start(Object sender, EventArgs e)
{
//init session pinger
PingSession_Init();
}

protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
//get context for local use  
System.Web.HttpContext context = System.Web.HttpContext.Current;
//make sure aspx request, otherwise session may not be available  
string strRequest = context.Request.ServerVariables["SCRIPT_NAME"];
if (strRequest.IndexOf(".aspx",1) > 0)
{
//ping session, check if expired  
PingSession();
}
}

public void PingSession_Init()
{
//get context and session id for local use
System.Web.HttpContext context = System.Web.HttpContext.Current;
string sessionID = context.Session.SessionID;

//make sure unique browser session
if (context.Request.Cookies["sessionPING"] == null)
{
//init cache and session
string sessionKey = "pinging the session object...
             this session will live for " +
Convert.ToString(context.Session.Timeout) +
                    " minutes if it is not pinged again...";
TimeSpan sessionTimeout = new TimeSpan(0, 0, context.Session.Timeout, 0, 0);
context.Cache.Insert("sessionPING_" + sessionID,
           sessionKey, null, DateTime.MaxValue, sessionTimeout,
&nbspSystem.Web.Caching.CacheItemPriority.NotRemovable, null);
context.Cache.Insert("sessionPING_" + sessionID +
               "_lastAccessDate", System.DateTime.Now.ToString(),
&nbspnull, DateTime.MaxValue, sessionTimeout,
               System.Web.Caching.CacheItemPriority.NotRemovable, null);

//init cookie - keep name simple since session id may be
// recycled on expire and this is client specific not site-wide
HttpCookie cookie  = new HttpCookie("sessionPING");
cookie.Value = sessionKey;
context.Response.Cookies.Add(cookie);
}
}

public void PingSession()
{
//get context, timeout, and session id for local use
System.Web.HttpContext context = System.Web.HttpContext.Current;
TimeSpan sessionTimeout = new TimeSpan(0, 0, context.Session.Timeout, 0, 0);
string sessionID = context.Session.SessionID;

//set sample data
string sessionKey = "pinging the session object...  this session will live for " +
Convert.ToString(context.Session.Timeout) +
               " minutes if it is not pinged again...";

//accessing the Cache Item extends the Sliding Expiration automatically
string sUser = Convert.ToString(context.Cache["sessionPING_" + sessionID]);

//check if session still valid
if (sUser == null || sUser == String.Empty)
{
//no Cache item, so session has expired, set the cache item values
context.Cache.Insert("sessionPING_" + sessionID, sessionKey, null,
           DateTime.MaxValue, sessionTimeout,
           System.Web.Caching.CacheItemPriority.NotRemovable, null);
context.Cache.Insert("sessionPING_" + sessionID + "_lastAccessDate",
            System.DateTime.Now.ToString(), null, DateTime.MaxValue, sessionTimeout,
            System.Web.Caching.CacheItemPriority.NotRemovable, null);

//get requested url and redirect - make sure endResponse arguement is passed true
// so additional logic is not processed, ie: bypass additional form process logic
string ReturnUrl = context.Request.Url.AbsoluteUri;
context.Response.Redirect("yourExpiredSessionPage.aspx?ReturnUrl="
             + ReturnUrl, true);
}
else
{
//update last access date
context.Cache.Insert("sessionPING_" + sessionID + "_lastAccessDate",
System.DateTime.Now.ToString(), null, DateTime.MaxValue, sessionTimeout,
System.Web.Caching.CacheItemPriority.NotRemovable, null);
}
}

John Hodgkinson is the Web Application Architect/Lead Web Developer at the North Carolina Bar Association in Cary, NC ( http://www.ncbar.org ).

By Peter Bromberg   Popularity  (4059 Views)