Click here to Skip to main content
Click here to Skip to main content
Go to top

ASP.NET Managed Page Session State

, 17 Jan 2009
Rate this:
Please Sign up or sign in to vote.
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.

[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.

[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.

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.

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.

<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)

Share

About the Author

Jason Law
Software Developer (Senior)
Malaysia Malaysia
Jason has worked on many projects in various roles, including software architecture, design and development, and project management. Over his career he has designed and helped to create systems for Warehouse Management System (WMS), Point of Sales (POS), ERP, Property Sales System, Property Management System, Inventory Control System, Asset Tracking System and etc.

Comments and Discussions

 
GeneralLooks perfect for me! PinmemberLuciano Guimaraes5-May-11 15:56 
GeneralMy vote of 1 PinmemberSaud AKhter19-Jan-09 3:53 
GeneralWhy not use ViewState (w/solution) PinmembermBonafe19-Jan-09 3:28 
QuestionCan you explain the problem? PinmemberAnjum.Rizwi18-Jan-09 19:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 17 Jan 2009
Article Copyright 2009 by Jason Law
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid