Dependency injection

What is dependency injection? Dependency injection is a technique where services are injected through the constructor and interfaces are used over implementations when defining a class. It allows developers to follow the inversion of control guidelines and lossens the coupling between classes. Classes are always coupled to interfaces and not to implementations. This allows different implementations to be created and lossely couples the clasess. See link for more details.

.NET Core 5 web API

.NET Core 5 supports dependency injection by default and encourages its usage when building applications. When creating a standard web API application using .NET Core, a built-in dependency injection framework is scaffolded in the Startup.cs class. .NET Core has its own in built container which we can use and register services to. With small applications, the default container should suffice and there’s no need to have a third-party dependency injector library added into the solution.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddTransient<IRandomGeneratorService, RandomGeneratorService>();
}

With the above example, RandomGeneratorService has been registered against the interface IRandomGeneratorService. This will allow us to use IRandomGeneratorService and inject that into the constructor wherever we need to use methods from the interface, or the service that implements it. Having a container built into the .NET framework seems to be good, but it can cause issues when trying to use a different dependency injection container.

Using a different dependency injection container

One of the more popular dependency injection library is Simple Injector. This is the library I use on a day to day basis and it has quite a good amount of documentation. What are the benefits of Simple Injector over the standard .NET container? A few of the beneftis of Simple Injector that I can immediately think of are:

  • Diagnosis can be done using the container’s verify() method. Once registration is done, we can call a container.Verify() to ensure that the registrations int he applications make sense
  • Named services are possible which would allow us to inject specific services
  • Registration of multiple services is easy as we can register services in the assembly

There are more benefits of using Simple Injector than the above but these 3 reasons are more than enough for us to use Simple Injector over the default .NET Core default container.

So how do we add Simple Injector into the project?

Adding Simple Injector into the project

Adding Simple Injector into .NET Core is quite simple as .NET Core as we can add it in the Startup.cs file.

Container container = new SimpleInjector.Container();

services.AddSimpleInjector(container, options =>
{
    // AddAspNetCore() wraps web requests in a Simple Injector scope and
    // allows request-scoped framework services to be resolved.
    options.AddAspNetCore()

        // Ensure activation of a specific framework type to be created by
        // Simple Injector instead of the built-in configuration system.
        // All calls are optional. You can enable what you need. For instance,
        // ViewComponents, PageModels, and TagHelpers are not needed when you
        // build a Web API.
        .AddControllerActivation()
        .AddViewComponentActivation()
        .AddPageModelActivation()
        .AddTagHelperActivation();

    // Optionally, allow application components to depend on the non-generic
    // ILogger (Microsoft.Extensions.Logging) or IStringLocalizer
    // (Microsoft.Extensions.Localization) abstractions.
    options.AddLogging();
    options.AddLocalization();
});

// In the Configure() method we will need to use the simple injector 
app.UseSimpleInjector(container);

The above code does a few things. It first adds simple injector into the services collection. What this does in the background will allow Simple Injector to be the container used when services are injected in the application. It also sets a couple of options in the Simple Injector container such as the default AsyncScopedLifestyle() which is a scoped lifestyle per HTTP request that Simple Injector uses with web APIs. Auto cross wiring is then set up by doing the above.

A few things to note though:

Instead of registering services using the service collection, it is recommended to register using the container.

container.Register<IRandomGeneratorService, RandomGeneratorService>();

This should be done after the container has been added into the services collection above. We should always be registering services against the container, instead of the services collection. The services collection should only be used for .NET internal framework registrations. Anything else that’s application specific should be registered using the container. If a depedency is required from the framework, simple inejctor will look at the services collectiont o see if the dependency is available there. This is called cross-wiring in which simple injector finds a dependency in the services collection if it’s unable to resolve the services from its own registration.

Using dependency injection will help applications be more loosely coupled in development and make it easier for unit tests to be creaated. It’s a pattern that most developers should use and learn properly.