The function you are looking for is not so simple to implement, at least when you use it in the controller, because the controllers are handled a little (by default the controllers are not registered with the ServiceCollection
and therefore are not allowed / created by the container and instantiated instead ASP.NET Core at request time, see also explanation and example in my related answer ).
With the built-in IoC container, you can only use it with the factory method, here with an example in the BmwCarFactory
class:
services.AddScoped<ICar, BmwCar>(); services.AddScoped<BmwCar>(); services.AddScoped<BmwCarFactory>(p => new BmwCarFactory(p.GetRequiredService<BmwCar>())));
The default IoC container is deliberately simplified to provide the basics of dependency injection so you get started, and for other IoC containers, you could easily plug in a plugin and replace the standard implementation.
For more complex scenarios, users are encouraged to use IoC of their choice, which supports more complex functions (assembly, decoration, conditional / parameterized dependencies, etc.
AutoFac (which I use in my projects) supports such advanced scripts. There are 4 scenarios in the AutoFac documentation (generally with the 3rd one that @pw suggested in the comments):
1. Redesign your classes
It takes a few extra overheads to reorganize the code and class hierarchy, but greatly simplifies the consumption of injectable services.
2. Change the registration
The docs describe it here if you don't want or can't change the code.
// Attach resolved parameters to override Autofac's // lookup just on the ISender parameters. builder.RegisterType<ShippingProcessor>() .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.Resolve<PostalServiceSender>())); builder.RegisterType<CustomerNotifier>(); .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.Resolve<EmailNotifier>())); var container = builder.Build();
3. Use of key services ( here )
This is very similar to the previous approach to 2. but solves services based on the key, not their specific type
4. Use metadata
This is very similar to 3. but you define the keys through the attribute.
Other containers like Unity have special attributes like DependencyAttribute
, which you can use to annotate dependencies, like
public class BmwController : Controller { public BmwController([Dependency("Bmw")ICar car) { } }
But this and Autofac option 4 leak the IoC container into your services, and you should consider other approaches.
Alternatively, you create classes and factories that permit your services based on certain agreements. For example, ICarFactory
:
public ICarFactory { ICar Create(string carType); } public CarFactory : ICarFactory { public IServiceProvider provider; public CarFactory(IServiceProvider provider) { this.provider = provider; } public ICar Create(string carType) { if(type==null) throw new ArgumentNullException(nameof(carType)); var fullQualifedName = $"MyProject.Business.Models.Cars.{carType}Car"; Type carType = Type.GetType(fullQualifedName); if(carType==null) throw new InvalidOperationException($"'{carType}' is not a valid car type."); ICar car = provider.GetService(carType); if(car==null) throw new InvalidOperationException($"Can't resolve '{carType.Fullname}'. Make sure it registered with the IoC container."); return car; } }
Then use it like
public class BmwController : Controller { public ICarFactory carFactory; public BmwController(ICarFactory carFactory) { this.carFactory = carFactory;
Alternative IServiceProvider
// alternatively inject IEnumerable<ICar> public CarFactory : ICarFactory { public IEnumerable<ICar> cars; public CarFactory(IEnumerable<ICar> cars) { this.cars = cars; } public ICar Create(string carType) { if(type==null) throw new ArgumentNullException(nameof(carType)); var carName = ${carType}Car"; var car = cars.Where(c => c.GetType().Name == carName).SingleOrDefault(); if(car==null) throw new InvalidOperationException($"Can't resolve '{carName}.'. Make sure it registered with the IoC container."); return car; } }