Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 819
ibubi Avatar asked Oct 25 '18 06:10

ibubi


2 Answers

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.

like image 188
Jan Jurníček Avatar answered Oct 01 '22 08:10

Jan Jurníček


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();
    }
}
like image 33
ibubi Avatar answered Oct 01 '22 08:10

ibubi