How to cal Session_End() method when you store the Session state not in Inprc but StageServer, etc.

Reference:http://www.codeproject.com/KB/aspnet/SessionEndStatePersister.aspx
The Session_End event is a useful event which an be handled in Global.asax to perform any actions when a session ends, such as logging an activity to the database, cleaning up temporary session files, etc.

However, when using any kind of state management other than InProc (such as StateServer or SqlStateServer), the ASP.NET web application does not fire the Session_End event, and any code in this method will not be executed.

Some browsing around returned a couple of good articles. The article Page tracking in ASP.NET offers a similar solution, though is geared around page tracking whereas my requirement was simply to find an alternative to the Session_End event that would work with the ASP.NET StateServer.

There's another excellent article called Preventing Multiple Logins in ASP.NET This is where I got the idea of using the application cache with a sliding expiry to trigger an event when the session ends.

The SessionEndModule class hooks into the PreRequestHandlerExecute event and inserts/replaces an item in the application cache, with a sliding expiry equal to the session expiry, and a callback method to be called when the item is removed from the application cache. The key of the cache item is the SessionId, and the value is the value of the item in the session with the key set as the SessionObjectKey.

When the item expires and the callback method is called. The key, value and reason of the expired item is passed to this callback method. The key is the SessionId, the value is the value copied from the session, and the reason is why the item was removed from the cache (was it removed, expired, underused, or it's dependency changed).

The callback method then checks that the item was removed as a result of it expiring, wraps the values into a SessionEndEventArgs class (which exposes SessionId and SessionObject properties), and fires the SessionEnd event.


//Code
using System;
using System.Diagnostics;
using System.IO;
using System.Web;
using System.Web.Caching;
using System.Web.SessionState;

namespace SessionTestWebApp
{
    
/// <summary>
    
/// When an ASP.NET State Server other than InProc, the Session_End event
    
/// does not get fired.  This is an HttpModule which uses some workarounds
    
/// and fires a static event when a session ends, with the value of a single
    
/// configurable session variable in the event arguments.
    
/// </summary>
    public class SessionEndModule : IHttpModule
    {
        
#region Private Variables

        
private HttpApplication m_HttpApplication;
        
private static string m_SessionObjectKey;

        
#endregion

        
#region Accessors

        
/// <summary>
        
/// This is the key of the item in the session which should be returned
        
/// in the SessionEnd event (as the SessionObject).
        
/// </summary>
        
/// <example>
        
///    If you're storing the user ID in the session, under a key called 'UserId'
        
/// and need to do something with it in the SessionEnd event, you would set
        
/// this to 'UserId', which would cause the value of the session key called
        
/// 'UserId' to be returned.
        
/// </example>
        public static string SessionObjectKey
        {
            
get
            {
                
return m_SessionObjectKey;
            }
            
set
            {
                m_SessionObjectKey 
= value;
            }
        }

        
#endregion

        
#region IHttpModule Implementation

        
public void Init(HttpApplication context)
        {
            m_HttpApplication 
= context;
            m_HttpApplication.PreRequestHandlerExecute 
+= new EventHandler(OnPreRequestHandlerExecute);
        }

        
public void Dispose()
        {
            
// Do Nothing
        }

        
#endregion

        
#region Events

        
/// <summary>
        
/// Event raised when the session ends
        
/// </summary>
        public static event SessionEndEventHandler SessionEnd;

        
#endregion

        
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            
// We only want to update the session when an ASPX page is being viewed
            
// We're also doing this in the PreRequestHandler, as doing it elsewhere
            
// (like the PostRequestHandler) can cause some strange behaviour.

            
if (Path.GetExtension(m_HttpApplication.Context.Request.Path).ToLower() == ".aspx")
            {
                
// Ensure we have a HttpContext
                if (HttpContext.Current == null)
                {
                    Debug.WriteLine(
"No current http context");
                    
return;
                }

                
// Get the current session
                HttpSessionState currentSession = HttpContext.Current.Session;

                
// Ensure we have a current session
                if (currentSession == null)
                {
                    Debug.WriteLine(
"No current session");
                    
return;
                }

                
// Get the session timeout
                TimeSpan sessionTimeout = new TimeSpan(00, currentSession.Timeout, 00);

                
// Get the object in the session we want to retrieve when the session times out
                object sessionObject = currentSession[SessionObjectKey];

                
// Add the object to the cache with the current session id, and set a cache removal callback method
                HttpContext.Current.Cache.Insert(currentSession.SessionID, sessionObject, null, DateTime.MaxValue, sessionTimeout, CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(CacheItemRemovedCallbackMethod));
            }
        }

        
/// <summary>
        
/// This method is fired when an item is removed from the cache.  It is used to detect when a cache item
        
/// expires, indicating that the session has expired, and fires the SessionEnd event.
        
/// </summary>
        private void CacheItemRemovedCallbackMethod(string key, object value, CacheItemRemovedReason reason)
        {
            
if (reason == CacheItemRemovedReason.Expired)
            {
                
if (SessionEnd != null)
                {
                    SessionEndedEventArgs e 
= new SessionEndedEventArgs(key, value);
                    SessionEnd(
this, e);
                }
            }
        }
    }

    
/// <summary>
    
/// Event handler for handling the SessionEnd event
    
/// </summary>
    public delegate void SessionEndEventHandler(object sender, SessionEndedEventArgs e);

    
/// <summary>
    
/// SessionEndedEventArgs for use in the SessionEnd event
    
/// </summary>
    public class SessionEndedEventArgs : EventArgs
    {
        
public readonly string SessionId;
        
public readonly object SessionObject;

        
public SessionEndedEventArgs(string sessionId, object sessionObject)
        {
            SessionId 
= sessionId;
            SessionObject 
= sessionObject;
        }
    }
}
//Web.config
<httpModules>
 
<add name="SessionEndModule" type="SessionTestWebApp.Components.SessionEndModule, SessionTestWebApp"/>
</httpModules>

<!-- Use the state server (rather than InProc), and set the timeout to 1 minute for easier testing-->
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="1" cookieless="false"/>
//Global.asx
protected void Application_Start(object sender, EventArgs e)
{
  
// In our sample application, we want to use the value of Session["UserEmail"] when our session ends
  SessionEndModule.SessionObjectKey = "UserEmail";

  
// Wire up the static 'SessionEnd' event handler
  SessionEndModule.SessionEnd += new SessionEndEventHandler(SessionTimoutModule_SessionEnd);
}
private static void SessionTimoutModule_SessionEnd(object sender, SessionEndedEventArgs e)
{
   Debug.WriteLine(
"SessionTimoutModule_SessionEnd : SessionId : " + e.SessionId);

   
// This will be the value in the session for the key specified in Application_Start
   
// In this demonstration, we've set this to 'UserEmail', so it will be the value of Session["UserEmail"]
   object sessionObject = e.SessionObject;
 
   
string val = (sessionObject == null? "[null]" : sessionObject.ToString();
   Debug.WriteLine(
"Returned value: " + val);
}
protected void Session_Start(object sender, EventArgs e)
{
   Debug.WriteLine(
"Session started: " + Session.SessionID);
 
   Session[
"UserId"= new Random().Next(1100);
   Session[
"UserEmail"= new Random().Next(1001000).ToString() + "@domain.com";
 
   Debug.WriteLine(
"UserId: " + Session["UserId"].ToString() + ", UserEmail: " + 
                 Session[
"UserEmail"].ToString());
}

posted on 2009-10-01 21:59  博览潇湘  阅读(1433)  评论(0编辑  收藏  举报

导航