Registering Multiple Implementation With Same Interface
We have an interface with name IVehicle
public interface IVehicle
{
string Type { get; set; }
}
We have 3 Implementations of this interface Car
,Van
and Truck
.
public class Car : IVehicle
{
public string Type { get; set; } = "Car";
}
public class Truck : IVehicle
{
public string Type { get; set; } = "Truck";
}
public class Van : IVehicle
{
public string Type { get; set; } = "Van";
}
We have a CarController
, where we want to use the Car
implementation of IVehicle
.
[Route("/api/cars")]
[ApiController]
public class CarController : ControllerBase
{
private IVehicle _vehicle;
public CarController(IVehicle vehicle)
{
_vehicle = vehicle;
}
}
It is a usual approach to inject any service. But, in our case IVehcle
have 3 implementations.However, determining which implementation is in use is not straightforward with our current method. To address this, we need to adopt a different approach.
1. Using IServiceProvider
Add these lines to the Program.cs
builder.Services.AddTransient<Car>();
builder.Services.AddTransient<Truck>();
builder.Services.AddTransient<Van>();
Update the controller.
public class CarController : ControllerBase
{
private IVehicle _vehicle;
public CarController(IServiceProvider serviceProvider)
{
_vehicle = serviceProvider.GetRequiredService<Car>();
}
}
Pros
- Simple to Implement: Easy to understand and straightforward to implement.
- Direct Resolution: Directly resolves the required service.
Cons
- Tight Coupling: The controller is tightly coupled to the
IServiceProvider
, which can make testing more difficult. - Violation of Dependency Injection Principles: It bypasses constructor injection and violates the principle of explicit dependencies.
- Less Flexible: Not easily extensible if you need to resolve other implementations based on different conditions.
2. Using Factory
Let's modify the Program.cs
file.
builder.Services.AddTransient<Car>();
builder.Services.AddTransient<Truck>();
builder.Services.AddTransient<Van>();
builder.Services.AddTransient<Func<string, IVehicle>>(serviceProvider => key =>
{
switch (key)
{
case "Car":
return serviceProvider.GetService<Car>();
case "Van":
return serviceProvider.GetService<Van>();
case "Truck":
return serviceProvider.GetService<Truck>();
default:
throw new KeyNotFoundException();
}
});
Let's break it down.
- First we are registering our
Car
,Truck
andVan
services. builder.Services.AddTransient<Func<string, IVehicle>>
registers a factory function that takes astring (key)
and returns anIVehicle
instance. The factory itself is registered as atransient
service, so a new factory function is created each time it is requested.serviceProvider => key => { ... }
defines the factory function. This function takes a string key and uses a switch statement to determine which concrete implementation of IVehicle to return. Eg. If we passCar
as a key, this functions will returns the instance ofCar
.
Now, let's see how we can use this factory in our controller.
public class CarController : ControllerBase
{
private IVehicle _vehicle;
public CarController(Func<string, IVehicle> vehicleFactory)
{
_vehicle = vehicleFactory("Car");
}
}
You can avoid the switch statement. For that you need to modify your code.
builder.Services.AddTransient<Func<string, IVehicle>>(serviceProvider => key =>
{
if (vehicleTypeMap.TryGetValue(key, out var type))
{
return serviceProvider.GetService(type) as IVehicle;
}
throw new KeyNotFoundException($"No service registered for key: {key}");
});
.NET 8.0 Keyed services
This feature is introduced with .net 8.0. It is much simpler approach. Register your services with AddKeyedTransient
(or AddKeyedScoped or AddKeyedSingleton)
builder.Services.AddKeyedTransient<IVehicle, Car>(nameof(Car));
builder.Services.AddKeyedTransient<IVehicle, Truck>(nameof(Truck));
builder.Services.AddKeyedTransient<IVehicle, Van>(nameof(Van));
In the controller,
[Route("/api/cars")]
[ApiController]
public class CarController : ControllerBase
{
private IVehicle _vehicle;
public CarController([FromKeyedServices(nameof(Van))] IVehicle vehicle)
{
_vehicle = vehicle;
}
}
Comments
Post a Comment