New Feature - Keyed Service in .NET 8

Valentyn Osidach 📚
.Net Programming
Published in
3 min readFeb 8, 2024

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!

--

--

Valentyn Osidach 📚
.Net Programming

I am a software developer from Ukraine with more than 7 years of experience. I specialize in C# and .NET and love sharing my development knowledge. 👨🏻‍💻