【问题标题】:Autofac: any way to resolve the innermost scope?Autofac:有什么办法可以解决最里面的范围?
【发布时间】:2012-02-24 16:05:57
【问题描述】:

在过去几年使用过 Ninject、Castle Windsor 和其他 IoC 容器之后,我目前正在一个新的 ASP.NET MVC 项目中尝试 Autofac。因此,虽然我对 IoC 容器有所了解,但我对 Autofac 还很陌生,我仍在寻找一些最佳实践。

目前我正在尝试找出是否有办法解决最里面的嵌套范围。

我有以下情况:注册为SingleInstance()的组件有一个方法创建嵌套的生命周期范围,提供配置动作将一些组件配置为InstancePerLifetimeScope,并在这个嵌套范围内解析注册的组件做有用的东西,比如:

ILifetimeScope currentScope = ???;

using (var scope = currentScope.BeginLifetimeScope(cb => {
  cb.RegisterType<X>().InstancePerLifetimeScope();
  // ...
}))
{
    var comp = scope.Resolve<X>();
    // ...
}

问题是我希望 currentScope 成为最内层的生命周期范围,因为我知道 X 依赖于最内层范围内的组件。在最简单的情况下,例如当前请求生命周期范围。我当然可以使用 AutofacDependencyResolver.Current.RequestLifetimeScope 来获得它,但我不想使用它,因为它的可测试性并不好。此外,生命周期范围不一定是最里面的。

那么,有没有办法找到给定的最里面的生命周期范围,例如根容器还是不同的 ILifetimeScope?

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 ninject autofac


    【解决方案1】:

    在 Autofac 中,最内部的范围始终是容器。使用 AutofacDependencyResolver,它会是 AutofacDependencyResolver.Current.ApplicationContainer

    没有办法从嵌套范围(如果你只有一个ILifetimeScope)“向后走”到容器。无论如何,我不一定确定你想这样做。

    听起来您的 SingleInstance 组件正在执行某种服务定位,基本上是手动注册/解析某些组件。如果要注册的类型集是固定的,我可能会建议(如果可能)重新设计您的系统,因此 SingleInstance 组件不再注册为 SingleInstance 而是注册为 InstancePerDependency,然后将这些其他项目作为构造函数参数。

    而不是...

    // Consuming class like this...
    public class BigComponent
    {
      public void DoSomethingCool()
      {
        using(var scope = ...)
        {
          var c = scope.Resolve<SubComponent>();
          c.DoWork();
        }
      }
    }
    
    // ...and container registrations like this...
    builder.RegisterType<BigComponent>().SingleInstance();
    

    你可以试着颠倒一下:

    // Consuming class like this...
    public class BigComponent
    {
      private SubComponent _c;
      public BigComponent(SubComponent c)
      {
        _c = c;
      }
      public void DoSomethingCool()
      {
        _c.DoWork();
      }
    }
    
    // ...and container registrations like this...
    builder.RegisterType<BigComponent>().InstancePerDependency();
    builder.RegisterType<SubComponent>().InstancePerLifetimeScope();
    

    这个想法是不必做即时注册和立即解决的事情。

    如果您在执行服务定位时遇到困难,如果您需要绝对最内层范围,则需要使用 AutofacDependencyResolver.Current.ApplicationContainer,但请记住,如果您这样做,您注册范围为 InstancePerHttpRequest 的任何对象都将无法解析,所以你可能会遇到麻烦。确实建议改用AutofacDependencyResolver.Current.RequestLifetimeScope。这将使您的方法:

    var requestScope = AutofacDependencyResolver.Current.RequestLifetimeScope;
    using (var scope = requestScope.BeginLifetimeScope(cb => {
      cb.RegisterType<X>().InstancePerLifetimeScope();
      // ...
    }))
    {
        var comp = scope.Resolve<X>();
        // ...
    }
    

    在测试环境中,AutofacDependencyResolver 允许您交换提供程序,该提供程序指示如何生成请求生命周期。您可以像这样实现一个简单/存根:

    public class TestLifetimeScopeProvider : ILifetimeScopeProvider
    {
        readonly ILifetimeScope _container;
        private ILifetimeScope _lifetimeScope = null;
    
        public TestLifetimeScopeProvider(ILifetimeScope container)
        {
            if (container == null) throw new ArgumentNullException("container");
            _container = container;
        }
    
        public ILifetimeScope ApplicationContainer
        {
            get { return _container; }
        }
    
        public ILifetimeScope GetLifetimeScope()
        {
            if (_lifetimeScope == null)
            {
                _lifetimeScope = ApplicationContainer.BeginLifetimeScope("httpRequest")
            }
            return _lifetimeScope;
        }
    
        public void EndLifetimeScope()
        {
            if (_lifetimeScope != null)
                _lifetimeScope.Dispose();
        }
    }
    

    再说一遍,这只是一个用于单元测试的存根,而不是您在生产中使用过的东西。

    然后,当您在测试中连接 DependencyResolver 时,您将提供您的生命周期范围提供程序:

    var lsProvider = new TestLifetimeScopeProvider(container);
    var resolver = new AutofacDependencyResolver(container, lsProvider);
    DependencyResolver.SetResolver(resolver);
    

    这使您可以在没有实际请求上下文的情况下使用InstancePerHttpRequest 和此类内部单元测试。这也意味着您应该能够在注册/解析方法中使用请求生命周期范围,而不必依赖应用程序容器。

    【讨论】:

    • 在我的例子中,“BigComponent”是一个在应用程序的整个生命周期中保存某些对象的注册表。在应用程序启动(第一个 HTTP 请求)期间使用嵌套范围来实例化一些动态发现的类型(插件架构)。我当前的解决方案是将 Func 注入当前注册为()=&gt;AutofacDependencyResolver.Current.RequestLifetimeScope 的“BigComponent”。仍然不是很好,但是它将对服务定位器的静态引用排除在范围之外。 [继续]
    • 顺便说一句:对于 innermost 范围,我的意思是嵌套最深的范围。对我来说,应用程序容器是最外层范围。
    • 我对最内层/最外层的误解,对不起。无论哪种方式,您都无法真正沿任一方向走堆栈,因此如果您必须进行服务定位,那么您将被 AutofacDependencyResolver 卡住。请注意,如果您使用 MVC IDependencyResolver,那么 DependencyResolver.Current.GetService 将始终超出请求生命周期,因此您可能不需要那个 lambda...哦,除非您正在动态注册。所以,是的,你仍然需要它。
    • 一般来说,我会尝试颠倒这些东西。如果我有一些带有缓存对象的应用程序级别的东西,我会将这些对象作为参数放入我的插件中,而不是反之亦然。有意设计以避免服务位置,如果你愿意的话。有时这是不可避免的,但我不禁认为这里有一种“代码异味”,您可以通过一些重构来解决。
    • 可能我的例子不是很好。我试图实现的一切都发生在初始化设置容器的过程中。直到运行时我才知道要注册的组件的类型,所以我无法静态解析它们。相反,我动态注册它们并直接解析它们,以便在没有显式服务位置的情况下很好地解决 它们的 依赖关系。我会在周日回来更好地描述我正在尝试做的事情。到目前为止已经感谢您的时间。可能它会归结为我的 Func 方法,否则我似乎无法获得最里面的范围。
    猜你喜欢
    • 1970-01-01
    • 2021-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多