Archives 04/03/2024

Registering our dependencies – Dependency Injection

In ASP.NET Core, we register our dependencies in the Program.cs file, which represents the composition root. Since the minimal hosting model, the WebApplicationBuilder exposes the Services property we use to add our dependencies to the container. Afterward, .NET creates the container when it builds the WebApplication instance.Next is a minimal Program.cs file depicting this concept:

var builder = WebApplication.CreateBuilder(args);
// Register dependencies
var app = builder.Build();
// The IoC container is now available
app.Run();

Then, we use the builder.Services property to register our dependencies in that IServiceCollection implementation. Here’s an example of registering some dependencies:

builder.Services.AddSingleton<Dependency1>();
builder.Services.AddSingleton<Dependency2>();
builder.Services.AddSingleton<Dependency3>();

The preceding code registers the dependencies using the singleton lifetime, so we get the same instance each time we request one.

Remember to compose the program in the composition root. That removes the need for those pesky new keywords spread around your code base and all the tight coupling that come with them. Moreover, it centralizes the application’s composition into that location, creating the plan to assemble the LEGO® blocks.

As you may be thinking right now, that can lead to a lot of registration statements in a single location, and you are correct; maintaining such a composition root would be a challenge in almost any application. To address this concern, we introduce an elegant way to encapsulate the registration code next, ensuring it remains manageable.

Registering your features elegantly

As we’ve just discovered, while we should register dependencies in the composition root, we can also arrange our registration code in a structured manner. For example, we can break down our application’s composition into several methods or classes and invoke them from our composition root. Another strategy could be to use an auto-discovery system to automate the registration of certain services.

The critical part is to centralize the program composition in one place.

A common pattern in ASP.NET Core is having special methods like Add[Feature name]. These methods register their dependencies, letting us add a group of dependencies with just one method call. This pattern is convenient for breaking down program composition into smaller, easier-to-handle parts, like individual features. This also makes the composition root more readable.

A feature is the correct size as long as it stays cohesive. If your feature becomes too big, does too many things, or starts to share dependencies with other features, it may be time for a redesign before losing control over it. That’s usually a good indicator of undesired coupling.

To implement this pattern, we use extension methods, making it trivial. Here’s a guide:

  1. Create a static class named [subject]Extensions in the Microsoft.Extensions.DependencyInjection namespace.
  2. Create an extension method that returns the IServiceCollection interface, which allows method calls to be chained.

According to Microsoft’s recommendation, we should create the class in the same namespace as the element we extend. In our case, the IServiceCollection interface lives in the Microsoft.Extensions.DependencyInjection namespace.

Of course, this is not mandatory, and we can adapt this process to our needs. For example, we can define the class in another namespace if we want consumers to add a using statement implicitly. We can also return something else when the registration process can continue beyond that first method, like a builder interface.

Builder interfaces are used to configure more complex features, like ASP.NET Core MVC. For example, the AddControllers extension method returns an IMvcBuilder interface that contains a PartManager property. Moreover, some extension methods target the IMvcBuilder interface, allowing further configuration of the feature by requiring its registration first; that is, you can’t configure IMvcBuilder before calling AddControllers. You can also design your features to leverage that pattern when needed.

Let’s explore a demo.