Dependency Injection in .NET with Microsoft.Extensions.DependencyInjection and Scrutor

Dependency Injection (DI) is a crucial design pattern in modern software development that enables the construction of more flexible, maintainable, and testable applications. In the .NET ecosystem, Microsoft.Extensions.DependencyInjection is the official implementation of this pattern, while Scrutor extends its capabilities, further facilitating service registration. In this article, we will explore both topics with practical examples.

1. What is Dependency Injection?

Dependency Injection (DI) is a technique that promotes the separation of concerns and facilitates the management of dependencies between application components. With DI, a component does not create its dependencies directly but receives them from a dependency injection container.

Advantages of DI:

  • Maintainability: Eases the change and update of dependencies.
  • Testability: Simplifies the creation of unit tests using mocks and stubs.
  • Flexibility: Allows for implementation changes without modifying the consumers.

2. Microsoft.Extensions.DependencyInjection

Microsoft.Extensions.DependencyInjection is the DI tool provided by Microsoft for .NET, included by default in .NET Core and .NET 5+.

2.1 Basic Configuration

To start, add the Microsoft.Extensions.DependencyInjection package if it is not already included in your project:

dotnet add package Microsoft.Extensions.DependencyInjection

2.2 Basic Example

Suppose we have an IMyService interface and its implementation MyService.

public interface IMyService
{
    void DoWork();
}

public class MyService : IMyService
{
    public void DoWork()
    {
        Console.WriteLine("Work done, Codú!");
    }
}

To configure DI:

  1. Create a ServiceCollection and register the services.
  2. Build a ServiceProvider.
  3. Use the ServiceProvider to resolve and utilise the dependencies.
using Microsoft.Extensions.DependencyInjection;
using System;

class Program
{
    static void Main(string[] args)
    {
        // Create a ServiceCollection
        var serviceCollection = new ServiceCollection();

        // Register services
        serviceCollection.AddTransient<IMyService, MyService>();

        // Build the ServiceProvider
        var serviceProvider = serviceCollection.BuildServiceProvider();

        // Resolve and use the service
        var myService = serviceProvider.GetService<IMyService>();
        myService.DoWork();
    }
}

2.3 Service Lifetimes

When registering services, you can specify different lifetimes:

  • Transient: A new instance is created each time the service is requested.
  • Scoped: A new instance is created per scope, useful in web applications where each HTTP request can have its own scope.
  • Singleton: A single instance is created and shared throughout the application.
serviceCollection.AddTransient<IMyService, MyService>();  // Transient
serviceCollection.AddScoped<IMyService, MyService>();     // Scoped
serviceCollection.AddSingleton<IMyService, MyService>();  // Singleton

3. Scrutor

Scrutor is a library that extends Microsoft.Extensions.DependencyInjection and facilitates the automatic registration of services.

3.1 Installing Scrutor

Add the Scrutor package using NuGet:

dotnet add package Scrutor

3.2 Example with Scrutor

Suppose we have several services and want to register them automatically. Scrutor makes this easy.

using Microsoft.Extensions.DependencyInjection;
using Scrutor;
using System;

public interface IServiceA
{
    void DoA();
}

public class ServiceA : IServiceA
{
    public void DoA()
    {
        Console.WriteLine("ServiceA executing DoA");
    }
}

public interface IServiceB
{
    void DoB();
}

public class ServiceB : IServiceB
{
    public void DoB()
    {
        Console.WriteLine("ServiceB executing DoB");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Create a ServiceCollection
        var serviceCollection = new ServiceCollection();

        // Register services automatically
        serviceCollection.Scan(scan => scan
            .FromAssemblyOf<IServiceA>()
            .AddClasses(classes => classes.AssignableTo<IServiceA>())
                .AsImplementedInterfaces()
                .WithTransientLifetime()
            .AddClasses(classes => classes.AssignableTo<IServiceB>())
                .AsImplementedInterfaces()
                .WithTransientLifetime()
        );

        // Build the ServiceProvider
        var serviceProvider = serviceCollection.BuildServiceProvider();

        // Resolve and use the services
        var serviceA = serviceProvider.GetService<IServiceA>();
        serviceA.DoA();

        var serviceB = serviceProvider.GetService<IServiceB>();
        serviceB.DoB();
    }
}

3.3 Conditional Registration

Scrutor allows for more complex rules when registering services.

serviceCollection.Scan(scan => scan
    .FromAssemblyOf<IServiceA>()
    .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Service")))
        .AsImplementedInterfaces()
        .WithScopedLifetime()
);

In this example, only classes whose names end with "Service" will be registered and will have a scoped lifetime.

Conclusion

Dependency injection is a powerful technique that, when used correctly, can improve the structure and maintainability of .NET applications. Microsoft.Extensions.DependencyInjection provides a solid foundation for DI, while Scrutor adds an extra layer of flexibility and convenience, allowing for automatic and conditional registration of services.

For more information, check out the official Microsoft documentation and the Scrutor documentation.

TipsWeb DevelopmentDependency InjectionDotnetCsharp
Avatar for Adrián Bailador

Written by Adrián Bailador

🚀 Full-Stack Dev 👨🏻‍💻 .NET Engineer 👾 Geek & Friki 💡 Talks about #dotnet, #csharp, #azure, #visualstudio and a little bit of #nextjs.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.