Say we register two types, RootA and RootB, that each have a dependency on ISubdependency.
Sharing the same subdependency implementation is easy:
services.AddSingleton<ISubdependency, SubdependencyZ>();
services.AddSingleton<IRootA, RootA>();
services.AddSingleton<IRootB, RootB>();
Now the goal is for the two root types to use different implementations of the subdependency. The caller should be able to register either an instance, a factory, or a type.
// Instance
services.AddRootA<IRootA, RootA>(options =>
options.UseSubDependency(new SubdependencyZ()));
// Factory
services.AddRootB<IRootB, RootB>(options =>
options.UseSubDependency(provider =>
new SubDependencyY(provider.GetRequiredService<IWhatever>())));
// Type
services.AddRootB<IRootB, RootB>(options =>
options.UseSubDependency<SubdependencyX>());
I have managed to achieve the first two scenarios, although the approach is a bit complex to explain here. The third scenario, however, is still beyond me. Let's assume that if we can solve that one, we can solve them all.
So the problem is this:
RootAandRootBdepend onISubdependency.- Other types might depend on ISubdependency as well.
- If we register a particular implementation, e.g.
services.AddSingleton<ISubdependency, SubdependencyZ>(), then that registration is global (to the container), and it overwrites any previous registrations forISubdependency. As a result, the last registration ends up being used for all dependants! - Particularly the type-based registration (scenario 3 above) is challenging, because we only have the type, and no easy way to resolve an instance. That means we have to resort to having the container resolve the registered type, which makes it even harder to work around the previous bullet point.
- We must stick to .NET Core's IOC extensions. We are not permitted to depend on a particular third party container. Edit: This is because the code is intended for use in NuGet packages, where the consuming application chooses the container.
Questions
How can we achieve the desired outcome? Preferably a non-convoluted way!
Is there a de facto standard with regards to this problem? Is it a use case that is generally recognized, using different implementations for dependants on the same interface? Or is this generally avoided altogether, forcing dependants to simply use the same implementation?
I have found a proper solution.
First, the user registers anything related to the subdependency. In this example, I have considered the case where the application also uses the subdependency directly, and the direct usage calls for another implementation than the usage by library RootA, which in turn calls for another implementation than RootB.
After registering all this (or before - technically the order doesn't matter), the user registers the high-level dependencies, RootA and RootB. Their options allow the user to specify the subdependency type to use.
Looking at the implementation, you can see that we use the factory-based overload of
AddSingleton, which lets us ask the service provider for any subdependencies at construction time.The implementation also initializes the type to use to
typeof(ISubdependency). If the user were to ignore theUseSubdependencymethod, that would be used:If the user fails to register an implementation for
ISubdependency, the get the usual exception for that.Note that we never allow the user to register a thing in a nested fashion. That would be confusing: it would look like the registration is only for the thing that wraps it, but since the container is a flat collection, it is actually a global registration.
Instead, we only allow the user to refer to something that they explicitly register elsewhere. This way, no confusion is introduced.