"HttpContext.Current.Session"与Global.asax中的"this.Session"有何区别?

13

最近,在工作中为ASP.NET项目编写代码时,我们需要一个跟踪工具来记录用户活动的基本指标(页面点击次数等),我们将它们存储在Session中,然后通过Global.asax中的Session_End将数据保存到数据库中。

我开始着手编写代码,最初的代码可以正常工作,在每个页面加载时更新数据库。但是,我想避免在每个请求上打开数据库连接,并且只依赖于Session_End来存储所有数据。

所有跟踪代码都封装在Tracker类中,包括实质上包装Session变量的属性。

问题是,当我在Session_End方法中执行Tracker.Log()时,Tracker代码中的HttpContext.Current.Session失败并出现了NullReferenceException。现在,这很有道理,因为HttpContext总是与当前请求相关联,当然在Session_End中没有请求。

我知道Global.asax有一个Session属性,它返回一个HttpSessionState,似乎工作得很好(最终我将其注入到了跟踪器中)。
但我很好奇,我怎么能从Global.asax之外获取与Global.asax使用的HttpSessionState对象相同的引用呢?
提前感谢各位,我很感激你们的回答。 :)

另外需要注意的是,Session_End 方法也不能保证一定会被调用。这种情况在使用 SQL Session 和 out-of-state session 设置时经常发生。 - Ray Booysen
有没有人可以友好地为初学者提供一个代码片段,以便理解如何在“in Proc”模式下访问存储在应用程序中的current.session中的值,并在global.asax文件中访问它。我是否可以在session_end中简单地使用this.session,还是需要一些额外的代码? - Unbreakable
此外,如果有多个用户登录到网站,则 this 将分别指向每个请求,对吗? - Unbreakable
7个回答

18
为了更好地回答原问题:
背景
每个页面请求都会启动一个新的 Session 对象,然后从会话存储中填充它。为此,它使用客户端提供的 cookie 或特殊的路径结构(用于无 cookie 会话)。使用这个会话标识符,它查询会话存储并反序列化(这就是为什么除了 InProc 提供程序外,所有提供程序都需要可序列化)新的 Session 对象。
在 InProc 提供程序的情况下,仅将存储在 HttpCache 中以会话标识符为键的引用交给您。这就是为什么当 AppDomain 被回收时(以及多个 Web 服务器不能共享 InProc 会话状态时),InProc 提供程序会丢弃会话状态的原因。
这个新创建和填充的对象被固定在 Context.Items 集合中,以便在请求期间可用。
任何对 Session 对象的更改都将在请求结束时持久化到会话存储中,通过序列化实现(或者在 InProc 的情况下,更新 HttpCache 条目)。
由于 Session_End 在没有当前请求的情况下触发,所以 Session 对象是 ex-nilo 启动的,没有可用的信息。如果使用 InProc 会话状态,则 HttpCache 的过期会触发回调事件到您的 Session_End 事件,因此会话条目可用,但仍然是最后存储在 HttpContext.Cache 中的副本。该值通过一个内部方法(称为 ProcessSpecialRequest)存储在 HttpApplication.Session 属性中,然后可用。在所有其他情况下,它内部来自 HttpContext.Current.Session 值。
您的答案

由于Session_End事件总是针对空Context触发,因此在该事件中应始终使用this.Session,并将HttpSessionState对象传递给跟踪代码。在所有其他上下文中,从HttpContext.Current.Session获取然后传递到跟踪代码是完全可以的。但不要让跟踪代码获取会话上下文。

我的回答

除非您知道正在使用的会话存储支持 Session_End,否则不要使用 Session_End,如果它从 SetItemExpireCallback 返回 true,则会话存储支持 Session_End。唯一支持它的内置存储是 InProcSessionState 存储。虽然可能编写支持它的会话存储,但如果有多个服务器,则处理 Session_End 的问题有些模糊。


11

Global.asax 实现 HttpApplication - 当您在其中调用 this 时,就是在与它交互。

HttpApplication 的 MSDN 文档详细介绍了如何在 HttpHandler 中获取它,然后访问其中的各种属性。

但是

您的应用程序可以创建多个 HttpApplication 实例来处理并行请求,并且这些实例可以被重复使用,因此仅仅以某种方式获取它并不能保证您拥有正确的实例。

我也要提醒一点 - 如果您的应用程序崩溃,那么不能保证 session_end 将会被调用,您将会失去所有会话中的数据,这显然不是一件好事。

我同意在每个页面上记录日志可能不是一个好主意,但是也许可以在一些异步日志记录发生的中间阶段做出妥协 - 您将详细信息发送到一个记录类中,该类偶尔记录您需要的详细信息 - 如果应用程序崩溃仍然不够稳定,但您不太可能失去所有内容。


很棒的回答,帮助我理清了思路。最后一段话我完全同意。考虑在跟踪器上设置滑动过期时间,以便更早地记录信息,而不需要Session_End。再次感谢 :) - Rob Cooper

4

我认为你已经回答了自己的问题:通常情况下,Global.asax中的Session属性和HttpContext.Current.Session是相同的(如果有当前请求的话)。但在会话超时的情况下,没有活动请求,因此您无法使用HttpContext.Current。

如果您想从Session_End调用的方法中访问会话,则将其作为参数传递。创建一个重载版本的Log()方法,它以HttpSessionState作为参数,然后从Session_End事件处理程序调用Tracker.Log(this.Session)。

顺便说一句:您知道在任何情况下都不能依赖于会话结束事件吗?只有在使用进程内会话状态时才能正常工作。当使用SQL服务器或StateServer来管理会话状态时,会话结束事件将不会触发。


但我的问题是,“我如何获得与Global.asax使用的相同对象的引用”? :) - Rob Cooper

3

只有在Web.config文件中设置sessionstate modeInProc时,才会触发Session_End事件。如果会话模式设置为StateServerSQLServer,则不会触发该事件。

使用Session["SessionItemKey"]来获取会话值。


0

好的,我也遇到了跟踪会话活动的同样问题。我没有使用session_end事件,而是将IDisposable接口和析构函数实现到我的sessiontracker类中。我修改了Dispose()方法以将会话活动保存到数据库中。当用户单击注销按钮时,我调用了obj.Dispose()方法。如果用户意外关闭了浏览器,那么GC在清理对象时将调用析构函数(不是立即调用,但肯定会在一段时间后调用此方法)。析构函数内部执行相同的Dispose()方法以将会话活动保存到数据库中。

-Shan


0

在Global.asax文件中,可以在Session_Start事件中使用Session。也许可以等到这个时候再做一些事情?


-1

请记住,当会话超时且没有活动时,Session_End 将运行。浏览器不会发起该事件(因为它处于非活动状态),因此您实际上只有在使用 InProc 提供程序时才会收到该事件。在其他任何提供程序中,此事件都不会触发。

道德教训?不要使用 Session_End。


有道理,但问题显然是基于我们正在使用Session_End(无论是好还是坏的选择)的假设。 - Rob Cooper
那么,以一种可能会出乎意料地失败的方式告诉别人他们做错了,这是一个不好的回答吗?请参考“敲钉子:用旧鞋还是玻璃瓶?”http://is.gd/gSb1 - IDisposable
2
就记录而言,我完全同意您帖子的主要观点,但我个人认为它并没有对问题本身做出“答案”的贡献。在我看来,“不要使用”并没有什么帮助.. “不要使用,但尝试这个”可能会更有用.. :) - Rob Cooper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接