Autofac: any way to resolve the innermost scope?

不想你离开。 提交于 2019-11-29 07:56:47

In Autofac, the innermost scope is always the container. Using the AutofacDependencyResolver, it'd be AutofacDependencyResolver.Current.ApplicationContainer

There is no way from a nested scope (if all you have is an ILifetimeScope) to "walk backward" to get to the container. I'm not necessarily sure you want to do that, anyway.

It sounds like your SingleInstance component is doing some sort of service location, basically, with manual registration/resolution of certain components. If the set of types being registered is fixed, I might recommend (if possible) some redesign of your system, so the SingleInstance component isn't registered as SingleInstance anymore and instead gets registered as InstancePerDependency, then have that take these other items in as constructor parameters.

Instead of...

// 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();

You might try inverting it a bit:

// 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();

The idea is to not have to do the on-the-fly registration-and-immediate-resolution thing.

If you're stuck doing service location, you'll need to use AutofacDependencyResolver.Current.ApplicationContainer if you need the absolute innermost scope, but keep in mind any objects you register scoped to InstancePerHttpRequest will not be resolvable if you do that, so you could get into trouble. It really is recommended to use the AutofacDependencyResolver.Current.RequestLifetimeScope instead. That would make your method:

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

In a testing environment, the AutofacDependencyResolver lets you swap in the provider that dictates how request lifetimes get generated. You can implement a simple/stub one like this:

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();
    }
}

Again, just a stub for unit testing, not something you'd ever use in production.

Then when you wire up the DependencyResolver in your test, you provide your lifetime scope provider:

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

This lets you use InstancePerHttpRequest and such inside unit tests without actually having a real request context. It also means you should be able to use the request lifetime scope in your registration/resolution method and not have to fall back on the application container.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!