0

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:

  • RootA and RootB depend on ISubdependency.
  • 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 for ISubdependency. 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

  1. How can we achieve the desired outcome? Preferably a non-convoluted way!

  2. 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?

Timo
  • 7,992
  • 4
  • 49
  • 67
  • There's no way to specify a specific implementation using standard .NET Core DI. You either do it in the setup like `services.AddSingleton(new RootB(new SubdependencyZ());` or allow `RootB` to take an `IEnumerable` and let it decide which one to use. – DavidG Aug 14 '19 at 11:59
  • Other containers like Autofac and Windsor support this. Microsoft allows you to use them in conjunction with theirs, and even recommends it if you need something they don't support. I recommend picking one (I'd lean toward Autofac just because of the level of documentation available) and learning how to use it. You'll get way more out of it than by learning to do something weird that `ServiceCollection` isn't designed for. Start with [this](https://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html) and suddenly you'll have tons of new options. – Scott Hannen Aug 14 '19 at 14:02
  • @ScottHannen I'm a fan of AutoFac. However, the reason I can't depend on a particular container here is that this is for use in NuGet packages. I'll add that explanation to the constraint so that its importance is more clear. – Timo Aug 15 '19 at 09:30

7 Answers7

2

The built in dependency injection doesnt support your scenario. What you're looking for is "Contextual Binding" which allows you to add a name to a particular binding and then use that name to choose which binding you want at runtime. Many other packages provide this feature out of the box, but MS DI does not. To implement the feature is not trivial. Whilst this answer does not "give you an answer" the answer is, you need to roll your own, or use a third party library instead

Robert Perry
  • 1,906
  • 1
  • 14
  • 14
  • Thanks for mentioning the name of the concept! I guess it's not _entirely_ impossible: Register the subdependency, remember its index (if 2 prior implementations were registered, we will use the 3rd registration), and re-insert the previous "winner" after us again. Then ask for an `IEnumerable` and use the implementation at the correct index. There is one problem left: If the user registers _no_ implementation themselves, then they expect a runtime error if they try to use the type elsewhere. Instead, they will get the type that we had intended _only_ for our component... – Timo Aug 14 '19 at 12:16
  • The basic concept is that the "ServiceDescriptor" describes the registration. What you want, is a property on that class; say "ContextName" and then plumb in the AddSingleton/AddTransient/AddScoped methods so that you can pass in a name. Then during the GetService method you want to be able to pass in the name and search the ServiceDescriptors for the specific name.. Its not a particular difficult concept, your hurdle is more that its MS code.. You could download the Microsoft.Extensions.DependencyInjection package from Github and have a go? But its probably easy to just use a 3rd party one – Robert Perry Aug 14 '19 at 12:23
  • The reason I'm not using a third party container is that all this is for NuGet packages, so we cannot depend on a particular one. But your suggestion is useful! I should be able to use a custom `ServiceDescriptor` subclass and only honor registrations of that type. Now, as with the `IEnumerable` idea: how do I keep those registrations from being used outside of `RootA`, particularly when the user asks for an `ISubdependency` but has forgotten to register one (outside of the nested `RootA`)? – Timo Aug 14 '19 at 12:31
2

I have found a proper solution.

// Startup.cs: usage

public void ConfigureServices(IServiceCollection services)
{
    // ...

    // This is what the application will use when directly asking for an ISubdependency
    services.AddSingleton<ISubdependency, SubdependencyZ>();
    // This is what we will have RootA and RootB use below
    services.AddSingleton<SubdependencyA>();
    services.AddSingleton<SubdependencyB>();

    services.AddRootA(options => options.UseSubdependency<SubdependencyA>());
    services.AddRootB(options => options.UseSubdependency<SubdependencyB>());

    // ...
}

// RootAExtensions.cs: implementation

public static IServiceCollection AddRootA(this IServiceCollection services, Action<Options> options)
{
    var optionsObject = new Options();
    options(optionsObject); // Let the user's action manipulate the options object

    // Resolve the chosen subdependency at construction time
    var subdependencyType = optionsObject.SubdependencyType;
    services.AddSingleton<IRootA>(serviceProvider =>
        new RootA(serviceProvider.GetRequiredService(subdependencyType)));

    return services;
}

public sealed class Options
{
    public IServiceCollection Services { get; }

    internal Type SubdependencyType { get; set; } = typeof(ISubdependency); // Interface by default

    public Options(IServiceCollection services)
    {
        this.Services = services;
    }
}

// Instructs the library to use the given subdependency
// By default, whatever is registered as ISubdependency is used
public static Options UseSubdependency<TSubdependency>(this Options options)
    where TSubdependency : class, ISubdependency
{
    options.SubdependencyType = typeof(TSubdependency);
    return options;
}

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 the UseSubdependency method, that would be used:

services.AddRootA(options => { }); // Will default to asking for an `ISubdependency`

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.

Timo
  • 7,992
  • 4
  • 49
  • 67
1

Due to lack of more complex features in .Net Core DI, maybe it is easiest for you to create marker interfaces for each specific sub type.

interface ISubdependency { }

interface ISubdependencyA : ISubdependency { }

class SubdependencyA : ISubdependencyA { }

interface IRootA {}

class RootA : IRootA
{ 
    public RootA(ISubdependency subdependency)
    {

    }
}

interface ISubdependencyB : ISubdependency { }

class SubdependencyB : ISubdependencyB { }

interface IRootB {}

class RootB : IRootB
{
    public RootB(ISubdependency subdependency)
    {

    }
}

If possible, the most straightforward DI composition would be if Root classes depend upon their subsystem interface, but if not possible you can use factory to register each Root:

services.AddSingleton<ISubdependencyA, SubdependencyA>();
services.AddSingleton<ISubdependencyB, SubdependencyB>();
services.AddSingleton<IRootA, RootA>(provider => new RootA(provider.GetRequiredService<ISubdependencyA>()));
services.AddSingleton<IRootB, RootB>(provider => new RootB(provider.GetRequiredService<ISubdependencyB>()));

The other possibility, is to depend upon IEnumerable<ISubdependency> and then take appropriate one to work with.

Darjan Bogdan
  • 3,780
  • 1
  • 22
  • 31
  • Very good. I had played with both the marker interfaces and the `IEnumerable` idea. `IEnumerable` is promising, but has one problem scenario (see my comment under Robert Perry's answer). The marker interfaces I would like to keep hidden from the user. So the user is still required to provide some `ISubdependency`. Then `RootA` will actually use an `ISubdependencyA : ISubdependency`. It can use a simple proxy that passes all behavior to a given `ISubdependency` (which fails to satisfy `ISubdependencyA` on its own). – Timo Aug 14 '19 at 12:23
  • However, `options.UseSubdependency()` is still not satisfied. We cannot expect an implementation (which might come from another NuGet package) to implement our _marker_ interface, `ISubdependencyA`, but only `ISubdependency`. So we need our little proxy in between. However, that needs an _instance_ of `SubdependencyX` to use, and we cannot resolve `SubdependencyX` without adding it to the container (which would inadvertently register it "globally"). – Timo Aug 14 '19 at 12:26
  • @Timo I get your point, tricky situation. Would you find convoluted to have a specific "resolver" class in between "collection of subdependencies" and "root" class. Something like: `interface ISubdepedencyResolver { ISubdependency Get(Type rootType); }`, implementation of that interface would get subdependency from the collection and return correct one to the caller. That means, `Root` classes would be dependant upon ISubdependencyResolver instead of collection of ISubdependencies. – Darjan Bogdan Aug 14 '19 at 12:30
  • I'm open to that idea, but I'm trying to see how it helps. Doesn't it still need to ask the container for an `ISubdependency`? Which means the implementation that was intended for `RootA` _only_... has to be registered in the container, inadvertently becoming available globally? Or am I missing something? – Timo Aug 14 '19 at 12:33
  • 1
    Ah yes, all subdependencies would be reachable globally, overlooked that requirement. Need to think how to circumvent. It's funny to see how in-built container lacks of some quite powerful features like Conditional or Contextual bindings out of the box :) – Darjan Bogdan Aug 14 '19 at 12:37
0

Edit: By now I have found a more appropriate solution, which I have posted as a separate answer.

I have found a (slightly convoluted) way to achieve the desired result without any third party libraries.

// RootA's options object has a fluent extension method to register the subdependency
// This registration will be used ONLY for RootA
public static RootAOptions AddSubdependency<TImplementation>(this RootAOptions options)
    where TImplementation : ISubdependency
{
    // Insert the desired dependency, so that we have a way to resolve it.
    // Register it at index 0, so that potential global registrations stay leading.
    // If we ask for all registered services, we can take the first one.
    // Register it as itself rather than as the interface.
    // This makes it less likely to have a global effect.
    // Also, if RootB registered the same type, we would use either of the identical two.
    options.Services.Insert(0,
        new ServiceDescriptor(typeof(TImplementation), typeof(TImplementation), ServiceLifetime.Singleton));

    // Insert a null-resolver right after it.
    // If the user has not made any other registration, but DOES ask for an instance elsewhere...
    // ...then they will get null, as if nothing was registered, throwing if they'd required it.
    options.Services.Insert(1,
        new ServiceDescriptor(typeof(TImplementation), provider => null, ServiceLifetime.Singleton));

    // Finally, register our required ISubdependencyA, which is how RootA asks for its own variant.
    // Implement it using our little proxy, which forwards to the TImplementation.
    // The TImplementation is found by asking for all registered ones and the one we put at index 0.
    options.Services.AddSingleton<ISubdependencyA>(provider =>
        new SubdependencyAProxy(provider.GetServices<TImplementation>().First()));

    return options;
}
Timo
  • 7,992
  • 4
  • 49
  • 67
  • This looks rather complicated. It depends on indexes within collections. This - "This makes it less likely to have a global effect" - is especially concerning. Anything that's "likely" or unlikely sounds unpredictable and may vex future developers. – Scott Hannen Aug 14 '19 at 14:05
  • @ScottHannen Yeah, I guess that's the "slightly convoluted" part. I am open to better solutions _that meet the constraints_. ;) – Timo Aug 15 '19 at 09:27
0

Here's a solution using Autofac.Extensions.DependencyInjection. Microsoft recommends using another container if the requirements exceed what the provided container does.

The built-in service container is meant to serve the needs of the framework and most consumer apps. We recommend using the built-in container unless you need a specific feature that it doesn't support.

The setup for answering this includes creating a few types just for illustration. I've tried to keep this as minimal as possible.

What we've got are:

  • Two types that both depend on IDependency
  • Two implementations of IDependency
  • We want to inject a different implementation of IDependency into each type that needs it.
  • The two classes that get IDependency injected both expose it as a property. That's just so we can test that the solution works. (I wouldn't do that in "real" code. This is just for the purpose of illustrating and testing.)
public interface INeedsDependency
{
    IDependency InjectedDependency { get; }
}

public class NeedsDependency : INeedsDependency
{
    private readonly IDependency _dependency;

    public NeedsDependency(IDependency dependency)
    {
        _dependency = dependency;
    }

    public IDependency InjectedDependency => _dependency;
}

public interface IAlsoNeedsDependency
{
    IDependency InjectedDependency { get; }
}

public class AlsoNeedsDependency : IAlsoNeedsDependency
{
    private readonly IDependency _dependency;

    public AlsoNeedsDependency(IDependency dependency)
    {
        _dependency = dependency;
    }

    public IDependency InjectedDependency => _dependency;
}

public interface IDependency { }

public class DependencyVersionOne : IDependency { }

public class DependencyVersionTwo : IDependency { }

How do we configure this so that NeedsDependency gets DependencyVersionOne and AlsoNeedsDependency gets DependencyVersionTwo?

Here it is in the form of a unit test. Writing it this way make it easy to verify that we're getting the result we expect.

[TestClass]
public class TestNamedDependencies
{
    [TestMethod]
    public void DifferentClassesGetDifferentDependencies()
    {
        var services = new ServiceCollection();
        var serviceProvider = GetServiceProvider(services);

        var needsDependency = serviceProvider.GetService<INeedsDependency>();
        Assert.IsInstanceOfType(needsDependency.InjectedDependency, typeof(DependencyVersionOne));

        var alsoNeedsDependency = serviceProvider.GetService<IAlsoNeedsDependency>();
        Assert.IsInstanceOfType(alsoNeedsDependency.InjectedDependency, typeof(DependencyVersionTwo));
    }

    private IServiceProvider GetServiceProvider(IServiceCollection services)
    {
        /*
         * With Autofac, ContainerBuilder and Container are similar to
         * IServiceCollection and IServiceProvider.
         * We register services with the ContainerBuilder and then
         * use it to create a Container.
         */

        var builder = new ContainerBuilder();

        /*
         * This is important. If we already had services registered with the
         * IServiceCollection, they will get added to the new container.
         */
        builder.Populate(services);

        /*
         * Register two implementations of IDependency.
         * Give them names. 
         */
        builder.RegisterType<DependencyVersionOne>().As<IDependency>()
            .Named<IDependency>("VersionOne")
            .SingleInstance();
        builder.RegisterType<DependencyVersionTwo>().As<IDependency>()
            .Named<IDependency>("VersionTwo")
            .SingleInstance();

        /*
         * Register the classes that depend on IDependency.
         * Specify the name to use for each one.
         * In the future, if we want to change which implementation
         * is used, we just change the name.
         */
        builder.Register(ctx => new NeedsDependency(ctx.ResolveNamed<IDependency>("VersionOne")))
            .As<INeedsDependency>();
        builder.Register(ctx => new AlsoNeedsDependency(ctx.ResolveNamed<IDependency>("VersionTwo")))
            .As<IAlsoNeedsDependency>();

        // Build the container
        var container = builder.Build();

        /*
         * This last step uses the Container to create an AutofacServiceProvider,
         * which is an implementation of IServiceProvider. This is the IServiceProvider
         * our app will use to resolve dependencies.
         */
        return new AutofacServiceProvider(container);
    }
}

The unit test resolves both types and verifies that we've injected what we expect.

Now, how do we take this and put it in a "real" application?

In your Startup class, change

public void ConfigureServices(IServiceCollection services)

to

public IServiceProvider ConfigureServices(IServiceCollection services)

Now ConfigureServices will return an IServiceProvider.

Then, you can add the Autofac ContainerBuilder steps to ConfigureServices and have the method return new AutofacServiceProvider(container);

If you're already registering services with the IServiceCollection, that's fine. Leave that as it is. Whatever services you need to register with the Autofac ContainerBuilder, register those.

Just make sure you include this step:

builder.Populate(services);

So that whatever was registered with the IServiceCollection also gets added to the ContainerBuilder.


This might seem a tiny bit convoluted as opposed to just making something work with the provided IoC container. The advantage is that once you get over that hump, you can leverage the helpful things that other containers can do. You might even decide to use Autofac to register all of your dependencies. You can search for different ways to register and use named or keyed dependencies with Autofac, and all of those options are available to you. (Their documentation is great.) You can also use Windsor or others.

Dependency injection was around long before Microsoft.Extensions.DependencyInjection, IServiceCollection, and IServiceProvider. It helps to learn how to do the same or similar things with different tools so that we're working with the underlying concepts, not just a specific implementation.

Here is some more documentation from Autofac specific to using it with ASP.NET Core.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
0

If you know exactly how you want to compose each class you can compose them "manually" and register those instances with the container. That's especially easy if the classes are registered as singletons, as in your question, but it can apply even if they are transient or scoped.

It's common to write this as an extension.

public static class YourServiceExtensions
{
    public static IServiceCollection AddYourStuff(this IServiceCollection services)
    {
        services.AddSingleton<SubdependencyOne>();
        services.AddSingleton<SubdependencyTwo>();
        services.AddSingleton<IRootA>(provider =>
        {
            var subdependency = provider.GetService<SubdependencyOne>();
            return new RootA(subdependency);
        });
        services.AddSingleton<IRootB>(provider =>
        {
            var subdependency = provider.GetService<SubdependencyTwo>();
            return new RootB(subdependency);
        });
        return services;
    }
}

Then, in Startup,

services.AddYourStuff();

Even if there are some more complex dependencies involved, that's okay. You only have to compose each class once and the consumer doesn't care how everything is composed - they just call the extension. (Although you're registering the composed instances with the container, this is similar to what we call pure DI.)

That makes this a much simpler approach than trying to get the IServiceProvider to figure out which dependency to resolve and when.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • Good one! That actually succeeds. In practice, it will generally be the case that the _application_ wants to choose which dependency uses which implementation as its subdependency, so that scenario remains an open challenge. In the meantime, I seem to have found a solution for that scenario as well, which I will post in a separate answer when I have the time. – Timo Aug 16 '19 at 14:02
  • Does the application decide this at startup? If so, it can pass a parameter to the extension, like an `enum`. It's still best to hide the full implementation details, but if the application can choose in an abstract sense that's good. – Scott Hannen Aug 16 '19 at 14:11
  • That would break extensibility. Further NuGet packages (potentially in the future; potentially by a third party) may want to create new subdependency implementations, e.g. SubdependencyThree. – Timo Aug 16 '19 at 15:02
  • That can be handled as well. If you want to configure your own dependencies while allowing the application to provide certain implementations, you can allow the extensibility for the app to either provide implementations of interfaces or provide custom methods for registering certain implementations. (Consider `AddHttpClient`.) That's good extensibility balanced with encapsulation. You determine exactly what you want to expose rather than allowing the app free reign to poke around at the internals of your code. – Scott Hannen Aug 16 '19 at 15:06
  • [Example](https://stackoverflow.com/questions/56255424/net-core-di-register-a-default-implementation-for-a-package) – Scott Hannen Aug 16 '19 at 15:09
0

the code is intended for use in NuGet packages, where the consuming application chooses the container

A library or framework shouldn't depend on any DI Container, not even a Conforming Container. That also rules out the built-in container in .NET.

Instead, design libraries so that they're friendly to any implementation of DI.

If RootA depends on ISubdependency, just make it a dependency, and use Constructor Injection to advertise it:

public RootA(ISubdependency)

If RootB also has the same dependency, use the same pattern:

public RootB(ISubdependency)

Any consumer of the library can now configure instances of RootA and RootB exactly how they prefer to do so. Using Pure DI, it's a simply as newing up the objects:

var rootA = new RootA(new SubdependencyX(/* inject dependencies here if needed */));
var rootB = new RootB(subdependencyY); // subdependencyY is an existing instance...

Any client is also free to pick a DI Container of its own choice, which may or may not be able to address complex dependency selection scenarios. This provides full flexibility for all clients, without constraining anyone to the lowest common denominator provided by a Conforming Container.

You can't beat Pure DI when it comes to flexibility, since it's based on the full power of C# (or Visual Basic, or F#, etc.) instead of an API that may or may not expose the functionality you need.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Please note that all this is possible with the libraries, as they use construction injection, of course. However, since virtually every consuming application is using .NET Core's regular container abstractions (`IServiceCollection` and friends), it is a _very_ useful feature to add methods that register the library in a single call, such as `services.AddMyLibrary()`. The library may register various dependencies, hosted services for background work, and other libraries that it depends on (provided these libraries require no configuration). – Timo Sep 02 '19 at 08:56
  • There is another benefit to the library registration extension method. Certain dependency implementations in the library are internal, and should be kept that way. The consuming application is unable to register those. We should not require that the application understands our library's inner workings, that it manually registers every internal dependency that we have. That would also make it very difficult to refactor the library without breaking changes to its API. – Timo Sep 02 '19 at 09:23