skip to Main Content

In C#, of course, many dependencies can be registered for DI with one short line via Microsoft.Extensions.DependencyInjection, especially when there is exactly one implementation for a interface. For example, suppose that Service1 depends on three other services, and one of those on two more:

_serviceCollection.AddTransient<IService1,Service1>();
_serviceCollection.AddTransient<IOtherService,OtherService>();
_serviceCollection.AddTransient<IAnotherOne,Service1>();
_serviceCollection.AddTransient<IService4,Service4>();
_serviceCollection.AddTransient<IService5,Service5>(); 
_serviceCollection.AddTransient<IService6WithAReallyLongName,Service6WithAReallyLongName>();

Is there a terser/easier way to register dependencies when I want only one (or a few) of them to be "special", perhaps specified with a factory method? A variation of the example here is as follows; I want to specify a special concrete type for one dependency of a service, and it likely has its own dependencies. In code called from Startup.cs, I believe I’m forced to specify all dependencies, and all dependencies my special dependency:

_serviceCollection.AddTransient<IService1>(x => 
    new Service1(
       x.GetRequiredService<IOtherService>(),
       x.GetRequiredService<IAnotherOne>(), 
       new SpecialService4(
           x.GetRequiredService<IService5>(), 
           x.GetRequiredService<IService6WithAReallyLongName>() ) ));

(Of course, it could equally well be AddSingleton or AddScoped as well.)

Note in the above, I want only SpecialService to be the special concrete dependency that I am registering; the other five can take the default implementation as registered in the top code example.

That’s really a lot of picky error-prone code entry, and every time I explain it to one of my teammates they rightly lament and wail. I just want to override one thing, why do I have to override six things to do so?

I’m looking for something instead like the following hypothetical syntax:

_serviceCollection.AddTransient<IService1, Service1>()
   .WithParameter<IService4, SpecialService4>();

And then have the constructors automatically fill in the concretes for IOtherService, IAnotherOne, IService5, and IService6WithAReallyLongName automatically.

Is there any better way, short of going to Autofac or some other DI container library, to specify one special concrete dependency for one parameter without specifying all other dependencies’ parameters AND all the non-special parameters of the special concrete dependency? Perhaps there is a NuGet package that adds some syntactic sugar to registering dependencies with AddTransient/AddScoped/AddSingleton?

It seems like this would happen all the time once we have more than one concrete class for an interface, so I’m surprised this need isn’t more prevalent.

4

Answers


  1. You can use marker interfaces where SpecialService1 has a dependency on IDependency1 but implemented in a "special" way by SpecialDependency1:

    interface IDependency1 { /* Something useful here */ }
    
    interface ISpecialDependency1 : IDependency1 { /* Nothing here - just a marker */ }
    
    class SpecialService1 : IService1
    {
        readonly IDependency1 dependency1;
        
        public SpecialService1(ISpecialDependency1 dependency1) =>
            this.dependency1 = dependency1;
    }
    
    class SpecialDependency1 : ISpecialDependency1 { /* So very special */ }
    

    You need to add the following services to the container:

    services
        .AddTransient<IService1, SpecialService1>()
        .AddTransient<ISpecialDependency1, SpecialDependency1>();
    

    This is nowhere close to what you have described as your proposed solution but I believe that it solves your problem. Personally, and without knowing your use case, I would probably be in the "lament and wail" camp if I were to work with something like this.

    Login or Signup to reply.
  2. One way to clean up your DI code somewhat is to register the special class as itself (or with a marker interface if you insist on only registering interfaces). Then register your factory delegate for the consumer, but use the DI container to construct the special class. Like so:

    _serviceCollection.AddTransient<SpecialService4>();
    _serviceCollection.AddTransient<IService1>(x => 
        new Service1(
           x.GetRequiredService<IOtherService>(),
           x.GetRequiredService<IAnotherOne>(), 
           x.GetRequiredService<SpecialService4>()));
    

    Edit

    This has been nagging at me and I finally realized why. I originally saw this technique in the context of registering decorators. The ActivatorUtilities.CreateInstance method can be used to simplify this like so:

    _serviceCollection.AddTransient<SpecialService4>();
    _serviceCollection.AddTransient<IService1>(provider =>
        ActivatorUtilities.CreateInstance<Service1>(
            provider, 
            provider.GetRequiredService<SpecialService4>()));
    

    The basic idea behind ActivatorUtilities.CreateInstance is that you can supply implementations of some or all of the constructor parameters, and anything that is not supplied will be retrieved from the service provider.

    Edit 2

    Since this is something I’ve occasionally considered using myself, I created a package that is basically a facade over ActivatorUtilities.CreateInstance:

    https://www.nuget.org/packages/ServiceProviderContextualBinding/

    Usage example:

    _serviceCollection.AddTransient<SpecialService4>();
    _serviceCollection.WithReplacement<IService4, SpecialService4>()
        .AddSingleton<IService1, Service1>();
    

    There is another package that does something similar, but also includes assembly scanning:

    https://www.nuget.org/packages/ForEvolve.DependencyInjection.ContextualBindings/

    Login or Signup to reply.
  3. You can do something similar using keyed registrations. Unfortunately build in DI container does not provide such feature but you can use 3rd party IoC container to replace a build in one. For example Autofac has keyed service registration/resolution.

    Login or Signup to reply.
  4. What you’re asking for is called Contextual Binding, and it’s a feature that some more advanced Dependency Injection frameworks have made available, at the cost of performance and simplicity. Microsoft.Extensions.DependencyInjection doesn’t support Contextual Binding.

    You can find some examples of workarounds in the answers to this similar question.

    One strategy that can at least simplify your current approach a little is to create a registration for the concrete type, and leverage that to avoid tightly coupling your service factory to that type’s dependencies:

    _serviceCollection.AddTransient<SpecialService4>();
    _serviceCollection.AddTransient<IService1>(x => 
        new Service1(
           x.GetRequiredService<IOtherService>(),
           x.GetRequiredService<IAnotherOne>(), 
           x.GetRequiredService<SpecialService4>() ));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search