'asp.net core constructor injection with inheritance

In my asp.net core application I have dependency classes which are injected to almost all services. So I want to build a base service class to get these dependencies to properties and my services inherit this base service class.

public abstract class BaseService
{
    protected Foo Foo { get; set; }
    protected Bar Bar { get; set; }

    public BaseService(Foo foo, Bar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}
public class Service : BaseService
{
    public Service(IOtherDependency otherDependency) { }

    public void Method()
    {
        var value = Bar.value;
        Foo.Do(value);
    }
}

So with the given code it warns me to call base constructor with supplied parameters, however they are the parameters that will be injected on runtime, I don't want it. If I add a parameterless constructor it will not call my parameterized constructor which I need.

I don't want to call or define any class that injected in base service(Foo and Bar) inside my inherited service, how can I do that ?

By the way Foo and Bar classes are injected as singleton to container in case their lifetime are important.



Solution 1:[1]

Good. Let us have the base class BaseService with a constructor passing its dependencies as parameters:

public abstract class BaseService
{
    protected IFoo Foo { get; private set; }
    protected IBar Bar { get; private set; }

    public BaseService(IFoo foo, IBar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}

public class Service : BaseService
{
    protected IOtherDependency otherDependency { get; private set; }

    public Service(IOtherDependency otherDependency, IFoo foo, IBar bar)
        : base(foo, bar)
    {
        OtherDependency = otherDependency;
    }

}

What can the developer of the BaseService class now do when they find out that some of its functionality should be outsourced to an external service on which that class should depend?

Adding another INewDependency parameter to the BaseService class constructor is a violation of the contract with the derived class developers, because they call the BaseService class constructor explicitly and expect only two parameters, so when upgrading to the new version, the corresponding signature constructor is not found and compilation fails. In my opinion, the requirement to repeat the list of base class dependencies in the code of derived classes is a violation of the Single Source Of Truth principle, and the compilation failure after allocating part of the base class functionality into the dependency is a consequence of this violation.

As a workaround, I suggest concentrating all the dependencies of the base class into the properties of the designated sealed class, register this class by ConfigureServices, and pass it in the constructor parameter.

Do the same for a derived class. The developer registers each of these classes with the IServicesCollection.

public abstract class BaseService
{
    protected IFoo Foo { get; private set; }
    protected IBar Bar { get; private set; }

    public BaseService(Dependencies dependencies)
    {
        Foo = dependencies.Foo;
        Bar = dependencies.Bar;
    }

    public sealed class Dependencies
    {
        internal IFoo Foo { get; private set; }
        internal IBar Bar { get; private set; }

        public Dependencies(IFoo foo, IBar bar)
        {
            Foo = foo;
            Bar = bar;
        }
    }
}

An object with parent class dependencies will be referenced by a class property that provides child class dependencies. However, the code of the derived class completely abstracts from the list of dependencies of the parent class, which is encapsulated in the BaseService.Dependencies type:

public class Service : BaseService
{
    protected IOtherDependency OtherDependency { get; private set; }

    public Service(Dependencies dependencies) : base(dependencies.BaseDependencies)
    {
        OtherDependency = dependencies.OtherDependency;
    }

    public new sealed class Dependencies
    {
        internal IOtherDependency OtherDependency { get; private set; }
        internal BaseService.Depencencies BaseDependencies { get; private set; }

        public Dependencies(IOtherDependency otherDependency, BaseService.Dependencies baseDependencies)
        {
            OtherDependency = otherDependency;
            BaseDependencies = baseDependencies;
        }
    }    
}

In this design, the constructor of each class has a single parameter, an instance of the sealed class with its dependencies, with inherited dependencies being passed as a property. The list of class dependencies is encapsulated in the Dependencies class, which is provided to consumers along with the class and default IServiceCollection registrations.

If a BaseClass developer decides to outsource some functionality to a new dependency, all necessary changes will be made within the supplied package, while its consumers do not have to change anything in their code.

Solution 2:[2]

Here is the simplest way by using the generic Base Controller class:

public abstract class BaseController<T> : Controller
{
    private IFoo _fooInstance;
    private IBar _barInstance;

    protected IFoo _foo => _fooInstance ??= HttpContext.RequestServices.GetService<IFoo>();
    protected IBar _bar => _barInstance ??= HttpContext.RequestServices.GetService<IBar>();
}

and if you are using Razor pages:

class BasePageModel<T> : PageModel where T : class
{
    private IFoo _fooInstance;
    private IBar _barInstance;

    protected IFoo _foo => _fooInstance ??= HttpContext.RequestServices.GetService<IFoo>();
    protected IBar _bar => _barInstance ??= HttpContext.RequestServices.GetService<IBar>();
}

Solution 3:[3]

You can't do it that way.

If you derive from a base class that does not have a default constructor then you must pass the parameters to the derived class constructor and call the base class constructor with them e.g.

public abstract class BaseService
{
    protected Foo Foo { get; set; }
    protected Bar Bar { get; set; }

    public BaseService(Foo foo, Bar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}

public class Service : BaseService
{
    public Service(IOtherDependency otherDependency, Foo foo, Bar bar) : base(foo, bar) { }
}

The compiler wants to create the base class but needs the parameters to do so. Image the scenario if you have 2 constructors on your base class

public abstract class BaseService
{
    public BaseService(Foo foo)
    {
        ...
    }

    public BaseService(Bar bar)
    {
        ...
    }
}

In this scenario which base class constructor does the compiler call from the derived class? You need to be explicit with parameterized constructors so you need to pass them to any derived constructors as well.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Majid Shahabfar
Solution 3 Simply Ged