在ASP.NET MVC 3中管理AutoFac的生命周期范围,每个会话和请求一个。

6

我想在一个Web应用程序中使用AutoFac。我有根容器,每个会话一个子容器和每个请求一个子容器。我正在尝试弄清楚最佳的方法来管理这些生命周期范围。在Global.asax.cs中,我添加了以下内容:

protected void Application_Start(object sender, EventArgs e)
{
    var container = ...;
}

protected void Session_Start(object sender, EventArgs e)
{
    var sessionScope = container.BeginLifetimeScope("session");

    Session["Autofac_LifetimeScope"] = sessionScope;
}

protected void Application_BeginRequest(object sender, EventArgs e)
{
    var sessionScope = (ILifetimeScope) Session["Autofac_LifetimeScope"];
    var requestScope = sessionScope.BeginLifetimeScope("httpRequest");

    HttpContext.Current.Items["Autofac_LifetimeScope"] = requestScope;
}

protected void Application_EndRequest(object sender, EventArgs e)
{
    var requestScope = (ILifetimeScope)HttpContext.Current.Items["Autofac_LifetimeScope"];
    requestScope.Dispose();
}

protected void Session_End(object sender, EventArgs e)
{
    var sessionScope = (ILifetimeScope)Session["Autofac_LifetimeScope"];

    sessionScope.Dispose();
}

protected void Application_End(object sender, EventArgs e)
{
    container.Dispose();
}
  1. 我如何告诉AutoFac使用我的请求作用域作为获取依赖项的起点,以便我注册为InstancePerLifetimeScope的实现将使用我的请求作用域解析?
  2. 如果这不可能,我能否让AutoFac从我的会话范围创建其每个请求的生命周期范围?
  3. 或者我走错了路?是否有其他方法使AutoFac意识到这种层次结构?

任何帮助或其他评论都将不胜感激。


回应Steven的问题:

我还处于原型设计的早期阶段,但是在sessionScope中可能存在以下内容:

  • 用户首选项
  • 身份验证和授权上下文(例如用户身份和角色)

与我要构建的应用程序无关,但在电子商务环境中,购物车可以是sessionScope。这可能是最好的具体示例。它是您希望比请求更长但比应用程序更短的东西。

可能会有更多,但是如果我有一个UserPreferences、Authentication和Authorization的策略,那么该策略也可以应用于稍后创建的其他组件。

一种可能的替代方案是在请求开始时获取所有必要信息,并将这些配置的组件放置在请求范围内。这将给我带来我期望的结果,但它不符合我心中关于应用程序-会话-请求层次结构的模型。我希望创建一个有意义的系统,因为我肯定不是维护它的人。


您希望使用每个会话范围注册哪些服务?这对于我们回答第3个问题“我在这条路上错了吗?”非常重要。 - Steven
1个回答

14
你需要做的是实现自己的 Autofac.Integration.Mvc.ILifetimeScopeProvider 接口。这个接口控制了请求生命周期范围的生成方式和位置。默认的那个,Autofac.Integration.Mvc.RequestLifetimeScopeProvider,在每个请求基础上处理生命周期范围的创建、清理和维护。 如果你计划进行这项工作,我强烈建议你浏览这里的 RequestLifetimeScopeProvider 代码,这是我能想到的最好的样本,其中包含演示此类事物职责的工作代码。 您的ILifetimeScopeProvider实现将是获取会话子容器、从中生成请求容器以及在请求结束时清理请求容器的地方。如果不存在会话容器,您还可以在其中创建会话容器。处理会话容器的清理/处理可能会有些棘手,但从设计角度来看,如果所有内容都在提供程序中而不是在应用程序类中,则更好。
一旦您拥有了ILifetimeScopeProvider,您将在设置依赖项解析器时使用它。
var scopeProvider = new MyCustomLifetimeScopeProvider(container, configAction);
var resolver = new AutofacDependencyResolver(container, scopeProvider);
DependencyResolver.SetResolver(resolver);

关于会话级别作用域的概念,有几点需要注意:
  1. 你的内存占用可能会很大。 您将为系统上每个用户都创建一个终身范围。虽然请求生命周期会很快弹出并消失,但这些会话级别的范围可能会长时间存在。如果您有许多会话范围的项目,则每个用户的内存使用量都会相当大。如果人们没有正确注销就“放弃”了他们的会话,那么这些项目的寿命就会更长。
  2. 终身范围及其内容不可序列化查看LifetimeScope的代码,它没有标记[Serializable]... 即使标记了,其中存储的已解析对象也不一定都标记为可序列化。这很重要,因为它意味着您的会话级别的终身范围在具有内存中会话的单个框中可能有效,但如果您部署到具有SQL会话或会话服务的农场,则会出现问题,因为会话无法序列化您存储的范围。如果选择不序列化范围,则每个用户在不同机器上都有不同的范围-这也是一个潜在的问题。
  3. 会话并非总是被重新加载。 如果被访问的处理程序(例如Web表单)不实现IRequiresSessionState,则会话将不会被重新加载(无论它是在进程中还是不在进程中)。Web表单和MvcHandler默认实现了该功能,因此您不会遇到任何问题,但如果您有需要注入的自定义处理程序,则会出现一些问题,因为这些请求的“会话”不存在。
  4. Session_End并非总是触发根据SessionStateModule.End上的文档,如果使用了外部进程会话状态,则实际上不会收到Session_End事件,因此您将无法进行清理。

考虑到限制,通常最好避免使用会话存储的范围。然而...如果这是你要做的事情,那么ILifetimeScopeProvider就是实现它的方式。


而且,当扩展到 Web Farm 时,您的应用程序将出现内存泄漏问题,因为在使用 out-of-proc 会话状态时,Session_End 永远不会被调用。 - Steven
好观点,@Steven - 我将其添加到问题列表中,并添加了一条注释,指出如果您不实现IRequiresSessionState,则将没有会话...这意味着也没有会话容器。 - Travis Illig
@Steven:它不会成为下一个Twitter。不需要扩展到Web农场。 - Jeroen
随时欢迎在Autofac Google Group邮件列表中直接向Nick提问。如果提供具体示例,那么困难的概念会变得更容易理解,但是对于大多数人而言,如果不是会话范围,很难想出一个简单的嵌套生命周期范例。没有推荐的方法可以在不遇到所有上述问题的情况下完成你所要求的工作,这需要进行相当数量的额外工作。我并不是故意让事情变得困难,事实就是如此。 - Travis Illig
@TravisIllig:我一直在研究AutoFac Google组,并且似乎在那里给出的答案是将可能访问会话状态的组件注册到请求范围内。这仍然感觉很糟糕。实现ILifetimeScopeProvider也是一种选择。我需要再考虑一下(幸运的是,该项目还要几周才开始)。谢谢。 - Jeroen
显示剩余4条评论

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