Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

ASP.NET Managed Page Session State

3.00/5 (4 votes)
17 Jan 2009CPOL2 min read 46K   243  
To manage session state within page scope

Introduction

It is very common for a WebForm programmer to store the business object in session state. However, the business object stored in the session is not protected within the page state. It ends up that a business object in one webform may be overwritten when the user opens another browser instance for the same webform.

The solution proposed here is to maintain the session within page state, by giving a unique identifier to each page. By having different page that has its own session state, we may end up with a lot of unused data in memory when the user leaves the page. The solution used here is to provide a Session Garbage Collector to release that unused memory which is recognised as timeout page session.

Background

This is my first attempt to submit an article to The Code Project.

Using the Code

There are 3 main classes in this solution:

  1. PageSession
  2. ManagedSession
  3. ManagedSessionGarbageCollector

1. PageSession

Instead of storing the data in a native Session object, the page data will be stored in the dictionary of a PageSession object.

The PageSession object keeps the last access timestamp. This is to indicate whether a session data needs to be released during garbage collection.

C#
[Serializable]
public sealed class PageSession
{
Dictionary<string, object> _items = new Dictionary<string, object>();
DateTime _timeLog = DateTime.Now;
string _key = Guid.NewGuid().ToString();
internal void TimeLog()
{
_timeLog = DateTime.Now;
}
/// <summary>
/// Gets the unique identifier for the page session.
/// </summary>
public string ID
{
get { return _key; }
}
/// <summary>
/// Gets the last access time for the page session.
/// </summary>
public DateTime LogTime
{
get { return _timeLog; }
}
/// <summary>
/// Gets the number of items in the page session-state collection.
/// </summary>
public int Count
{
get { return _items.Count; }
}
/// <summary>
/// 
/// </summary>
/// <param name="name">The key name of the session value.</param>
/// <returns></returns>
public object this[string name]
{
get
{
if (_items.ContainsKey(name))
{
return _items[name];
}
return null;
}
set
{
if (!_items.ContainsKey(name))
{
_items.Add(name, value);
}
else
{
_items[name] = value;
}
}
}
/// <summary>
/// Removes all keys and values from page session-state collection. 
/// </summary>
public void Clear()
{
_items.Clear();
}
}

2. ManagedSession

The ManagedSession object is responsible for maintaining the PageSession life-cycle. It is also responsible for mapping the correct PageSession to a responding web page. This is done by registering the PageSession unique identifier in the webform viewstate.

C#
[Serializable]
public sealed class ManagedSession
{
private const string JSL_ManageSession = "__JSL_ManageSession";
private const string JSL_PageSession_ID = "__JSL_PageSession_ID";
Dictionary<string, PageSession> _pageSession;
ManagedSessionGarbageCollector _sgc;
public ManagedSession()
{
_pageSession = new Dictionary<string, PageSession>();
_sgc = new ManagedSessionGarbageCollector(_pageSession);
}
/// <summary>
/// Get the page scope session. Always call this in Page_Load event.
/// </summary>
/// <returns>Page scope managed session</returns>
public static PageSession GetPageSession(System.Web.UI.StateBag viewState)
{
return GetManagedSession().RegisterPageSession(viewState);
}
private static ManagedSession GetManagedSession()
{
var ms = System.Web.HttpContext.Current.Session[JSL_ManageSession] as ManagedSession;
if (ms == null)
{
ms = new ManagedSession();
System.Web.HttpContext.Current.Session[JSL_ManageSession] = ms;
}
return ms;
}
private PageSession RegisterPageSession(System.Web.UI.StateBag viewState)
{
string id = "";
if (viewState[JSL_PageSession_ID] != null)
{
id = viewState[JSL_PageSession_ID].ToString();
}
PageSession ps;
if (_pageSession.ContainsKey(id))
{
ps = _pageSession[id];
}
else
{
if (id.Length > 0)
{
var url = ManagedSessionSetting.ExpiredURL;
if (url == "")
throw new Exception("Page session expired!");
if (url == "REFRESH") url = System.Web.HttpContext.Current.Request.Url.PathAndQuery;
System.Web.HttpContext.Current.Response.Redirect(url, true);
}
ps = new PageSession();
_pageSession.Add(ps.ID, ps);
viewState[JSL_PageSession_ID] = ps.ID;
}
ps.TimeLog();
_sgc.GarbageCollection();
return ps;
}
}

3. ManagedSessionGarbageCollector

As the name implies, the ManagedSessionGarbageCollector is responsible for performing garbage collection to release those expired/timeout PageSession maintained by ManagedSession.

C#
internal class ManagedSessionGarbageCollector
{
DateTime _lastCollectionTime = DateTime.Now;
Dictionary<string, PageSession> _managedPageSession;
Thread _garbageCollectorThread;
public ManagedSessionGarbageCollector(Dictionary<string, PageSession> pageSession)
{
_managedPageSession = pageSession; 
}
public void GarbageCollection()
{
lock (this)
{
if (IsCollectable())
{
_garbageCollectorThread = new Thread(new ThreadStart(Collect));
_garbageCollectorThread.Start();
}
}
}
public bool InProcess
{
get
{
if (_garbageCollectorThread == null) return false;
return (_garbageCollectorThread.ThreadState == ThreadState.Running);
}
}
private void Collect()
{
List<string> garbage = new List<string>();
foreach (var id in _managedPageSession.Keys)
{
if (CheckTimeOut(_managedPageSession[id].LogTime))
{
garbage.Add(id);
}
}
garbage.ForEach(id => _managedPageSession.Remove(id));
_lastCollectionTime = DateTime.Now;
}
private bool IsCollectable()
{
bool collectable = false;
if (!InProcess)
{
collectable = CheckTimeOut(_lastCollectionTime);
}
return collectable;
}
private bool CheckTimeOut(DateTime checkPoint)
{
TimeSpan ts = DateTime.Now.Subtract(checkPoint);
if ((ts.Minutes * 60 + ts.Seconds) > ManagedSessionSetting.SessionTimeout)
return true;
return false;
}
}

Code in Action

To use the code, you just need to get the PageSession object from ManagedSession in the Form_Load event.

C#
private JSL.Web.PageSession mySession;
protected void Page_Load(object sender, EventArgs e)
{
  mySession = JSL.Web.ManagedSession.GetPageSession(this.ViewState);
}

After this, you need refer to mySession instead of native Session to store your data.

You may set the timeout in web.config file, default and minimum value is 30 seconds.

XML
<appSettings>
<add key="JSL.ManagedSession.Timeout" value ="60"/>
<add key="JSL.ManagedSession.ExpiredURL" value ="Default2.aspx"/>
</appSettings>

When a page is trying to access a timeout and remove PageSession, an exception will be thrown if the ExpiredURL is not set.

Points of Interest

I use threading for garbage collection. However, I am not good in that area. I hope that some expert on CodeProject will help me to improve the code so that I can learn from it.

History

  • 2009 January 17 - Original version posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)