3

I've run into a bit of trouble trying to use dependency injection and need help.

I have a service IService that is implemented a few different ways in my application.

ServiceA : IService { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IService { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IService serviceA, IService serviceB) {...} }

In my startup.cs file, I will pick which one to use based on a parameter in my configuration file. Something like this:

var service = Configuration["AppConfig:Service:ID"];
switch(service) {
    case "A":
        services.AddTransient<IService, ServiceA>();
    case "B":
        services.AddTransient<IService, ServiceB>();
    case "C":
        // ??
}

I am able to create and use service A and service B very easily. I inject their dependencies A and B before hand and they get created just fine. The problem is with the third service. I need to inject the two other services into it. My question is: what is the best to do this?

Is there a way I can create concrete implementations of service A and B but somehow use the injected dependency A and B in their constructor? Do I have to mess around with the interfaces to get this to work? Maybe I have to change the constructor of ServiceC to take in concrete implementations of A & B?

Updated: don't know if it's the best solution, but I ended up doing the following to get it to work.

...
case "C":
    services.AddTransient<ServiceA>();
    services.AddTransient<ServiceB>();
    services.AddTransient<IService>(s => 
        new ServiceC(
            s.GetService<ServiceA>(),
            s.GetService<ServiceB>()
    ));
9
  • Add more details about service C. If it really needs these 2 concrete implementations, then you should change the constructor. Otherwise it will break Liskov Substitution Principle. Fix switch, there are no breaks, and serviceA is registered 2 times. Commented Sep 1, 2020 at 19:35
  • The goal of service C is to use the implementation of service A, but if service A could not provide a correct result, fallback to the result of service B. Commented Sep 1, 2020 at 19:37
  • Create a factory pattern for required services, and instantiate them when needed. Not everything needs to be handles by DI() Commented Sep 1, 2020 at 19:39
  • Based on your last comment here the usage of services based on their result, you could achieve this in a cleaner fashion using a concept similar to the Strategy Pattern. It certainly would be better if you could predict whether or not the result will be successful. Commented Sep 1, 2020 at 20:12
  • 1
    @mymemesarespiciest even if you don't use Polly directly you should see how it works, how it solves the same problems you may have Commented Sep 2, 2020 at 10:10

2 Answers 2

1

Another alternative. Introduce another interface, so you can register the both ServiceC and it's dependencies at the same time, without any ambiguity or circular references.

interface IService {}
interface IServiceImpl : IService {}

ServiceA : IServiceImpl { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IServiceImpl { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IEnumerable<IServiceImpl> services) {...} }

services.AddTransient<IServiceImpl, ServiceA>();
services.AddTransient<IServiceImpl, ServiceB>();
services.AddTransient<IService, ServiceC>();
Sign up to request clarification or add additional context in comments.

5 Comments

Yes it will. DI will usually inject the first registered type. Note that I changed the constructor of ServiceC to IEnumerable<T> so it will receive all of the service implementations.
I understand why ServiceC is OK. I need to test setup for ServiceA and ServiceB on my own during this weekend.
BTW: When testing this, how should I resolve ServiceA and ServiceB in e.g. a controller? By injecting IServiceImpl?
The idea was to keep IService as the "public API", and only refer to IServiceImpl "internally" as an implementation detail...
I see that OP solved it by using services.AddTransient<ServiceA>(); services.AddTransient<ServiceB>(); , maybe no point digging further into this for now. Anyway, Now I do understand the logic behind your answer, +1 for that :-) Thanks for your time Jeremy. BR
0

There must be a better explanation for having such dependencies.

Microsoft.Extensions.DependencyInjection does not provide named registrations like other IoC containers i.e. Castle.Windsor.

There is a way you can still register ServiceC with instances of A and B

            services.AddTransient<ServiceA>();
            services.AddTransient<ServiceB>();
            services.AddTransient<IService>(serviceProvider => {
                return new ServiceC((IService)serviceProvider.GetRequiredService(typeof(ServiceA)), (IService)serviceProvider.GetRequiredService(typeof(ServiceB)));
            });

2 Comments

Funny that you post this because it's nearly identical to the solution I just found that ended up working. The only difference is that you don't need cast to IService because those services are already implementing that interface.
aha nice! Btw. I dont refresh while writing. and "funny" enough you think like that :) The only reason for casting is GetRequiredService does require typecasting

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.