Search code examples
c#oopdependency-injectionazure-functions.net-6.0

Dependency Injection in abstract classes sharing the same inteface


I have a bunch of abstract classes with the base inheriting from an interface, as such :

public abstract class ServiceBase<T> : IServiceBase
protected ServiceBase(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3)

public abstract class ServiceA<T> : ServiceBase<T>
protected ServiceA(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

public abstract class ServiceAA : ServiceA<Type1>
protected ServiceAA(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

public abstract class ServiceAB : ServiceA<Type2>
protected ServiceAB(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

public abstract class ServiceB<T> : ServiceBase<Type3>
protected ServiceB(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

    public abstract class ServiceBA : ServiceB<Type1>
protected ServiceBA(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

    public abstract class ServiceBB : ServiceB<Type2>
protected ServiceAA(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3) 
: base(extService1, extService2, extService3)

with DI setup like this :

...
builder.Services.AddScoped<IExtService1, extService1>
builder.Services.AddScoped<IExtService2, extService2>
builder.Services.AddScoped<IExtService3, extService3>
builder.Services.AddScoped<IServiceBase, ServiceAA>
builder.Services.AddScoped<IServiceBase, ServiceAB>
builder.Services.AddScoped<IServiceBase, ServiceBA>
builder.Services.AddScoped<IServiceBase, ServiceBB>
...

now I would like to have access to ServiceBA in ServiceA or, if not possible, in ServiceAA and ServiceAB, like this :

public abstract class ServiceA<T> : ServiceBase<T>
protected ServiceA(IExtService1 extService1, IExtService2 extService2, IExtService3 extService3, ServiceBA<T> serviceBA) 
: base(extService1, extService2, extService3)

But it seems that serviceBA in ServiceA isn't instanciated and is always null ... How can I fix that?


Solution

  • Your services are registered in the DI-container by their interface, not the implementation type, so the DI-container cannot retrieve the ServiceBA by its type. But you can register services not only by interface:

    builder.Services.AddScoped<IExtService1, extService1>
    builder.Services.AddScoped<IExtService2, extService2>();
    builder.Services.AddScoped<IExtService3, extService3>();
    builder.Services.AddScoped<IServiceBase, ServiceAA>();
    builder.Services.AddScoped<IServiceBase, ServiceAB>();
    builder.Services.AddScoped<IServiceBase, ServiceBA>();
    builder.Services.AddScoped<IServiceBase, ServiceBB>();
    builder.Services.AddScoped<ServiceAA>();
    builder.Services.AddScoped<ServiceAB>();
    builder.Services.AddScoped<ServiceBA>();
    builder.Services.AddScoped<ServiceBB>();
    

    After that container can resolve the correct implementation for each service.

    Another solution is to make a factory that can resolve a specific service by string or by another key and inject it instead of directly injecting service itself:

    public enum ServiceType
    {
        ServiceAA,
        ServiceAB,
        ServiceBA,
        ServiceBB
    }
    
    public interface IServiceFactory
    {
        IServiceBase CreateService(ServiceType serviceType);
    }
    
    public class ServiceFactory : IServiceFactory
    {
        private readonly IServiceProvider _serviceProvider;
    
        public ServiceFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
    
        public IServiceBase CreateService(ServiceType serviceType)
        {
            return serviceType switch
            {
                ServiceType.ServiceAA => _serviceProvider.GetService<ServiceAA>(),
                ServiceType.ServiceAB => _serviceProvider.GetService<ServiceAB>(),
                ServiceType.ServiceBA => _serviceProvider.GetService<ServiceBA>(),
                ServiceType.ServiceBB => _serviceProvider.GetService<ServiceBB>(),
                _ => throw new ArgumentException($"Invalid service type: {serviceType}")
            };
        }
    }
    

    After that you can register ServiceFactory in DI-container:

    builder.Services.AddScoped<IServiceFactory, ServiceFactory>();
    

    And use it to resolve a concrete service:

    public class SomeClass
    {
        private readonly IServiceFactory _serviceFactory;
    
        public SomeClass(IServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }
    
        public void SomeMethod()
        {
            IServiceBase serviceAA = _serviceFactory.CreateService(ServiceType.ServiceAA);
            IServiceBase serviceAB = _serviceFactory.CreateService(ServiceType.ServiceAB);
        }
    }