Introduction
'Sessions' in simple terms are nothing but information that is stored and persisted for a specific time period. Asp.net provides three ways to manage sessions.They are
-
In-process Session Management (default)
-
Out-process Session Management (Asp.Net state service)
-
Sql-Server Session Management (using sql server)
For more information on configuring sessions (in web.config) refer
http://msdn2.microsoft.com/library/ms178586(en-us,vs.80).aspx
As long as a web site is deployed in one server, sessions can be managed by using any one of the above-said methods. But in a real-word scenario, critical e-commerce and line-of-business applications have to be deployed in different servers that are managed and integrated using the concept of "Clustering" and "Network Load Balancing (NLB)".
For more details on clustering visit
http://www.microsoft.com/windows2000/techinfo/howitworks/
cluster/introcluster.asp.
Why Custom Components?
In a typical web garden scenario where a particular web site has been deployed in more than one server, Sessions can be maintained using the Asp.Net state service or using the Sql server session management (which are provided by Microsoft). But there are situations where we have to take control of session management (allocating sessions, storing session data and removing sessions). This article provides an insight on creating a custom Session Manager component using c#.
Solution
Step 1: Create the following ISessionManager and ISession interface(s)
public interface ISessionManager
{
string createSessionId();
ISession getSession(string strSesId);
ISession removeSession(string strSesId);
}
public interface ISession
{
Object getAttribute(string strKey);
void setAttribute(string strKey, Object objValue);
Object removeAttribute(string strKey);
}
About ISessionManager
The IsessionManager interface contains the list of generic methods and functionalities that can be used for creating and managing sessions.
- The createSessionId method is used to create a new session id. For instance, a new session id will be allocated when the user hits the web site for the first time.
- The getSession method is used to fetch an existing session. The method returns an object, which has implemented the Isession interface (which would be dealt in detail below).
- The removeSession method is used to remove a particular session allocated to an user and returns the session object that was removed (The session object would be the class that has implemented the Isession interface).
About ISession
The Isession interface contains the list of methods used to store and retrive individual session data/information. For e.g., an online website can store the preferences of an user, temporarily in the session object.
- The getAttribute method returns the value stored against the unique key from the session object from the hash table (all session values are stored in a hash table).
- Similarly setAttribute method is used to assign/set a key/value.
- removeAttribute is used to remove a particular key/value stored in the session.
Managing timeouts
The SessionManager class (which implements the ISessionManager interface), has a timer variable, which would be instantiated when an object of the SessionManager class is created for the first time. This timer variable is liked with a timercallback thread which would be fired for every 8 seconds (which would in turn invoke the call back method). It would check for the "lastAccessedDateTime" value set, for every session and would compare the time difference with the current date time. If it is greater than or equal to 2 house the session would be removed.
Step 2: Implement the ISessionManager interface
using System.Collections;
public class SessionManager : ISessionManager
{
//Hashtable to store the list of sessions allocated.
Hashtable hsSession = new Hashtable();
static SessionManager objSM; //static SINGLETON object
//a timer to manage the session timeouts
private static Timer tmrSessionTimeout;
//Private Constructor - To prevent creating instaces explicitly
private SessionManager()
{
}
//Create a new object of session manager.
public static SessionManager getInstance()
{
if (objSM == null)
{
objSM = new SessionManager();
//create a new objectof the timer class and associte //a timer call back delegate. The removesession
//method will be invoked by the timer object for //every 1000 milliseconds (change it as u like).
tmrSessionTimeout = new Timer(new TimerCallback (objSM.removesession) , null, 0, 100);
return objSM;
}
return objSM;
}
//This method will be invoked by the timer call back for every 1000 milliseconds.
private void removesession(object state)
{
IDictionaryEnumerator objSesEnum = hsSession.GetEnumerator();
//A temporary session object.
ISession tmpSesObj;
ArrayList arrExpiredKeys = new ArrayList();
//for each session, check the last used datetime.
while (objSesEnum.MoveNext())
{
//The session will be removed, if the user is idle for 2 hours. (change it as u like).
tmpSesObj = (ISession) objSesEnum.Value;
DateTime dt = DateTime.Parse(tmpSesObj.getAttribute ("lastAccessedDatetime").ToString());
TimeSpan ts = dt.Subtract(DateTime.Now);
//If the difference between the last accessed //datetime and current date time is equal to 2 hours //then the session id will be added to the arraylist.
//(ie., left idle for 2 hours)
if (dt.Subtract(DateTime.Now).Hours >= 2)
{
arrExpiredKeys.Add(objSesEnum.Key);
}
//remove the list of keys added to the array list.
int i =0;
while (arrExpiredKeys.Count > i)
{
//remove the session from the hashtable.
hsSession.Remove(arrExpiredKeys[i]);
}
}
}
public string createSessionId()
{
//Create a new guid which will act as the session id
string newSessionId = Guid.NewGuid().ToString;
//create a new session object and store the object in the //hashtable against the new session id (GUID).
Session objS = new Session();
hsSession.Add(newSessionId, objS);
//Set the session id into the session object.
objS.setAttribute("SessionId", newSessionId);
//return the session id,
return newSessionId;
}
public ISession getSession(string strSesId)
{
ISession objS = (ISession) hsSession[strSesId];
//everytime when the session object is retried, update the //lastaccesseddate variable with the latest datetime
objS.setAttribute("lastAccessedDatetime", DateTime.Now);
return objS; //return the session object
}
public ISession removeSession(string strSesId)
{
ISession objS = (ISession) hsSession[strSesId];
//remove the sessionid from the hash table.
hsSession.Remove(strSesId);
return objS; //return the removed session
}
}
Step 3: Implement the ISession interface
public class Session : ISession
{
//Hashtable to store the values..
Hashtable hsValues = new Hashtable();
//Default constructor
public Session()
{
}
public object getAttribute(string strKey)
{
//get the value for the key specified
return hsValues[strKey];
}
public void setAttribute(string strKey, Object objValue)
{
//add a new value, if the key already exists then it will //be overridden
hsValues.Add(strKey,objValue);
}
public object removeAttribute(string strKey)
{
object obj = hsValues[strKey]; //fetch the value
hsValues.Remove(strKey);
return obj; //return the value that is removed
}
}
At any time there will be only one instance of the session manager class (as it is declared as static). It must always remain as a singleton object, as persistent storage and maintenance of multiple sessions cannot be achieved otherwise. A new static method getInstance is added into the SessionManager class to create a new instance of the SessionManager class. The constructor of the class is made private to prevent creating objects of the SessionManager component explicitly.
Testing the Session Manager component
Now we can test the session manager component. Create a new console application project and add a reference of the SessionManager component. The following code snippet creates an object of the session manager class (using the getInstance method) and assigns a value in the session object. The WriteLine method prints the value in the session object.
using NpSessionManager; //add the namespace
class SessionManagerTest
{
static string strid;
[STAThread]
static void Main(string[] args)
{
//Create a new session and assign values to the session object.
SessionManager sm = SessionManager.getInstance();
//store the session id in some global variable.
strid = (string) sm.createSessionId();
//get the session
ISession objS = (ISession) sm.getSession(strid);
objS.setAttribute ("website", "c-sharpcorner");
Console.WriteLine(objS.getAttribute("website").ToString());
Console.ReadLine();
}
}
Scalibility and deploying the component
Due to the object oriented design of the session manager component, any amount of flexibility and scalability is achievable.
- The session manager component can be deployed in the following ways
- In-process version (will run the Asp.Net Process / w3wp.exe process, just dll reference is enough)
- Out-process version (Using .net Remoting).
In-process version
In-proc version is achievable by just adding a reference of the session manager component to the web application (can also be used with windows application). During execution the session manager component (Sessionmanager.dll) will be loaded and executed in the AspNet_wp.exe or w3wp.exe (in case of windows 2003 server) process. One of the main disadvantages is the loss of all sessions, whenever the AspNet_wp.exe or w3wp.exe process crashes or restarted. To overcome this issue, the Session Manager component can be deployed as an Out-proc version.
Out-process version
The session manager component can be hosted in a separate process by deploying it using .net Remoting. Just create a remoting application and host the session manager component in the remote server. Using remoting (at runtime), proxy can be generated from the client by accessing the server by providing the requisite port number/path and credentials. (If you need code to host it as a remoting component, just mail me). We can also deploy the component as a Windows Service using .net Remoting.
Conclusion
Even though Microsoft has provided various options to maintain and manage sessions, often developers are caught in scenarios where solutions have to be provided for functionalities that are too complex to solve. In a real-world application, most of the Requirements put-forth are hard to achieve using the available resources. Often, we have to develop our own custom components and techniques to overcome such hardships. The SessionManager component is one of its kinds, which is up and running successfully in a project undertaken by our company.