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?
You can't cancel a registration inside the lambda of that same registration. As I see it, you have two options:
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();
}
}
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();