New Feature - Keyed Service in .NET 8
Keyed Services. It allows us to register multiple implementations of the same interface under different names and select the appropriate implementation at runtime based on the name.
learn.microsoft
The official documentation gives a sample code for using the keying service:
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
The code above uses the [FromKeyedServices] attribute to access registered services by name.
Some thoughts
It does not make sense to inject a generic interface when the implementation is already clear during the design phase.
In this case, define two specific interfaces inherited from ICache, IBigCache, and ISmallCache, and use these inherited interfaces in the code.
This makes the program's intent clearer, and we can still use ICache where it is generic.
builder.Services.AddSingleton<ICache, BigCache>();
builder.Services.AddSingleton<IBigCache, BigCache>();
builder.Services.AddSingleton<ISmallCache, SmallCache>();
app.MapGet("/common", (ICache cache) => cache.Get("date"));
app.MapGet("/big", (IBigCache bigCache) => bigCache.Get("date"));
app.MapGet("/small", (ISmallCache smallCache) => smallCache.Get("date"));
So, are keying services useless?
No, no, no.
The advantage of keyed services is that it allows us to dynamically select the right service at runtime, rather than having it fixed at compile time.
This way, we can use different service implementations depending on different scenarios or conditions.
Strategy pattern
The Strategy Pattern is a behavioral design pattern that defines a series of algorithms and encapsulates them into individual classes. Then, according to different situations, choose the appropriate algorithm to execute.
Suppose we are creating an e-commerce website and need to add the functionality of sending order updates to customers. The specific method of sending these updates would be determined by the customer, and this information would be stored in the SendType field (which could be either email or SMS) of the user table.
If we don’t use a keying service, we might write code like this:
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddTransient<ISmsSender, SmsSender>();
if(user.SendType == "Email")
{
emailSender.Send(user);
}
else if(user.SendType == "Sms")
{
smsSender.Send(user);
}
There are issues with the previous code. If new sending methods like WhatsApp are added, it will require modification in both the dependency injection and the usage code. Additionally, the code is not flexible enough.
If we use a keying service, we can use the following code to achieve the same functionality:
builder.Services.AddKeyedTransient<ISender, EmailSender>("Email");
builder.Services.AddKeyedTransient<ISender, SmsSender>("Sms");
public class SendController(IServiceProvider serviceProvider)
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
[HttpGet]
public async Task Send(int userId)
{
var user = GetUser(userId);
var service = _serviceProvider.GetRequiredKeyedService<ISender>(user.SendType);
service.Send(user);
}
}
The code above uses user.SendType as the key. This allows us to select the appropriate send method at runtime based on the value stored in the user table.
When adding a sending method, such as WhatsApp, we simply register another keying service:
builder.Services.AddKeyedTransient<ISender, WhatsAppSender>("WhatsApp");
Summarizing
With .NET 8's keyed services, we can now register multiple implementation classes for a single interface. At runtime, we can then select the appropriate service by name.
Keyed services can be utilized for implementing the strategy pattern.
Share your thoughts or alternative scenarios for using keyed services in the comments section below!