ASP.NET HttpModule 用于处理 StateServer 的会话结束






4.75/5 (10投票s)
本文解释了在使用 ASP.NET StateServer(它不触发 Session_End 事件)时如何管理会话的结束。
引言
Session_End
事件是一个有用的事件,可以在 Global.asax 中处理,以便在会话结束时执行任何操作,例如将活动记录到数据库、清理临时会话文件等。
但是,当使用 InProc 以外的任何状态管理(例如 StateServer
或 SqlStateServer
)时,ASP.NET Web 应用程序不会触发 Session_End
事件,并且此方法中的任何代码都不会被执行。
背景
经过一番搜索,我找到了一些不错的文章。文章 ASP.NET 中的页面跟踪 提供了一个类似的解决方案,尽管它侧重于页面跟踪,而我的需求只是找到一个适用于 ASP.NET StateServer 的 Session_End
事件的替代方案。
还有另一篇精彩的文章,名为 防止 ASP.NET 中重复登录。我从这里得到了使用具有滑动到期时间的应用程序缓存来在会话结束时触发事件的想法。
工作原理
SessionEndModule
类挂钩到 PreRequestHandlerExecute
事件,并在应用程序缓存中插入/替换一个项,该项具有等于会话到期时间的滑动到期时间,以及一个回调方法,该方法将在项从应用程序缓存中删除时被调用。缓存项的键是 SessionId
,值是会话中键为 SessionObjectKey
的项的值。
当项过期并且调用回调方法时。键、值和原因会作为参数传递给此回调方法。键是 SessionId
,值是从会话复制的值,原因是被移除的原因(是移除、过期、使用不足还是依赖项更改)。
回调方法然后检查该项是否是由于过期而被删除的,将值封装到一个 SessionEndEventArgs
类(该类公开 SessionId
和 SessionObject
属性),并触发 SessionEnd 事件。
使用代码
代码包含一个名为 SessionEndModule
的 HttpModule
,它需要通过 web.config 文件包含在项目中。它公开一个名为 SessionObjectKey
的静态属性和一个名为 SessionEnd
的静态事件,当会话结束时将触发该事件。SessionObjectKey
的值将作为事件参数返回给 SessionEnd 事件。
首先,我们设置 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.asax
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);
}
然后我们需要创建我们的事件处理程序方法来处理 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);
}
在此演示中,我们还将连接 Session_Start
并将一些测试数据放入会话中
protected void Session_Start(object sender, EventArgs e)
{
Debug.WriteLine("Session started: " + Session.SessionID);
Session["UserId"] = new Random().Next(1, 100);
Session["UserEmail"] = new Random().Next(100, 1000).ToString() + "@domain.com";
Debug.WriteLine("UserId: " + Session["UserId"].ToString() + ", UserEmail: " +
Session["UserEmail"].ToString());
}
测试代码
以调试模式启动此项目,并密切关注输出窗口。当会话开始时,会话内容将用随机的 UserId
和 UserEmail
填充。会话将在大约 1 分钟后结束,并触发 SessionEnd 事件,该事件将执行 SessionTimoutModule_SessionEnd
方法,并打印结束会话的 SessionId
以及其中包含的 UserEmail
。
返回多个会话值
HttpModule.SessionEnd
事件仅支持返回单个会话变量的值。如果您需要返回多个值,最简单的方法是创建一个可序列化的类,其中包含所有值的属性,然后将其存储在会话中。
例如
[Serializable]
public class SessionInfo
{
public string UserId;
public string UserName;
public string UserEmail;
}
SessionInfo sessionInfo = new SessionInfo();
sessionInfo.UserId = 10;
sessionInfo.UserName = "Bob Jones";
sessionInfo.UserEmail = "bobjones@company.com";
Session["SessionInfo"] = sessionInfo;
在 Global.asax 中,您将设置 SessionObjectKey 为 'SessionInfo'
SessionEndModule.SessionObjectKey = "SessionInfo";
然后您可以在 SessionEnd 事件中访问它
private static void SessionTimoutModule_SessionEnd(object sender, SessionEndedEventArgs e)
{
Debug.WriteLine("SessionTimoutModule_SessionEnd : SessionId : " + e.SessionId);
SessionInfo sessionInfo = e.SessionObject as SessionInfo;
if (sessionObject == null)
{
Debug.WriteLine("Returned value is null");
}
else
{
Debug.WriteLine("Returned values - UserId: " + sessionInfo.UserId + ", UserName: " +
sessionInfo.UserName + ", UserEmail: " + sessionInfo.UserEmail);
}
}
已知限制
由于模块挂钩到 PreRequestHandler
事件,因此在页面执行之前,应用程序缓存中的 SessionObjectKey
的值会被存储。这意味着,如果您在 SessionEnd
事件(或 SessionInfo
对象)中更改了模块返回的会话变量,然后会话超时,SessionEnd
事件将包含旧值。可以将其更改为使用 PostRequestHandler
事件,尽管我遇到过一些奇怪的行为,尤其是在使用 Response.TransmitFile 时。
在您使用服务器场的情况下,此代码也将不起作用。在文章'防止 ASP.NET 中重复登录'中,Peter Bromberg 对此问题进行了更深入的讨论,并提供了一些解决方案。
历史
版本 1.0 - 2007 年 11 月 02 日