Introduction
In.NET 8, the keyed services are supported in the built-in Dependencies Injection (DI); previously, they weren’t supported directly in the DI. If we are using this type of service, then we are dependent on other third-party packages like Autofac, StructureMap, etc.
What are the Keyed Services?
The keyed services are basically provided to the same interface, and multiple implementations are there.
At that moment, when we register with the DI, we are setting ourselves apart with the special key identifier.
When you need to retrieve a particular implementation based on a key at runtime, this capability comes in handy.
Here is an example of registering in the DI.
In the above example, I have used the unique key identifier associated with the hard-coded value. Here in resolving time, if we are making it a typo for the hard-coded value, then it will give an exception to the runtime, so to avoid this type of mistake, we need to create constants and use them in the register time and resolving time as well.
Here is an example of registering with a constant value in the DI.
Which Keyed Services are available?
The following techniques can be used to add essential services to the service provider:
- builder.Services.AddKeyedSingleton<T>()
- builder.Services.AddKeyedScoped<T>()
- builder.Services.AddKeyedTransient<T>()
Wherever we are using any key services, we need to associate the unique key identifier. If we associate any key identifier, then it’s given to the compilation error.
The keyed services use the same lifespan as the AddSingleton, AddScoped, and AddTransient that we are accustomed to seeing in.NET 6.
In essence, we are resolving dependencies at run time—the point at which the process has been running—in the keyed services.
Let’s see with an example for better understanding.
Here, I have created an IShapeService interface and its definition for implementation in the other class.
public interface IShapeService
{
string Shape();
}
Here is the class implementation for the IShapeService interface.
public class SquareService : IShapeService
{
public string Shape()
{
return "This is the Square shape service.";
}
}
public class CircleService : IShapeService
{
public string Shape()
{
return "This is the circle shape service.";
}
}
In the above example, I have created a couple of classes, derived the same interface, and implemented the Shape method.
Let’s see how to use it on our application side. So here, I have created an API controller for the demo purpose for better understanding.
I have created a shape controller, and I've created a couple of endpoints for the given data for square and circle.
[Route("api/[controller]")]
[ApiController]
public class ShapeController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;
public ShapeController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpGet("square")]
public IActionResult GetSquare()
{
var squareService = _serviceProvider.GetRequiredKeyedService<IShapeService>(Constants.Square);
return Ok(squareService.Shape());
}
[HttpGet("circle")]
public IActionResult GetCircle()
{
var squareService = _serviceProvider.GetRequiredKeyedService<IShapeService>(Constants.Circle);
return Ok(squareService.Shape());
}
}
In the above example, we are trying to resolve the dependencies of the endpoints themselves.
Here, we are trying to resolve the dependencies on the constructor side and use them in an API endpoint.
[Route("api/[controller]")]
[ApiController]
public class ShapeController : ControllerBase
{
private readonly IShapeService _circleShapeService;
private readonly IShapeService _squareShapeService;
public ShapeController([FromKeyedServices(Constants.Circle)] IShapeService circleShapeService,
[FromKeyedServices(Constants.Square)] IShapeService squareShapeService)
{
_circleShapeService = circleShapeService;
_squareShapeService = squareShapeService;
}
[HttpGet("square")]
public IActionResult GetSquare()
{
return Ok(_squareShapeService.Shape());
}
[HttpGet("circle")]
public IActionResult GetCircle()
{
return Ok(_circleShapeService.Shape());
}
}
Let’s demonstrate by running the application and calling both endpoints and seeing what the results are.
Here, is both endpoints are there in the swagger documents.
Let’s try to execute one-by-one endpoints.
Should we need to add more shapes, for example, all we have to do is implement and register the new shape class in the DI; the application will then make use of the new shape class.
We learned the new technique and evolved together.
Happy coding. :)