Deploying multiple implementations using dependency injection

I am currently working on a major ASP.NET project and want to use the built-in Injection (DI) functionality.

Well, I started with the interface:

ICar { string Drive(); } 

and want to implement the ICar interface several times, for example

 public class BMW : ICar { public string Drive(){...}; } public class Jaguar : ICar { public string Drive(){...}; } 

and add the following to the Startup class

 public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddTransient<ICar, BMW>(); // or services.AddTransient<ICar, Jaguar>(); } 

Now I need to make a decision between the two implementations, and my selected class will be installed in every constructor that needs an ICar implementation. But my idea was to say that if the requested controller is a BMWController, then use the BMW implementation or use Jaguar if requested by the JaguarController .

Otherwise, DI makes no sense to me. How can I deal with this problem correctly?

To better understand my problem, look at this pic: https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=true How the dependency converter works and where I can install it in the ASP.NET core?

In Unity, you can do something like this container.RegisterType<IPerson, Male>("Male"); container.RegisterType<IPerson, Female>("Female");

and call the correct type like this

 [Dependency("Male")]IPerson malePerson 
+6
source share
1 answer

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; // Get the car ICar bmw = carFactory.Create("Bmw"); } } 

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; } } 
+11
source

Source: https://habr.com/ru/post/1012856/


All Articles