17

Is it possible to register a component conditionally on an other component's state? Something like:

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

I've found that prior to V2 of autofac one could use a "Register().OnlyIf()" construction that seemed like the one I'm looking for. I would like such feature to conditionally override a default registration.

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

The foo will be according to ISettings.ReallyUseSpecificFoo either instance of DefaultFoo or instance of SpecificFoo.

Thank you.

Steven
  • 166,672
  • 24
  • 332
  • 435
Jan Červák
  • 305
  • 2
  • 7

1 Answers1

25

There is no way to perform conditional registration at the container level based on container contents. The trouble is that you would need to resolve something in the container in order to determine what gets registered in the container, which then could technically affect whether you wanted to register the thing in the first place. Chicken/egg circular dependency problem.

You can, however, register things conditionally into nested lifetime scopes. Most integration points (like ASP.NET) resolve out of nested lifetime scopes (like an HTTP-request-length lifetime scope). You can register things on the fly into nested lifetime scopes and that might solve your problem.

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

Another option you have is to make the registration a lambda. It might make the registration itself more complex but it's an option you could consider.

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

If manual construction there isn't appealing, you could pass it through Autofac, too.

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

Yet another option is to update an existing container after being built. In this case, you avoid the chicken/egg scenario by actually building the container, using it, and changing registrations after the fact.

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

Finally, you might consider XML configuration. Given the registration is dependent on some sort of configuration setting, you might consider using Autofac's XML configuration support. That way, instead of trying to resolve something out of an un-built container to conditionally register something else, you could just specify the right thing to register using the XML configuration and register the correct thing the first time.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • I can see the chicken/egg in general but this scenario is not that case. For true circular dependency I suppose there might suffice some run-time exception at resolve time. But if this is an intended functionality thank you for letting me know. – Jan Červák Sep 25 '13 at 08:13
  • The lambda registration is unfortunately not suitable: `CommonRegistrations` and `SpecificRegistrations` belong to different assemblies. With the suggested lambda I would have to specify `DefaultFoo` in every "extending assembly" registrations - I would like to evade this. – Jan Červák Sep 25 '13 at 08:17
  • @JanČervák I thought of one more approach and added it to my answer. It's a lot like the lifetime scope update, except you're updating the container. If none of these options will work for you... then you probably can't do what you need with Autofac. There's really no way to resolve something from a container without the container being built. – Travis Illig Sep 25 '13 at 14:14
  • @JanČervák I thought of one more option - XML configuration. – Travis Illig Sep 25 '13 at 14:18