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

Adrián Bailador - Jul 7 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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ú!");
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
);
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .