If you want to go straight to the solution of how to resolve instances by a name or key without reading the background, go to Part 2 of this post.
Many dependency injection container solutions support the concept of labeling a registered dependency with a name or a key that can be used by the container to resolve the dependency required when presented by a dependent class.
For instance, Autofac supports both named and keyed labeling using a syntax like this to register the dependency:
It provides both an explicit method to resolve the dependency, and an attributed way on the constructor parameters of a class.
public DeviceInfo([KeyFilter(“online”)] IDeviceState deviceState)
However, the container that is provided out of the box with .NET Core does not support named or keyed registrations.
Mix And Match Containers
The Microsoft DI Container is a ‘Conforming Container‘ that provides a common abstraction that can be wired up to other containers such as Autofac, Ninject, StructureMap et al.
As a result, it only supports the lowest common denominator across multiple DI solutions, and more accurately, only what Microsoft deemed as necessary for its purposes.
This means that if you want to use features such as named/keyed resolution, you end up having to mix and match both the Microsoft DI Container and your other container of choice.
Now, most of the big name containers support this and provide methods that register the custom container so it can be retrieved in constructor injection and the custom features accessed using that containers methods.
However, the downside to this is that
- you are losing the common abstraction as you are including code that is specific to a container technology in your code base
- if you inject the specific container into a constructor, you are inadvertently being led towards the Concrete Service Locator Anti-Pattern where the specific container is effectively acting as the service locator for you
- you are binding your solution to a particular container technology which needs changing across the codebase if you decide to use a different container.
“I could write an adapter …”
The first and last of these issues can be mitigated by creating an interface for the functionality you require to abstract your code away from a particular container technology and then writing an adapter.
However, you are still have a service locator being injected into constructors in order to get to the abstracted functionality (in this case, obtaining an instance based on a name or key).
Why Would I Need to Get An Instance By Name or Key? – Surely it is an Anti-Pattern?
To an extent, I agree that using a name string or key to resolve an instance is effectively a service locator as it is forcing a lookup within the container to retrieve an instance of the required type.
Under normal circumstances, I would be looking at ways to avoid using this way of resolving an instance such as creating alias interfaces.
For example, say I have three different classes FooA, FooB & FooC that all implement an interface IFoo.
I have a class that has needs all three implementations to perform some functionality. I have two choices in how I can inject the classes.
I can (a) either explicitly define the three class types as parameters in the constructor of my class, or (b) I can register all three classes as IFoo and define a single parameter of IEnumerable<IFoo>.
The downside to (a) is that I am binding to the concrete implementations which may limit my unit testing if I am unable to access or mock the concrete instances in my unit test project. It also means that if I want to replace one of the classes with a different implementation I would need to change the constructor signature of my class.
The downside to (b) is that the class receives the implementation instances in the order that the container decides to present them (usually the order of registration) and the class code needs to know how to tell the implementations apart. This effectively is moving the service locator inside my class.
To get around this scenario, I could create three interfaces IFooA, IFooB and IFooC that each directly inherits from IFoo. The three class implementations would then be implementations of the appropriate interface and can be registered with the DI container as such.
The receiving class can then explicitly define IFooA, IFooB and IFooC as parameters to the constructor. This is effectively the same as we had before, but makes the class more testable as the interfaces can be easily mocked in unit testing instead of being bound to the concrete classes.
Alternatively, we can still leave the classes registered as implementations of IFoo and have a constructor that takes IEnumerable as its parameter. However, this will mean that our class has to iterate over the enumeration to identify which classes are implementations of IFooA, IFooB & IFooC and then cast as appropriate. It then also has to know how to handle multiple registrations of the three derived interfaces. E.g. what happens if there are three IFooA implementations? Which should be used? The first? The last? Chain all of them?
With all this said, I recently had a situation where I needed to resolve instances from the container using a string as the key, which is what got me thinking about this in the first place.
Data Driven Instance Resolution
Say you have a data source that has child records in multiple different formats. The association is defined by an identifier and a string that indicates the type of data.
In order to correctly retrieve the data and then transform it into a common format suitable for export to another system, you are reliant upon that string to identify the correct mapper to use. In this situation, we need some way of being able to identify a mapper class instance from the identifying string in order to perform the correct mapping work required.
You could create a class mapping factory that maintains a dictionary of the acceptable strings that will be in the data and the type required for that string, then has a method that provides an instance of the type for a particular string. E.g.
IFoo GetFooByKey(string key);
The problem here is that your implementation of IFooFactory becomes a service locator as it will need the IServiceProvider passed to its constructor in order to be able to create the instance required.
Ideally, you want your code to be ignorant of IServiceProvider so that you do not have to mock it in your unit tests.
This is the point where you want your DI container to be able to be able to inject by name or key.
This is where I come to the whole point of this post.
Resolving an Instance by Name from the Microsoft DI Container using a Strongly Typed Delegate.
We want our classes to be completely ignorant of the DI container being used and just be able to dynamically obtain the instance.
This is where our old friend, the delegate keyword can help us, which I describe in more detail in Part 2.