Keyed services are a new addition to .NET 8 and a long-requested feature that has finally arrived. This new feature allows us to register multiple implementations of the same type by specific keys. We can then use those keys to tell our application which implementation to use explicitly. Keyed services come in handy when we have an interface with different implementations that we want to use in our application, especially when we need to use several of those implementations in different places in our application at the same time. So, in our application, we have only registered the PlayerGenerator
service, but let’s add the BetterPlayerGenerator
service registration as well:
builder.Services.AddScoped<IPlayerGenerator, PlayerGenerator>(); builder.Services.AddScoped<IPlayerGenerator, BetterPlayerGenerator>();
Now, if we run the app, and send the same request, we will get this result:
{ "name": "Better - Jon Irenicus", "gender": 0, "age": 25, "hairColor": 2, "strength": 11, "race": "Better - Human" }
Even though we have both services registered, and the interface injected into our controller:
private readonly IPlayerGenerator _playerGenerator; public GamesController(IPlayerGenerator playerGenerator) { _playerGenerator = playerGenerator; }
We get the Better Player’s data. This is the case because this service is registered the last. With the implementation as is, we can’t switch between services – well, there are some advanced techniques to do that, but let’s see how keyed services help us with this. Let’s modify the Program
class implementation:
builder.Services.AddKeyedScoped<IPlayerGenerator, PlayerGenerator>("player"); builder.Services.AddKeyedScoped<IPlayerGenerator, BetterPlayerGenerator>("betterPlayer");
As you see, now, we are using the AddKeyedScoped
method instead of the AddScoped
method, and we also provide the “serviceKey” argument for each registration. Yes, this means, we also have the AddKeyedSingleton
and the AddKeyedTransient
methods to register keyed services with different lifetimes. Now, we have to modify the controller logic:
private readonly IPlayerGenerator _playerGenerator; public GamesController([FromKeyedServices("player")]IPlayerGenerator playerGenerator) { _playerGenerator = playerGenerator; }
We are doing the same constructor injection, just this time, we are using the [FromKeyedServices]
attribute and provide the “serviceKey” value. This is the value we used to register the specific service we want to resolve here. Now, we can run the app, and verify our result:
{ "name": "Dynaheir", "gender": 1, "age": 21, "hairColor": 5, "strength": 14, "race": "Human" }
We get the player’s JSON data as a result. Of course, if we simply replace the key in the constructor:
public GamesController([FromKeyedServices("betterPlayer")]IPlayerGenerator playerGenerator) { _playerGenerator = playerGenerator; }
We will get a better player’s JSON data as a result. Excellent. With all this knowledge about dependency injection in .NET, we can continue with our main application and introduce the logger service.