项目
版本

如何在 Web 应用中创建基于会话的生命周期范围?

在 ASP.NET 中,“每个请求一个实例”的概念是内置的,但你可能希望为某些对象创建 “每个会话一个实例” 。这条路充满了危险,并且完全不被支持。 由于这个问题经常被问到,我们提供了一些关于如何让它正常工作的信息,这是基于 StackOverflow 上的一个答案,但如果它对你不起作用,或者你需要额外的支持来实现它,你将只能自己解决

此外,这些信息适用于经典 ASP.NET MVC,而不是 ASP.NET Core,但仍然会遇到相同的挑战。很可能不会为 ASP.NET Core 更新这些内容。它也可能不会为 Web API、web 表单或其他任何其他集成更新。你需要根据需要从这里获取想法并进行调整。

这是一个坏主意的原因

在你开始之前,这里是你使用会话范围生命周期会遇到的一些挑战:

内存占用

你最终会为系统中的每个用户都创建一个生命周期范围。虽然请求级别的范围很快出现并消失,但会话级别的范围可能会持续很长时间。如果你有很多会话范围内的项目,每个用户的内存使用量都会相当大。如果人们没有正确注销就“放弃”他们的会话,那么这些东西将会活得更久。

不适合分布式环境

生命周期范围及其内容不可序列化。看看LifetimeScope代码,它并没有标记为[Serializable]……即使标记了,存储在其中的解析对象也不一定都被标记为可序列化。这很重要,因为这意味着你的会话级别生命周期范围可能在一个具有内存会话的单个盒子上工作,但是如果你部署到具有 SQL 会话或会话服务的农场,事情就会崩溃,因为会话无法序列化你存储的范围。

如果你选择不序列化范围,那么在机器之间,每个用户都有不同的范围——这也是一个问题。

会话不一致使用

会话并不总是重新激活。如果访问的处理器(例如,web 表单)没有实现 IRequiresSessionState ,则不会重新激活会话(无论是否在进程内)。Web 表单和 MvcHandler 默认实现了这一点,所以你不会遇到任何问题,但如果你有需要注入的自定义处理器,你会遇到一些问题,因为这些请求中的“会话”不存在。对于明确标记不需要会话的处理器(例如,出于性能原因),你也会有麻烦。

不可靠的释放

Session_End 并不总是触发。根据SessionStateModule.End文档,如果你使用进程外会话状态,实际上你不会收到 Session_End 事件,因此无法清理。

如何做到这一点

假设你已经阅读了所有这些内容,并且想要这样做。

至少在经典 ASP.NET MVC 中,你需要实现自己的Autofac.Integration.Mvc.ILifetimeScopeProvider。这个接口决定了何时/在哪里生成请求生命周期范围。

具体如何实现将取决于你自己。 这是因为上述所有挑战。例如,你将在哪里保存会话级别的生命周期范围?它是附加到实际会话(由于序列化而存在问题)吗?还是在某个静态字典中?还是你想在其他地方保存这些引用?这些问题都不能在这里回答——这很大程度上都是“读者的练习”。

默认的 ILifetimeScopeProvider 实现,Autofac.Integration.Mvc.RequestLifetimeScopeProvider,负责按请求创建、管理和维护生命周期范围。你可以在这里浏览 RequestLifetimeScopeProvider 的代码:这里 ,如果你打算这样做,这是最好的示例代码,展示了此类提供程序的责任。

ILifetimeScopeProvider的实现将是你……

  • 为用户找到(或创建)会话生命周期范围
  • 将请求生命周期范围作为会话生命周期范围的子范围创建
  • 在请求结束时释放请求生命周期范围

这可能也是从设计角度看,你想要在何处释放会话生命周期范围,但它可能很棘手,因为提供程序不会自动接收到会话结束事件。

一旦你有了 ILifetimeScopeProvider ,你就可以在设置依赖注入解析器时使用它。

var scopeProvider = new MyCustomLifetimeScopeProvider(container, configAction);
var resolver = new AutofacDependencyResolver(container, scopeProvider);
DependencyResolver.SetResolver(resolver);

你还需要挂钩到Session_End事件(例如,在 Global.asaxMvcApplication 类中)来释放会话范围。再次,具体如何做完全取决于你,因为 ILifetimeScopeProvider 不会接收任何与会话相关的事件。

在本文档中