Dependency Injection in Abstract or Base Class Constructor for .NET Core

While implementing base classes, intented to be inherited by other classes, it's better to think ahead and design things resilient to being easily broken by code changes. Using dependency injection in base or abstract class constructors with explicit service names can cause problems further down the road.


namespace MyBaseNamespace
{
    public abstract class SomeBase
    {
        private readonly ILogger<SomeBase> _logger;
        private readonly ISomeService _someService;

        public SomeBase(ISomeService someService, ILogger<SomeBase> logger)
        {
            _logger = logger;
            _someService = someService;
        }

        //more code
    }
}

namespace MyNamespace
{
    public class AwesomeClass : SomeBase

    public AwesomeClass(ISomeService someService, ILogger<SomeBase> logger):base(someService, logger)
    {
    }
}

Above code has a couple of problems. First, logs in base class methods with ILogger, will log under "MyBaseNamespace.SomeBase" even if being performed by parent class AwesomeClass because logger type is explicitly defined. In such case, logging under "MyNamespace.AwesomeClass" would be preferable for tracking. This can be achieved by using a non-generic ILogger in base class definition.

Second, anytime new services has to be injected in SomeBase due project necessities and SomeBase constructor changes , all classes inheriting SomeBase has to be modified. This can cause major headaches depending on project size and who uses that piece of code. Using aggregate service pattern is one way to deal with this problem. Another way, is to inject IServiceProvider into base class constructor and use IServiceProvider extension methods in "Microsoft.Extensions.DependencyInjection" package like shown below.


namespace MyBaseNamespace
{
    public abstract class SomeBase
    {
        private readonly ILogger _logger;
        private readonly ISomeService _someService;

        public SomeBase(IServiceProvider serviceProvider)
        {
            _logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(this.GetType().FullName);
            _someService = serviceProvider.GetService<ISomeService>();
        }

        //more code...
    }
}

namespace MyNamespace
{
    public class AwesomeClass : SomeBase

    public AwesomeClass(IServiceProvider serviceProvider):base(serviceProvider)
    {
    }
}

GetService<T> and GetRequiredService<T> are extension methods for IServiceProvider. GetRequiredService throws an exception if service is not configured in host builder, while GetService retuns null in that case. Depending on your desing, some services may be optional and may not be registered in host builder. In that case using GetService will avoid getting NullReferenceException at base class constructor. It's good practice to perform null check before using service instance acquired from GetService method, like so:


    if (_someService != null)
    {
        _someService.SomeMethod();
    }