ASP.NET Core 3.1: Using Factories in the Dependency Injection System

There is a video version of this tutorial:

Since its beginnings, ASP.NET Core has come with a dependency injection system. With this system we can centralize the mechanism that provides the different dependencies of our classes in one place. Using dependency injection is a good practice that allows us to apply the dependency inversion principle to have flexible software.

In more concrete terms, when we use the dependency injection system we configure what class to serve when a particular service is requested. For example:


services.AddScoped<IFileStorageService, AzureStorageService>();

view raw

Startup.cs

hosted with ❤ by GitHub

Note: In this entry we are using AddScoped, however, everything we will learn applies to both AddTransient and AddSingleton.

The previous code means that when a class requests the IFileStorageService service, then an instance of the AzureStorageService class must be delivered. That is, if we have a class called PeopleController, which requests the IFileStorageService service through the constructor, then, at runtime, an instance of the AzureStorageService class will be served:


[ApiController]
[Route("api/[controller]")]
public class PeopleController : ControllerBase
{
private readonly IFileStorageService fileStorageService;
public PeopleController(IFileStorageService fileStorageService)
{
this.fileStorageService = fileStorageService;
}
// …
}

However, the way we are using the dependency injection system is not very flexible.

Suppose we have another class that implements the IFileStorageService interface and we need the following: When we are in a development environment, we want the InAppStorageService class to be served, and, when we are in a non-development environment, we want to use AzureStorageService. How can we add this flexibility? We can use a factory.

When we talk about factory we refer to a mechanism within your software which is responsible for instantiating classes and returning those instances.

The ASP.NET Core dependency injection system allows us to define our own factories in order to add custom logic when selecting the class we wish to serve when supplying a service.

If we look at the AddScoped overloads, we will see that one of them has the following signature:


public static IServiceCollection AddScoped<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;

As we can see, the previous overload indicates a parameter of type Func, which sends as a parameter an IServiceProvider, and returns a TService.

In Plain English, the above means that we can send a factory method which will return a class that implements our IFileStorageService interface. In addition, we can have a service provider for the case in which we need to use a service within our factory. This is exactly what we need.

Let’s see an implementation. Notice that we remove the type argument AzureStorageService, because now we have two implementations to use. In addition, we use the IServiceProvider to obtain an instance of IWebHostEnvironment, which is a service that helps us determine what environment we are in:


services.AddScoped<IFileStorageService>((serviceProvider) =>
{
var env = serviceProvider.GetRequiredService<IWebHostEnvironment>();
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
if (env.IsDevelopment())
{
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
return new InAppStorageService(env, httpContextAccessor);
}
else
{
return new AzureStorageService(configuration);
}
});

view raw

Startup.cs

hosted with ❤ by GitHub

In this way, one class or another will be used depending on the environment in which we are executing our application.

If you don’t like to be placing the factory directly in the AddScoped method, you can put it in a class. In my case I am going to use a static class:


public static class Factories
{
public static IFileStorageService FileStorageService(IServiceProvider serviceProvider)
{
var env = serviceProvider.GetRequiredService<IWebHostEnvironment>();
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
if (env.IsDevelopment())
{
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
return new InAppStorageService(env, httpContextAccessor);
}
else
{
return new AzureStorageService(configuration);
}
}
}

view raw

Factories.cs

hosted with ❤ by GitHub

Then, in the AddScoped method we can choose one of the following two options:


// Option 1
services.AddScoped(Factories.FileStorageService);
// Option 2
services.AddScoped<IFileStorageService>(Factories.FileStorageService);

view raw

Startup.cs

hosted with ❤ by GitHub

Both options do the same, though option 2 is explicit in regard to the service to be configured.

Summary

  • ASP.NET Core has a dependency injection system integrated
  • We can use factories to customize the logic of selecting service implementations

Regards!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s