asp.net core constructor injection with inheritance

15,650

Solution 1

This is what I was looking for.


I modified my code for my controller base as indicated above post.

For my service side which I was asking in my question; as they do not make use of HttpContext like built-in Controller base class, I only inject IServiceProvider class to my BaseService, so whatever I need in all my services, I get it to property via provider.GetService().

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

    public BaseService(IServiceProvider provider)
    {
        Foo = provider.GetService<Foo>();
        Bar = provider.GetService<Bar>();
    }
}
public class Service : BaseService, IService
{
    public Service(IOtherDependency otherDependency, IServiceProvider provider) : base(provider) { }

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

public class SomeController : BaseController
{
    private readonly IService _service;

    public SomeController(IService service)
    {
        _service = service;
    }

    public IActionResult Index()
    {
        //call method
        _service.Method();
    }
}

Solution 2

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
    {
        public IFoo Foo { get; private set; }
        public 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 sealed class Dependencies
    {
        public IOtherDependency OtherDependency { get; private set; }
        public 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 3

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 4

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.

Share:
15,650

Related videos on Youtube

ibubi
Author by

ibubi

Updated on October 20, 2022

Comments

  • ibubi
    ibubi over 1 year

    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.

  • ibubi
    ibubi over 5 years
    The above code is that what I have done so far. Actually I am asking that; - Hey derived service! You do not pass any parameter to the base service, because base service will create via dependency injection and pass them to its properties for you. So, you can offer another class structure or ways to achieve this.
  • Simply Ged
    Simply Ged over 5 years
    Sorry. It cannot be done - and it is bad practice to do so because you are hiding dependencies. See this answer for a good description of why property injection is not done.
  • Amir
    Amir over 4 years
    How will you invoke class Service as it requires parameter for its constructor?
  • ibubi
    ibubi over 4 years
    @Amir I do not directly invoke Services in controllers, they are called via dependency injection, with interface convention.
  • Amir
    Amir over 4 years
    So could you also provide code for binding. How you are binding your class and interface
  • Nexus
    Nexus almost 4 years
    This works well, thank you. But I'm curious if this is an anti-pattern. My understanding is that this resembles the Service Locator anti-pattern. If so, why is it an anti-pattern and what can we do about it to make it better? How I'm using it is, I have an abstract base class called BaseVerifier and numerous other classes implement this base class and provide their own business logic. They all need access to several services through DI, for example the AppDbContext and UserManager<T>. I don't want to pass these arguments all over the place, so I resolve them in the base class.
  • Renil Babu
    Renil Babu over 3 years
    @SimplyGed . You are right. Classes that support DI cannot be used as general classes and we cannot use much of the Design patterns. The problem with the above code is that it has coupling with the base class which makes it hard to maintain and scale.
  • Quido
    Quido about 3 years
    @Nexus I agree with you: though this solution works (and is the best I have right now) it itches... This solution exposes that is has a dependency (to the IServiceProvider) but hides the dependencies to Foo and Bar. Adding new dependencies to BaseService will definitely cause some serious debugging for any consumers of this class that does not have the source code (or is in another part of the enterprise). I think I'll solve my problem with the (meta) design pattern: Prefer composition over inheritance.
  • mkb
    mkb about 3 years
    nice, but I think a sealed Dependencies class in every derived class is too much. Just having one for BaseClass is enough for me. And now I realized this is actually what I always do in javascript to manage method parameters. wp
  • Jan Jurníček
    Jan Jurníček about 3 years
    If the Services class is not marked as sealed, the situation may recur: The same or another developer may derive another class from it in the constructor of the derived class and will call the constructor of the Service class explicitly. Then, if someone finds that part of the functionality of the Service class should be outsourced to a dependency and changes the signature of its constructor, a problem occurs. In my opinion, the signatures of the class constructors should be standardized, unless the classes are sealed or very simple.
  • al.koval
    al.koval over 2 years
    for generic method: using Microsoft.Extensions.DependencyInjection;