.NET CORE DI

Integrating with .NET CORE dependency injection patterns.

.NET Core DI

Assumptions

Before we dive into the features of the .NET dependency injection it’s important to understand what dependency injection is.

  • Dependency injection is a pattern where classes can request their dependencies.
  • In .NET Core IHost is a recent abstraction.
    • The classic .NET WebHost was made generic allowing for other hosts such as OrleansHost.
    • In fact one can now think of IHost as the center of most modern .NET Core applications.
    • You might want to create your own builder/host in order to abstract configurations from external developers.

Key principals

  • IServiceCollection allows you to register a class against a given interface.
  • IServiceProvider provides you interfaces from IServiceCollection
  • Dependency injection originates from IHostBuilder providing a rich API for customizing your IHost.
    • Logging, SignalR, cache, DB to name a few.
  • The IHostBuilder maintains a private instance of IServiceCollection which is injected into the IHost.
  • IHost is responsible for injecting interfaces when requested by a class.
    • Examples of this are classes inheriting Controller in web APIs and Grain in Orleans clusters.

Although it’s worth noting that requesting interfaces directly from IServiceProvider is considered an anti-pattern, one reason is that constructor injection of interfaces is considered standard. More on that can be found here.

Lifetime

The .NET dependency injection provides allows you to register your classes with one of the following lifetimes:

  • Scoped
    • When your class is registered as a scoped class the same instance will be used for the entire request, afterwards it’s disposed of.
    • This is suitable when your class maintains a state that should be scoped to the request.
  • Singleton
    • When your class is registered as a singleton class the same instance will be used for all requests.
    • This is suitable when your class maintains a state that should be considered global.
  • Transient
    • When your class is registered as a transient class a new instance will always be used
    • This is suitable when your class maintains no state.

Register Class

When you are registering a class, you are registering an implementation that should occur each time a contract is invoked.

Example:

A common scenario is the ConfigureServices function provided by the web framework in .NET.

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyClass, MyClass>();    
}

Here we are registering MyClass against IMyClass, meaning each time IMyClass is invoked we’re using the implementation as defined in MyClass

Example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyClass, MyClass>();    
    services.AddTransient<IMyClass, MyClass2>();    
}

Here we are registering multiple classes against an interface.

Request Class

In .NET the standard pattern for requesting classes is through constructor injection.

Example:

public AClass(IMyClass myClass)
{
    this.myClass = myClass;
}

Here our class AClass has requested its dependency on IMyClass and assigned it to a local variable.

Note: When requesting a single dependency you will get the latest registered class, meaning MyClass2 if both MyClass and MyClass2 are both registered against IMyClass.

Example:

public AClass(IEnumerable<IMyClass> myClass)
{
    this.myClasses = myClasses;
}

Here our class AClass has requested its dependency on all implementations of IMyClass and assigned to a local variable.