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.
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.
Related
We all know you can register multiple instances and resolve them all with Autofac
builder.RegisterType<Foo1>().As<IFoo>();
builder.RegisterInstance(new Foo2("something")).As<IFoo>();
builder.RegisterInstance(new Foo2("another")).As<IFoo>();
//and finally
var foos = ctx.Resolve<IEnumerable<IFoo>>();
Now I had to register some foos as follow:
builder.Register(ctx =>{
var factory = ctx.Resolve<IFooFactory>();
var allTheFoos = config.FooConfigs.Select(fooConfig => factory.CreateFoo(fooConfig));
return allTheFoos;
}).As<IEnumerable<IFoo>>();
This still works, but I think it is not the correct way.
In addition to this, I still want to add an additional registration of IFoo
builder.RegisterInstance(new Foo2("this one")).As<IFoo>();
If I now resolve the IEnumerable, I get the collection explicitly registered as IEnumerable, but I need it to also contain the Foo2("this one");
//contains allTheFoos, but not Foo2("this one");
var foos = ctx.Resolve<IEnumerable<IFoo>>();
The registrations itself can not be merged since they are in different modules for a reason. I think I need to register the foos created with the factory individually, but this need to be inside the Register method (or equivalent) since I needs to be able to first resolve the factory itself. I am hoping on something like:
builder.Register(ctx => /* generate foos*/).AsMultiple<IFoo>();
EDIT: Based upon the answer
public class BotRegistrationSource : IRegistrationSource
{
private readonly IConfiguration _config;
public BotRegistrationSource(IConfiguration config)
{
_config = config;
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service,
Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (!(swt?.ServiceType.IsAssignableTo<IBot>() ?? false))
{
return Enumerable.Empty<IComponentRegistration>();
}
return _config.GetSection("Bots")
.GetChildren()
.Select(c =>
new ComponentRegistration(
Guid.NewGuid(),
new DelegateActivator(swt.ServiceType, (componentContext, _) =>
{
var botFactory = componentContext.Resolve<IBotFactory>();
var config = botFactory.CreateConfig();
c.Bind(config);
var bot = botFactory.Create(config);
bot.Enable();
return bot;
}),
new CurrentScopeLifetime(),
InstanceSharing.None,
InstanceOwnership.OwnedByLifetimeScope,
new[] { service },
new Dictionary<string, object>()));
}
public bool IsAdapterForIndividualComponents => false;
}
I think what you're going to be looking at is a registration source. Registration sources are a way to do exactly what you want - provide one or more components to the container when a service is requested. The documentation has an example.
Has anyone tried to use the new View Injection from ASP.NET Core?
I'm trying to use straight forward as described on the documentation (https://docs.asp.net/en/latest/mvc/views/dependency-injection.html) but no success at all.
The unique diference from my implementation and the documentation is that I'm using AutoFac for DI.
When I try to use the injection on my view I get an exception that my Service has not been registered.
#inject Domain.Service.LevelService LevelService
Error Message:
ComponentNotRegisteredException: The requested service 'Domain.Service.LevelService' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Btw, the service is correctly registered and can be accessed from the controller for example.
Edit to include Startup:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
return new AutofacServiceProvider(DependencyInjection.RegisterServices(services));
}
Code of the method RegisterServices:
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(ServiceInjectionModule)))
.ToList()
.ForEach(x =>
{
var t = (ServiceInjectionModule)Activator.CreateInstance(x, new object[] { true });
t.AddtoContainer(builder);
});
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
The problem is in the assembly scanning section you've written. It's much easier to use the built in functionality of AutoFac. Not sure your code is .Net Core just based on the fact you're not using GetTypeInfo. GetTypeInfo is backwards compatible so will work with .Net 4.x
public static IContainer RegisterServices(IServiceCollection services)
{
// Create the container builder.
var builder = new ContainerBuilder();
var assembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.GetTypeInfo().IsSubclassOf(typeof(ServiceInjectionModule)))
.AsSelf();
// Add automapper configurations
var mapperConfiguration = AutoMapperConfig.Configure();
var mapper = mapperConfiguration.CreateMapper();
builder.RegisterInstance(mapper).As<IMapper>();
// Populate default services
builder.Populate(services);
return builder.Build();
}
OK, I solved the problem.
Well, I didn't paid attention and seems that no one too :p.
The problem is that I'm trying to inject an instance and not an interface. Just changed the implementation and everything started working.
Final code:
#inject Domain.Service.Interfaces.ILevelService LevelService
How is it possible to use Autofac's Modules combined with the Multitenant package?
This is my bootstrapping part for Autofac:
var builder = new ContainerBuilder();
// only very basic common registrations here...
// everything else is in modules
// Register the Autofac module for xml configuration as last
// module to allow (emergency) overrides.
builder.RegisterModule(new ConfigurationSettingsReader());
// create container for IoC
this.container = builder.Build();
// check for tenant strategy -> if exists, go multi-tenant
if (this.container.IsRegistered<ITenantIdentificationStrategy>())
{
var tenantIdentificationStrategy = this.container
.Resolve<ITenantIdentificationStrategy>();
this.container = new MultitenantContainer(tenantIdentificationStrategy,
this.container);
// how to use xml modules instead of manually register at this point?
}
else
{ ... }
I don't want to call something like
container.ConfigureTenant('1', b => b.RegisterType<Tenant1Dependency>()
.As<IDependency>()
.InstancePerDependency());
within the bootstrapper (which don't need to know something about this).
Instead I want to do something like this:
public class TenantModule : Module
{
protected override void Load(ContainerBuilder builder)
{
container.ConfigureTenant('1', b => b.RegisterType<Tenant1Dependency>()
.As<IDependency>()
.InstancePerDependency());
container.ConfigureTenant('2', b => b.RegisterType<Tenant2Dependency>()
.As<IDependency>()
.InstancePerDependency());
...
My first idea was to check when registering something for a special tenant:
public class TenantModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register((c, p) => {
var s = c.Resolve<ITenantIdentificationStrategy>();
if (s != null)
{
var id = s.IdentifyTenant<string>();
...
}
});
I could check the id and register only if id fits, but what to return if id is not matching? null? DoNothingObject? Looks strange/horrible in code to me and something like
builder.Register("1", (context, parameters) => ...
feels more natural. But I don't know how to achieve this out of the box with Autofac. Did I miss something? How did you solved that problem?
Missed the point that ConfigureTenant also passes the containerBuilder which allows the registration of a tenant specific ConfigurationSectionReader. So an option is to have an additional customer/tenant specific ConfigurationSection:
mtc.ConfigureTenant("mytenant1",
containerBuilder =>
{
containerBuilder.RegisterModule(new ConfigurationSettingsReader("mytenant1"));
}
);
mtc.ConfigureTenant("mytenant2",
containerBuilder =>
{
containerBuilder.RegisterModule(new ConfigurationSettingsReader("mytenant2"));
}
);
This also leads me to the point that I can resolve the tenant identification strategy first, get the identifier and make only the registrations for the current tenant (like a customer specific bootstrap):
var tenantIdentificationStrategy = container.Resolve<ITenantIdentificationStrategy>();
var tid = tenantIdentificationStrategy.IdentifyTenant<string>();
mtc.ConfigureTenant(tid ,
containerBuilder =>
{
containerBuilder.RegisterModule(new ConfigurationSettingsReader(tid));
// or something like that which identifies the tenant config section
}
);
Or I could put this into a simple foreach to make registrations for all available tenants (but this sounds curious in most scenarios here because only one tenant will be resolved at time at its a bootstrapping part, maybe for other special cases worth to mention).
Any better ideas? I'll vote up for better ideas... ;-)
I use the following code in order to register log4net for all the classes that need it.
public class LogInjectionModule : Module
{
private readonly string _configPath;
public LogInjectionModule(string configPath)
{
_configPath = configPath;
}
protected override void AttachToComponentRegistration(IComponentRegistry registry,
IComponentRegistration registration)
{
XmlConfigurator.Configure(new FileInfo(_configPath));
registration.Preparing += OnComponentPreparing;
}
private static void OnComponentPreparing(object sender, PreparingEventArgs e)
{
var t = e.Component.Activator.LimitType;
e.Parameters = e.Parameters.Union(new[]
{
new ResolvedParameter((p, i) => p.ParameterType == typeof (ILog),
(p, i) => LogManager.GetLogger(t))
});
}
}
All the classes are registered using autofac's types scanning:
builder.RegisterAssemblyTypes(typeof (IResourceFinder).Assembly)
.AsImplementedInterfaces();
And it works fine!
One class needs to be registered explicitly tries to resolve ILog and fails
builder.Register(x => new ClassThatNeedsILog(x.Resolve<ILog>())).AsImplementedInterfaces();
Here is that class
public class ClassThatNeedsILog
{
public ClassThatNeedsILog(ILog log)
{
}
}
I am getting the following exception:
Autofac.Core.Registration.ComponentNotRegisteredException : The
requested service 'log4net.ILog' has not been registered. To avoid
this exception, either register a component to provide the service,
check for service registration using IsRegistered(), or use the
ResolveOptional() method to resolve an optional dependency.
Your LogInjectionModule never registers any ILog into the container only supplies paramters for the resolved instances on the preparing face, and it only works for instances created by Autofac.
So when you write builder.Register(x => new ClassThatNeedsILog(x.Resolve<ILog>())) you are creating the ClassThatNeedsILog manually. with new ClassThatNeedsILog(...)
Hence Autofac does not know about your instance creation (so your OnComponentPreparing won't run) and because you haven't really registered any ILog implementation you get the ComponentNotRegisteredException.
You have two options:
register an ILog in the container directly
let Autofac create your ClassThatNeedsILog type.
So you can just register an ILog in the container with:
builder.RegisterInstance(LogManager.GetLogger("Logger")).As<ILog>();
Then your code will work fine.
Or if you anyway creating the ClassThatNeedsILog by hand can just supply directly the ILog there:
builder.Register(x => new
ClassThatNeedsILog(LogManager.GetLogger(typeof(ClassThatNeedsILog))))
.AsImplementedInterfaces();
The other options is to let Autofac create the instances for you, so change your registration to:
builder.RegisterType<ClassThatNeedsILog>()
.AsImplementedInterfaces();
In this case Autofac will handle the instance creation for you and it will call the OnComponentPreparing method of your module.
If you want to supply additional constructor parameters you can use WithParameter and WithParameters methods:
builder.RegisterType<ClassThatNeedsILog>()
.AsImplementedInterfaces()
.WithParameters(new Parameter[] {
ResolvedParameter.ForNamed<IAnotherInterface>("NAME"),
ResolvedParameter.ForNamed<IYetAnotherInterface>("ANOTHERNAME")});
The builder needs to Build a container before it can Resolve.
Try something like this (untested)
builder
.RegisterAssemblyTypes(typeof (IResourceFinder).Assembly)
.AsImplementedInterfaces();
/* process LogInjectionModule */
IContainer container = builder.Build();
var updateBuilder = new ContainerBuilder();
updateBuilder
.Register(x => new ClassThatNeedsILog(x.Resolve<ILog>()))
.AsImplementedInterfaces();
updateBuilder.Update(container);
nemesv's answer resolved the issue for me.
Though, I had to spend some more time getting it to work after using the RegisterType approach, as I was missing the below:
config.Filters.AddRange(config.DependencyResolver.GetServices(typeof(IExceptionFilter)).Select(o => o as IExceptionFilter));
Just adding mentioning it here, if other's have forgotten to configure the filter in the first place.
I have an application which might needs to connect to multiple databases. But each module will only connect to one db. So I though it might make sense to isolate the db into each module so each module will get its own db auto resolved and I don't need to bother with named registration. But to my astonishment, it seems that Autofac's module is more a code module than a logical module (am I wrong here?): IA
[Test]
public void test_module_can_act_as_scope_container()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new Module1());
IContainer c = builder.Build();
var o = c.ResolveNamed<CB>("One");
Assert.That(o.A.Name, Is.EqualTo("One"));
builder = new ContainerBuilder();
builder.RegisterModule(new Module1());
builder.RegisterModule(new Module2());
c = builder.Build();
var t = c.ResolveNamed<CB>("One");
Assert.That(t.A.Name, Is.EqualTo("Two"));
}
And the interfaces/Modules used:
public interface IA
{
string Name { get; set; }
}
public class CA : IA
{
public string Name { get; set; }
}
public class CB
{
public CB(IA a)
{
A = a;
}
public IA A { get; private set; }
}
public class Module1 : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new CA() { Name = "One" }).As<IA>();
builder.RegisterType<CB>().Named("One", typeof(CB));
}
}
public class Module2 : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new CA() { Name = "Two" }).As<IA>();
builder.RegisterType<CB>().Named("Two", typeof(CB));
}
}
Yes, you're kind of correct.
Modules serve only for splitting configuration into somewhat independent parts. They do not scope configuration in any way. Having modules is actually the same as if you merged all modules' Load methods' code into a single configuration method and then built the container.
In your case your Module2 actually overrides the registration for IA interface from Module1.
I've also been interested in finding a solution to the problem. I've come to the following approach:
Keyed service
var key = new object();
builder.Register(c => new CA() { Name = "Two" }).Keyed<IA>(key);
builder.RegisterType<CB>().Named("Two", typeof(CB))
.WithParameter(new ResolvedParameter(
(pi, ctx) => pi.Type == typeof(IA),
(pi, ctx) => ctx.ResolveKeyed<IA>(key)
));
Pros:
You can control which IA instances will be injected in each module.
Contras:
It's quite a lot of code
It does not make IA component 'internal' to the module - other modules can still resolve it using simple Resolve<IA>. Modules aren't isolated.
Hope this helps
UPDATE
In some cases it may be easier, and frankly more correct from design point of view, to make it this way:
Delegate registration
builder.Register(ctx => new CB(new CA { Name = "Two" }))
.Named("Two", typeof(CB));
Pros:
You don't expose your module-specific CA to other modules
Contras:
If CA and CB have complex dependencies and a lot of constructor parameters, you'll end up with mess of constructing code
If you need to use CA in several places inside a module, you'll have to find a way to avoid copy-pasting
Nested Container instances
And yet another option is having an independent Container inside each module. This way all modules will be able to have their private container configurations. However, AFAIK, Autofac doesn't provide any built-in means to somehow link several Container instances. Although I suppose implementing this should not be very difficult.
You could use nestet lifetime scopes. These form a hierarchy in which subscopes can resolve services registered in superscopes. Additionally, you can register unique services in each subscope, like this:
var cb = new ContainerBuilder();
cb.RegisterModule<CommonModule>();
var master = cb.Build();
var subscope1 = master.BeginLifetimeScope(cb2 => cb2.RegisterModule<Module1>());
var subscope2 = master.BeginLifetimeScope(cb2 => cb2.RegisterModule<Module2>());
With this setup, services in Module1 will only be available to instances resolved from subscope1.