Autofac: любой способ разрешить самую внутреннюю область? - PullRequest
4 голосов
/ 24 февраля 2012

В настоящее время я испытываю Autofac в новом проекте ASP.NET MVC после использования Ninject, Castle Windsor и других контейнеров IoC в последние годы. Поэтому, хотя я и знаю о контейнерах 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?

1 Ответ

4 голосов
/ 25 февраля 2012

В 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 и такие внутри модульные тесты без реального контекста запроса.Это также означает, что вы должны иметь возможность использовать область действия запроса в методе регистрации / разрешения и не должны использовать контейнер приложения.

...