Регистрация условного компонента в autofac

Возможно ли зарегистрировать компонент условно в другом состоянии компонента? Что-то вроде:

ContainerBuilder.RegisterConditionally<T>(
  Func<IComponentContext, bool>, 
  Func<IComponentContext, T>);

Я обнаружил, что до V2 autofac можно было использовать конструкцию "Register().OnlyIf()", которая казалась той, которую я ищу. Я бы хотел, чтобы такая функция условно переопределяла регистрацию по умолчанию.

class CommonRegistrations
{
  public virtual void Register(ContainderBuilder builder)
  {
    builder.Register(ctx => LoadSettings()).As<ISettings>().SingleInstance();
    builder.RegisterType<DefaultFoo>().As<IFoo>();
  }
}

class SpecificRegistrations : CommonRegistrations
{
  public virtual void Register(ContainerBuilder builder)
  {
    base.Register(builder);
    builder.ConditionalyRegister(
      ctx => ctx.Resolve<ISettings>().ReallyUseSpecificFoo, 
      ctx => new SpecificFoo()).As<IFoo>();
  }
}

...

var builder = new ContainerBuilder();
var registrations = new SpecificRegistrations();
registrations.Register(builder);
var container = builder.Build();
IFoo foo = container.Resolve<IFoo>();

foo будет соответствовать ISettings.ReallyUseSpecificFoo любому экземпляру DefaultFoo или экземпляру SpecificFoo.

Спасибо.

Ответ 1

Невозможно выполнить условную регистрацию на уровне контейнера на основе содержимого контейнера. Проблема заключается в том, что вам нужно будет что-то разрешить в контейнере, чтобы определить, что регистрируется в контейнере, которые затем могут технически повлиять на то, хотите ли вы зарегистрировать вещь в первую очередь. Проблема зависимости курица/яйца.

Вы можете, однако, регистрировать события условно во вложенные области времени жизни. Большинство точек интеграции (например, ASP.NET) устраняют из вложенных областей времени жизни (например, продолжительность жизни по длине HTTP-запроса). Вы можете регистрировать вещи "на лету" во вложенные области жизни, и это может решить вашу проблему.

var builder = new ContainerBuilder();
builder.Register(ctx => LoadSettings()).As<ISettings>().SingleInstance();
builder.RegisterType<DefaultFoo>().As<IFoo>();
var container = builder.Build();

var settings = container.Resolve<ISettings>();
using(var scope =
  container.BeginLifetimeScope(b => {
    if(settings.ReallyUseSpecificFoo)
    {
      b.RegisterType<SpecificFoo>().As<IFoo>();
    }
  })
{
  // Resolve things from the nested lifetime scope - it will
  // use the overrides. This will get the SpecificFoo if the
  // configuration setting is true.
  var foo = scope.Resolve<IFoo>();
}

Другой вариант, который у вас есть, - сделать регистрацию лямбдой. Это может сделать регистрацию более сложной, но это вариант, который вы могли бы рассмотреть.

var builder = new ContainerBuilder();
builder.Register(ctx => {
    var settings = ctx.Resolve<ISettings>();
    if(settings.ReallyUseSpecificFoo)
    {
      return new SpecificFoo();
    }
    return new DefaultFoo();
  }).As<IFoo>();

Если ручная конструкция не привлекательна, вы можете передать ее также через Autofac.

var builder = new ContainerBuilder();
// Register the IFoo types - but NOT "As<IFoo>"
builder.RegisterType<DefaultFoo>();
builder.RegisterType<SpecificFoo>();
// In the lambda use Resolve<T> to get the instances.
builder.Register(ctx => {
    var settings = ctx.Resolve<ISettings>();
    if(settings.ReallyUseSpecificFoo)
    {
      return ctx.Resolve<SpecificFoo>();
    }
    return ctx.Resolve<DefaultFoo>();
  }).As<IFoo>();

Еще один вариант - обновить существующий контейнер после его создания. В этом случае вы избегаете сценария курица/яйцо, фактически создавая контейнер, используя его и меняя регистрацию после факта.

var builder = new ContainerBuilder();
builder.Register(ctx => LoadSettings()).As<ISettings>().SingleInstance();
builder.RegisterType<DefaultFoo>().As<IFoo>();
var container = builder.Build();

var settings = container.Resolve<ISettings>();
if(settings.ReallyUseSpecificFoo)
{
  var updater = new ContainerBuilder();
  updater.RegisterType<SpecificFoo>().As<IFoo>();
  updater.Update(container);
}

Наконец, вы можете рассмотреть конфигурацию XML. Учитывая, что регистрация зависит от какого-либо параметра конфигурации, вы можете рассмотреть возможность использования поддержки конфигурации AutoFac XML. Таким образом, вместо того, чтобы пытаться решить что-то из неконструированного контейнера, чтобы условно зарегистрировать что-то еще, вы можете просто указать правильную вещь для регистрации с использованием конфигурации XML и зарегистрировать правильную вещь в первый раз.