ASP.NET 管理页面会话状态
在页面范围内管理会话状态
引言
WebForm 程序员通常会将业务对象存储在会话状态中。但是,存储在会话中的业务对象并未受到页面状态的保护。结果是,一个 WebForm 中的业务对象可能会在用户打开同一 WebForm 的另一个浏览器实例时被覆盖。
这里提出的解决方案是在页面状态中维护会话,为每个页面赋予一个唯一的标识符。通过拥有各自会话状态的不同页面,当用户离开页面时,可能会在内存中留下大量未使用的数据。这里使用的解决方案是提供一个会话垃圾回收器来释放这些被识别为超时页面会话的未使用内存。
背景
这是我第一次尝试向 The Code Project 提交文章。
Using the Code
这个解决方案包含 3 个主要的类
PageSession
ManagedSession
ManagedSessionGarbageCollector
1. PageSession
与将数据存储在原生 Session
对象中不同,页面数据将存储在 PageSession
对象的字典中。
PageSession
对象会保存上次访问的时间戳。这用于指示在垃圾回收期间是否需要释放会话数据。
[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
ManagedSession
对象负责维护 PageSession
的生命周期。它还负责将正确的 PageSession
映射到响应的 Web 页面。这是通过在 WebForm 的 ViewState 中注册 PageSession
的唯一标识符来完成的。
[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
顾名思义,ManagedSessionGarbageCollector
负责执行垃圾回收,以释放由 ManagedSession
维护的那些已过期/超时的 PageSession
。
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;
}
}
代码示例
要使用此代码,您只需要在 Form_Load
事件中从 ManagedSession
获取 PageSession
对象。
private JSL.Web.PageSession mySession;
protected void Page_Load(object sender, EventArgs e)
{
mySession = JSL.Web.ManagedSession.GetPageSession(this.ViewState);
}
之后,您需要引用 mySession
而不是原生 Session
来存储您的数据。
您可以在 web.config 文件中设置超时时间,默认值和最小值是 30 秒。
<appSettings>
<add key="JSL.ManagedSession.Timeout" value ="60"/>
<add key="JSL.ManagedSession.ExpiredURL" value ="Default2.aspx"/>
</appSettings>
当页面尝试访问超时并删除 PageSession
时,如果未设置 ExpiredURL
,将抛出异常。
关注点
我使用了线程进行垃圾回收。但是,我在这方面并不擅长。我希望 CodeProject 上的专家能够帮助我改进代码,以便我能够从中学习。
历史
- 2009 年 1 月 17 日 - 发布原始版本