(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,
 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);
//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 ).