avatarSasha Mathews

Summary

This context discusses advanced dependency injection techniques in ASP.NET Core, including implementing DI Factory, registering a service with multiple interfaces, and creating scopes manually.

Abstract

The context explores several advanced ASP.NET Core dependency injection techniques that go beyond the standard DI usage patterns. It begins by discussing the implementation of DI Factory, which allows for changing the implementation of an interface at runtime based on configuration values, user input, or other variables. The text then covers registering a service with multiple interfaces, ensuring that only one instance of the service is created. Lastly, it explains how to create scopes manually, which is useful for executing startup logic synchronously in the Program.cs, disposing of scoped services on demand, and solving the Captive Dependencies problem.

Bullet points

  • Implementing DI Factory allows for changing the implementation of an interface at runtime based on configuration values, user input, or other variables.
  • Registering a service with multiple interfaces should be done carefully to avoid creating redundant objects.
  • ASP.NET Core manages scopes creation and deletion, but there are cases when developers should create and manage scopes manually.
  • Manually creating scopes is useful for executing startup logic synchronously in the Program.cs, disposing of scoped services on demand, and solving the Captive Dependencies problem.

Advanced Dependency Injection Techniques in ASP.NET Core

Photo by Christopher Gower on Unsplash

In this post, we’re going to explore several ASP.NET Core dependency injection techniques that are present in many complex projects but go beyond the standard DI usage patterns.

Implementing DI Factory

Sometimes it is necessary to change the implementation of an interface at runtime according to some configuration value, user input, or other variables to change the behavior of the application.

For instance, there might be a need to choose between old and new functionality based on the value of the feature flag. The very first idea of fulfilling this requirement would be to simply add a condition to the ConfigureServices method like that:

However, the demonstrated solution has certain limitations. The ConfigureServices method runs only once when the application starts. This means that changing the value of the FeatureEnabled flag will not matter until the application is restarted, which is not very flexible.

Most often, it becomes necessary to switch between implementations of the interface between web requests, or even multiple times within a single web request. This can be achieved with a factory method delegate that can be registered with the DI container.

Each consumer that requires to get an instance of the IImplementation type needs to inject the ImplementationFactory delegate and then resolve the required IImplementation instance.

Registering a Service with Multiple Interfaces

Some classes can implement multiple interfaces to conform to the Interface Segregation principle. Here’s an example:

Of course, both interfaces must be registered with the DI container. Let’s do it in the most obvious way:

However, the problem here is that two instances of the Storage object will be instantiated, which should be the case for the singleton object.

Here is the correct way to register a service with multiple interfaces and avoid lifetime issues:

On line 3, we register the Storage instance as a singleton. On lines 4 and 5, we request DI container just to return a Storage instance for the IReadStorage and IWriteStorageinterfaces.

Creating Scopes Manually

The usual usage of the Dependency Injection container is pretty straightforward. The developer should register some interface in the ConfigureServices method or extension methods and then inject that interface into constructors.

//Register
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IRepository, Repository>();
}
//Consume
public class OrderService
{
    public OrderService(IRepository repository)
    {
    }
}

ASP.NET Core will handle scope creation and deletion itself.

However, during development, this simple “register-inject” pattern may not always be sufficient. There are several cases when injecting the service into the constructor will not help you achieve your goal or is not possible at all, so there will be a need to work with the scopes and IServiceProvider instance directly.

Accessing Services in the Program.cs

It is often necessary to synchronously execute some logic when starting an application, such as initializing the database, populating the cache, etc. One good place to run startup logic synchronously is in the Program.cs class right after the host object is created.

The first step would be to register the class that encapsulates the startup logic into the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IStartupLogic, StartupLogic>();
}

However, it’s not possible to inject an IStartupLogic instance into the constructor of the Program class. Thus, the correct way to resolve the IStartupLogic type would be to use the IServiceProvider instance available on the host object.

The ConfigureServices method runs before the host is built, so we are allowed resolving IStartupLogic service from the IServiceProvider after line 5.

Disposing Scoped Services on Demand

Scoped services are disposed automatically at the end of the web request. However, there might be a need to create, use, and then necessarily dispose a scoped service multiple times in a row within a single web request. This might be achieved by manually creating a scope and dispose the scope when it’s no longer needed.

Disposing a scope ensures that all services resolved in that scope are disposed immediately that might be important for some memory intensive scenarios.

Solving the Captive Dependencies Problem

The last common way where manually creating a scope is needed to be related to solving the Captive Dependency problem. I’ve already covered the Captive Dependency issue in my post other posts here and here.

Summary

  • To switch the implementation of an interface at runtime, it’s necessary to implement a factory method and register it with the DI container.
  • Developers should be careful when registering the class with multiple interfaces to avoid creating redundant objects.
  • ASP.NET Core manages scopes creation and deletion by itself, but there are cases when developer should deal with scope and IServiceProvider on his own like resolving services from Program.cs, disposing services frequently and avoiding captive dependency issue.

Thanks for reading. If you liked what you read, check out this story below:

☕☕ Also, consider supporting me on Buy Me a Coffee.

Programming
Software Engineering
Software Development
Csharp
Aspnetcore
Recommended from ReadMedium