Autofac - 生命周期和模块

9

问题(摘要)

给定一个注册依赖X的模块。在MVC3应用程序中,依赖项X的生命周期(每个HttpRequest)与控制台应用程序中(具有名称的依赖项期间范围)不同。在哪里或如何指定依赖项X的生命周期?

案例

我将所有与数据库相关的代码放在一个包含模块的程序集中,该模块注册所有存储库。现在ISession(Nhibernate)的注册也在该模块中。

ISession是给定问题示例中的依赖项X。在MVC3应用程序中,ISession的寿命(每次请求)与在控制台应用程序中定义命名的生命周期范围不同。

ISession的注册是否应该在模块之外?这很奇怪,因为它是实现细节。

在这里做什么是最好的情况?设计缺陷还是有一些聪明的构造方法:)


你是否有连接的具体生命周期?如果有,每个应用程序的生命周期是否不同,还是无论应用程序类型如何总是相同的?连接池呢?您在某些应用程序中使用连接池,而在其他应用程序中则不使用吗? - Travis Illig
就像你所说的“MVC扩展使用命名范围来实现一次请求机制”。当你在控制台应用程序中使用该模块时,Http命名范围不可用。目前我正在研究工作单元将是什么,并且将封装在一个范围内。 - mark_dj
2个回答

7
根据您的用例描述,我认为您有几个选择。
首先,您可以让每个应用程序注册自己的依赖项,包括生命周期范围。在这方面有一两个“重复”的代码并不是什么大问题,考虑到应用程序之间的差异以及注册似乎相当小。
其次,您可以将公共部分(减去生命周期范围)包装成一个ContainerBuilder扩展方法,该方法可在每个应用程序中使用。这仍然意味着每个应用程序都有一些“重复代码”,但是公共逻辑将被包装在一个简单的扩展中。
public static IRegistrationBuilder<TLimit, ScanningActivatorData, DynamicRegistrationStyle>
  RegisterConnection<TLimit, ScanningActivatorData, DynamicRegistrationStyle>(this ContainerBuilder builder)
{
  // Put the common logic here:
  builder.Register(...).AsImplementedInterfaces();
}

使用这样的扩展,在每个应用程序中的使用方式如下所示:

在每个应用程序中使用此扩展的代码如下:

builder.RegisterConnection().InstancePerHttpRequest();
// or
builder.RegisterConnection().InstancePerLifetimeScope();

最后,如果你知道这是网站还是非网站,你可以创建一个自定义模块来处理开关

public class ConnectionModule : Autofac.Module
{
  bool _isWeb;
  public ConnectionModule(bool isWeb)
  {
    this._isWeb = isWeb;
  }

  protected override void Load(ContainerBuilder builder)
  {
    var reg = builder.Register(...).AsImplementedInterfaces();
    if(this._isWeb)
    {
      reg.InstancePerHttpRequest();
    }
    else
    {
      reg.InstancePerLifetimeScope();
    }
  }
}

在每个应用程序中,您都可以注册该模块:
// Web application:
builder.RegisterModule(new ConnectionModule(true));

// Non-web application:
builder.RegisterModule(new ConnectionModule(false));

另外,你提到你其他应用程序中的lifetime scope有一个名称。 你可以让你的模块采用这个名称

public class ConnectionModule : Autofac.Module
{
  object _scopeTag;
  public ConnectionModule(object scopeTag)
  {
    this._scopeTag = scopeTag;
  }

  protected override void Load(ContainerBuilder builder)
  {
    var reg = builder.Register(...)
                     .AsImplementedInterfaces()
                     .InstancePerMatchingLifetimeScope(this._scopeTag);
  }
}

消费类似:

// Web application (using the standard tag normally provided):
builder.RegisterModule(new ConnectionModule("httpRequest"));

// Non-web application (using your custom scope name):
builder.RegisterModule(new ConnectionModule("yourOtherScopeName"));
我建议在Web应用程序中不要简单地使用InstancePerLifetimeScope,除非这确实是你想要的。正如其他答案/评论中指出的那样,InstancePerHttpRequest使用特定的命名生命周期范围,以便安全地创建子生命周期范围;而使用InstancePerLifetimeScope没有这样的限制,因此会为每个子范围实际上获取一个连接,而不是每个请求获取一个连接。就我个人而言,我不认为其他开发人员不会使用子生命周期范围(这是推荐的做法),因此在我的应用程序中我非常具体。如果您完全控制应用程序,并且可以保证您不会创建额外的子范围,或者您确实希望每个范围都有一个连接,那么也许InstancePerLifetimeScope可以解决您的问题。

感谢您详尽的回答!我想到了这个方案:https://gist.github.com/2883596 您可以调用ConfigureUnitOfWork来配置依赖项IUnitOfWork,这将隐藏实现细节。此外,这种解决方案不依赖于MVC3扩展程序。这种解决方案的唯一缺点是,您可以在配置所有内容时只想允许生命周期,但可以将其抽象化处理,目前只能这样 :) - mark_dj

0

通常使用每个 HTTP 请求一个连接的做法。在这种情况下,可以使用 .InstansePerLifetimeScope() 注册连接。例如,您可能会这样做:

builder
    .Register(c => {
                       var conn = new SqlConnection(GetConnectionString());
                       conn.Open();
                       return conn;
                   })
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

是的,我知道你可以这样做。我的问题是在哪里做这个?这应该是“数据”模块的职责。然而,似乎没有办法在模块内注册连接并在模块外设置生命周期。 - mark_dj
我会在数据模块内注册连接并设置生命周期。 - Jim Bolla
这就是在MVC应用程序中的问题,其生命周期与控制台应用程序不同,在控制台应用程序中没有像Http这样的上下文。 - mark_dj
InstancePerLifetimeScope() 是不可知的。在控制台应用程序中,可能只有一个生命周期范围。在 asp.net 应用程序中,autofac 将为您将生命周期范围绑定到 httpcontext 上。实际上非常聪明。 - Jim Bolla
1
仅使用InstancePerLifetimeScope不能保证每个HTTP请求只有一个实例。如果在单个请求期间创建任何嵌套的生命周期范围,您也会得到每个嵌套范围的一个实例。MVC扩展使用命名范围来实现一次请求机制。 - Travis Illig
没错,但我想在内部生成嵌套作用域是不常见的,即使你这样做了,你可能需要或至少可以接受嵌套作用域的第二个数据库连接。 - Jim Bolla

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