Search code examples
c#dependency-injectionautofac

Autofac 5 abort register based on condition


For an application I have multiple IAuthentificationProvider:

public interface IAuthentificationProvider
{
   bool IsUserValid(string login, string password)
}

For each IAuthentificationProvider I have a IAuthentificationProviderConfig associated with:

public interface IAuthentificationProvider
{
   bool IsEnabled { get; set; }
}

To use all these enabled providers I have an other class:

public class AuthentificationManager
{
   public AuthentificationManager(IEnumerable<IAuthentificationProvider> providers)
   {
      //do something with providers
   }

   //other class logic
}

Now I want to register only enabled providers. I tried something like that:

//finding all possible providers in assembly
var providerInterface = typeof(IAuthentificationProvider);
var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(p => providerInterface.IsAssignableFrom(p) && !p.IsAbstract).ToList();

foreach (var type in types)
{
    //Register type as self for simple resolve in lambda expression
    containerBuilder.RegisterType(type).AsSelf().SingleInstance();
    containerBuilder.Register(c =>
    {
        var configs = c.Resolve<IAuthentificationProvidersConfig>();
        //get specific configuration for provider
        var config = configs.GetConfig(type.Name);
        if (config.IsEnabled)
        {
           //return provider
           return c.Resolve(type);
        }
        else
        {
           //"cancel the register"
           return new object();
        }

     }).As<IAuthentificationProvider>().SingleInstance();
}

In Autofac 4.9 the above code works but now in Autofac 5.1.2 the line return new object(); doesn't work anymore:

InvalidCastException: Object cannot be stored in an array of this type.

I there an other way to cancel a register inside the lambda?


Solution

  • You can't cancel a registration inside the lambda of that same registration. As I see it, you have two options:

    1: Split reading the config and configuration the container

    You should prefer splitting the phase in application startup where you read the configuration from the phase where you register the container. This allows you to make the registration conditionally based on the configuration value.

    For instance:

    var configs = new AuthentificationProvidersConfig();
    
    builder.RegisterInstance(configs).As<IAuthentificationProvidersConfig>();
    
    foreach (var type in types)
    {
        if (configs.GetConfig(type.Name).IsEnabled)
        {
            builder.Register(type).As<IAuthentificationProvider>().AsSingleInstance();
        }
    }
    

    2. Create a composite that allows filtering providers at runtime

    In case the first solution is not viable, you can filter these types at runtime. There are multiple ways to do this, such as hiding IEnumerable<IAuthentificationProvider> behind a composite, or filtering the types inside the AuthentificationManager, or perhaps just simply registering the IEnumerable<IAuthentificationProvider> directly using a lambda, so that you can construct a filtered list:

    foreach (var type in types)
    {
        //Register type as self for simple resolve in lambda expression
        containerBuilder.RegisterType(type)
            .AsSelf()
            .SingleInstance();
    }
    
    containerBuilder.Register(c => (
        from type in types
        let configs = c.Resolve<IAuthentificationProvidersConfig>()
        let config = configs.GetConfig(type.Name)
        where config.IsEnabled
        select (IAuthentificationProvider)c.Resolve(type))
        .ToList()
        .AsReadOnly())
        .As<IEnumerable<IAuthentificationProvider>>()
        .SingleInstance();