My take on Dependency Injection in ASP.NET Core

Application Settings – Configuration

Everything that you used to store in .net web.config is now stored in .net core appsettings.json content file. To manipulate, you will use microsoft.extensions.configuration.json package. Once it is loaded through the BuildWebHost(…) method defined in Program.cs, you will be able to use IConfiguration DI object for appsettings data reading.

Apart from reading settings as an indexer (_configuration[key]), there are various other ways you can read app settings and the most commonly used is reading by section.

// in appsettings.json
"ApiConfiguration": {
 "URL": ""
}


// in ApiConfiguration.cs 
public class ApiConfiguration {
 public string URL {
  get;
  set;
 }
}

// in Startup.cs, Inject under ConfigureService(...)
services.Configure <ApiConfiguration> (Configuration.GetSection("ApiConfiguration"));

// in apiproxy.cs, consume ApiConfiguration object 

public class ApiProxy {
 private readonly ApiConfiguration _apiConfiguration;
 public ApiProxy(IOption <ApiConfiguration> options) {
  this._apiConfiguration = options.Value;
 }
}

If you don’t like the IOption<> way injection, you can use a strongly typed configuration.

// declare class
public interface IClientApiConfiguration {
 string URL {
  get;
  set;
 }
}
// startup.cs
services.Configure <ApiConfiguration> (Configuration.GetSection("ApiConfiguration"));
services.TryAddSingleton <IClientApiConfiguration> (sp => sp.GetRequiredService <IOptions<ApiConfiguration>>().Value);


// ApiProxy.cs
public class ApiProxy {
	 private readonly IClientApiConfiguration _clientApiConfiguration;
	 public ApiProxy(IClientApiConfiguration clientApiConfiguration) {
		  this._clientApiConfiguration = clientApiConfiguration;
	 }
}

DI Service Lifetime

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. 

Dependency Injection Container also called inversion of control container is the part of Microsoft.Extensions.DependencyInjection namespace.There are two main services that we will be dealing with all the time:

  • Class IServiceCollection collects all DI registrations.
  • Class IServiceProvider resolves service instances

Main thing to understand about this DI is the service lifetime, which are of three types.

  • Transient – Transient objects are always different; a new instance is provided to every controller and every service. 
    • Objects are not requierd to be thread-safe
    • Potentionally less effective because it creates a new object, everytime it is resolved.
  • Singleton – Singleton objects are the same for every object and every request.
    • More performance improvement by not creating too many objects and reduce load on GC
    • Majority of middlerware constructor DI are singleton
    • It must be thread-safe
    • Suited for functional stateless services
  • Scoped – Scoped objects are the same within a request, but different across different requests.

Scope Validation

There are some rules on mixing different types of service lifetime. As an example – Singleton service should not store Transient service because it will lead the transient service being locked by singleton service.

  • transit service can depend on – transient, scoped singleton
  • scoped service can depend on – scoped singleton
  • singletone service can depend on – singleton

You can enable a .net core framework runtime validation code and please enable it only for debug mode, as it has some performance impact.

// In program.cs
 WebHost.CreateDefaultBuilder(args)
                .UseDefaultServiceProvider(options =>
                {
# if DEBUG
                    options.ValidateScopes = true;
#endif
                })

If you have multiple implementations of the same interface, and you have injected twice in that case by default you will get the last implementation that you have registered. However, if you want to receive all DI objects, you can use IEnumerable<> for constructor injection.

// startup.cs 
services.AddSingleton<INotification,EmailNotification>();
services.AddSingleton<INotification,SMSNotification>();
services.AddSingleton<INotification,NativeNotification>();
services.AddSingleton<INotification,PaperNotification>();
services.AddSingleton<INotifyService,NotifyService>();


// NotifyService.cs

public class NotifyService : INotifyService{

 public NotifyService(IEnumerable<INotification> notifications) {
  ...
 }

 public void Notify() {
  _notificationGateeway.Foreach().notify();
 }
}

Otherwise, if you don’t want to add multiple time, but at the same you don’t know if it has been added already then you can try with the following extension methods:

services.TryAdd<scope/transient/singleton>(...) or services.RemoveAll<interface>() or services.Replace<interface,implementation>();

Generic Registration

In order to register generic type, use this.

services.TryAddSingleton<IService<Payments>,Service<Payments>>();
services.TryAddSingleton<IService<Accounts>,Service<Accounts>>();
// Or
services.TryAddSingleton(typeof(IService<>), typeof(Service<T>));

Extension Methods

Because Startup.cs looks messy with so many injections, it is better you create custom extension methods, one for Services, one for Proxies, for dependancy injection and it is advised to use Microsoft.Extensions.DependencyInjection namespace.

namespace Microsoft.Extensions.DependencyInjection
{
    public static class ConfigurationServiceCollectionExtensions {
           public static IServiceCollection AddProxies(this IServiceCollection services, IConfiguration configuration)
          { ...
           return services;
          }
    }
}

Then call the same in startup.cs

services.AddProxies(_configuration).AddServices().AddProxiesHttpClients(_configuration);

Using Autofac

It is recommended by the Microsoft that you use their built-in DI. However, if you think it is too much of work for you to remove existing DI from your helix application, then you can mix .net core DI it with other DI such as Autofac, which is heavily used in helix projects.

In order to do that you basically have to do the following:

  • Install-Package Autofac.Extensions.DependencyInjection
  • Program.cs

public static IWebHostBuilder BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
          .ConfigureServices(services => services.AddAutofac())
                …

  • Startup.cs : This method will be called by the .net core automatically to set the service collection
public void ConfigureContainer(ContainerBuilder builder) { 
  builder.RegisterType<Service>().As<IService>().InstancePerLifetimeScope();
}

Injection types

Constructor Injection :

  • Assign default values for arguments that are not provided by the container.
  • when service are resolved a public constructor is required
  • Only one constructor should be there to resolve parameters
    • You cannot have like this

   public class Service {
    public Service (INotification notification, IParam param){}
    public Service (INotification notification){}
   }
   Otherwise, it will throw an exception like this: InvalidOperationException: Multiple constructors accepting all given argument types have been found in type &#x27; 27;. There should only be one applicable constructor.</div>

Action Injection

 Inject through controller using [FromServices] attribute in your action
  public void DoAction([FromServices] IConfiguration confugration ) {
  …
  }

Middleware Injection

  • Middleware components are constructed once, thus any dependency via the constructor should singleton only. Otherwise, validation scope will throw an exception. However, the factory middlewares are the exception.
  • Instead inject in InvokeAsync method where the scope is injected for every single request

  public async Task InvokeAsync(IConfiguration configuration ) {
  ..
  }

Transient or Scoped for Stateless Services

Microsoft states in their document that it is better to use Transient for Stateless services, such as REST API. However, it does not back this suggestion with any reasoning (Smith, Addie and Latham, 2019) .

Smith, S., Addie, S. and Latham, L. (2019). Dependency injection in ASP.NET Core. [online] Docs.microsoft.com. Available at: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2 [ Accessed 3 Jun. 2019].

Some useful packages

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection https://github.com/khellang/Scrutor

services.Scan(scan=>scan.FromAssemblyOf<IProxy>().AddClasses(c=> c.AssignableTo<IProxy>()).AsImplementedInterface().WithScopedLifetime());
 // or
 services.Scan(scan=>scan.FromAssemblyOf<IProxy>().AddClasses(c=> c.AssignableTo<IApiProxy>()).As<IProxy>().WithScopedLifetime());