.NET 8 is going to ship Keyed Services with
Microsoft.Extensions.DependencyInjection, the default IOC container. Keyed
services allow developers to register multiple implementations of an interface
under different names (or, more generically, “keys”). This is useful in some
scenarios. Let’s take a look.
First off, imagine you are building a web app that sends push notifications to Android and iOS devices. You’ll have to write different code to interact with Google and Apple’s push notification services. They use different authorization mechanisms and have different capabilities. Still, if you’re only going to use the features common to both, it would be convenient to have a single interface:
Both classes implement the same interface. Most of the code that sends notifications can be agnostic towards which particular platform is in play. Someone just has to get ahold of the correct implementation.
One way to grab a particular implementation is to use the
attribute. Placed on a constructor parameter, you can ask for the implementation
you need by name:
GoogleConsumer knows it needs the
"google" flavor of
IPushNotificationService and declares that fact in the constructor. The framework will
make sure it finds an implementation with that name and injects it.
This is a bit of a smell, though. The whole idea of “inversion of control” is that, well, control is inverted and comes from outside the class. So asking for a particular version of a generic interface strikes me as strange.
I’d probably not use keyed services for this situation. If my class needed the
Google flavor, I might as well be upfront about it and use a Google-specific
interface. Attributes like
FromKeyedServices are not as explicit as interfaces
and don’t show up in intellisense popup documentation.
I’d make an
IGooglePushService interface that inherits from
IPushNotificationService and inject that. That’s more explicit about what I
need, and also gives me an interface to which I can attach platform-specific
methods. Since it uses inheritance, I can still pass it on to other truly
generic code that only needs
Keyed Services aren’t always a smell: one place they can shine is in strategy pattern code, where you don’t know what implementation you need until runtime.
That might look like this:
Here we have a class that doesn’t know until runtime what service it will need. It inspects state to know what platform the user has, and then resolves the right service.
But isn’t this a service locator pattern? My senior developer sent me a blog post once called Service Locator Considered Harmful…
Well, sure, it is a service locator.
But sometimes you need code that locates services! If it makes you feel
better, wrap it up in another class and call it the
PushNotificationServiceFactory. People that hate Service Locators love
Anyway, there are other ways to skin this cat of course. You could inject both
and decide between them with a switch statement. You could inject an
IEnumerable<IPushNotificationService> and search through it.
But I think the mechanism shown above is still better: it only instantiates the implementation you’re actually going to use.
Sometimes you have a common utility service that can be configured in different ways. It’s useful to name those different flavors and resolve them by name later.
Imagine you have a utility class called
RestClient that can be configured for
different APIs by setting common properties:
RestClient but the
underlying client should be configured in different ways. The
different, and they use different authorization mechanisms.
It would be nice to lift that configuration up to the composition root
Startup.cs) and never have to think about it again. With Keyed services, you
can declare your configured
RestClients and then inject them by name into
classes that need them.
Does this look familiar? This is exactly like the API for using
where you can name your different configurations. I suspect Microsoft had to do
some gymnastics to make this work on the current
IServiceProvider but the new
keyed services feature in .NET 8 will simplify it greatly.
- Check out my earlier post comparing AutoFac to Microsoft.Extensions.DependencyInjection which also has a discussion of keyed services
- API Proposal and Specification
- Announcement Blog
- Andrew Locke discusses the feature