🔑 Understanding Keyed Services in .NET Dependency Injection
Simplify your .NET dependency injection setup with Keyed Services the elegant new feature for managing multiple implementations effortlessly
When working with modern .NET applications, Dependency Injection (DI) has become the standard way to manage dependencies and build modular, testable systems.
But what if you have multiple implementations of the same interface — like Email, SMS, and Push notifications — and you need to decide which one to use at runtime?
That’s where Keyed Services, introduced in .NET 8, come to the rescue.
🧩 The Problem Before Keyed Services
Imagine you have an interface:
public interface INotificationService
{
void Send(string message);
}And you have multiple implementations:
public class EmailNotificationService : INotificationService
{
public void Send(string message)
=> Console.WriteLine($”Email: {message}”);
}
public class SmsNotificationService : INotificationService
{
public void Send(string message)
=> Console.WriteLine($”SMS: {message}”);
}
public class PushNotificationService : INotificationService
{
public void Send(string message)
=> Console.WriteLine($”Push: {message}”);
}Before .NET 8, registering all these in DI meant you had to inject them as IEnumerable<INotificationService> and manually decide which one to use — not ideal.
🚀 Enter Keyed Services
With .NET 8, you can now register services with keys and inject them directly by key.
Here’s how it looks in code:
builder.Services.AddKeyedSingleton<INotificationService,SmsNotificationService>("sms");
builder.Services.AddKeyedSingleton<INotificationService,EmailNotificationService>("email");
builder.Services.AddKeyedSingleton<INotificationService,PushNotificationService>("push");Each service registration is now associated with a unique key — “sms”, “email”, and “push”.
💉 Injecting Keyed Services
Once registered, you can inject the specific implementation using the [FromKeyedServices] attribute:
public class NotificationHandler
{
private readonly INotificationService _emailService;
private readonly INotificationService _smsService;
private readonly INotificationService _pushService;
public NotificationHandler(
[FromKeyedServices(”email”)] INotificationService emailService,
[FromKeyedServices(”sms”)] INotificationService smsService,
[FromKeyedServices(”push”)] INotificationService pushService)
{
_emailService = emailService;
_smsService = smsService;
_pushService = pushService;
}
public void NotifyAll()
{
_emailService.Send(”📧 Sending Email Notification”);
_smsService.Send(”📱 Sending SMS Notification”);
_pushService.Send(”🔔 Sending Push Notification”);
}
}No more manual filtering or loops — just clean, declarative dependency injection.
⚙️ Manual Resolution (Optional)
If you ever need to resolve a keyed service manually, you can use:
var smsService = provider.GetKeyedService<INotificationService>(”sms”);
smsService?.Send(”Manual SMS Notification”);This is useful in factories, background jobs, or middleware where constructor injection isn’t practical.
💡 When to Use Keyed Services
✅ You have multiple strategies or implementations (like different payment, cache, or logging providers).
✅ You want to choose implementations explicitly without complex factory logic.
✅ You prefer strong typing over string-based condition checks.
⚠️ When Not to Use It
❌ If you only have one implementation — regular AddSingleton or AddTransient is simpler.
❌ If runtime selection depends on dynamic conditions — a factory pattern may still be more flexible.
❌ Overusing keys can make DI harder to trace — use thoughtfully.
🧠 Bonus: Keyed Generics
Keyed services also support generic registrations:
builder.Services.AddKeyedScoped(typeof(IRepository<>), typeof(SqlRepository<>), “sql”);
builder.Services.AddKeyedScoped(typeof(IRepository<>), typeof(MongoRepository<>), “mongo”);And you can inject them just like before:
public class DataSyncService
{
private readonly IRepository<Customer> _sqlRepo;
public DataSyncService([FromKeyedServices(”sql”)] IRepository<Customer> sqlRepo)
{
_sqlRepo = sqlRepo;
}
}🏁 Final Thoughts
Keyed Services are one of the most practical additions in .NET 8’s DI system.
They make it easier to handle multiple implementations of the same interface — without messy filtering logic or custom factories.
So the next time you’re juggling different strategies — whether it’s Email, SMS, or Push — remember:
A well-chosen key can make your dependencies cleaner and smarter.
👉 Full working code available at:
🔗https://sourcecode.kanaiyakatarmal.com/KeyedServices
I hope you found this guide helpful and informative.
Thanks for reading!
If you enjoyed this article, feel free to share it and follow me for more practical, developer-friendly content like this.


