Dependency Injection in .NET with Microsoft.Extensions.DependencyInjection and Scrutor
Tags: webdev, dotnet, csharp, programming
Published: 2024-07-07 09:53:33

**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:

```sh
dotnet add package Microsoft.Extensions.DependencyInjection
```

### **2.2 Basic Example**

Suppose we have an `IMyService` interface and its implementation `MyService`.

```csharp
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.

```csharp
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.

```csharp
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:

```sh
dotnet add package Scrutor
```

### **3.2 Example with Scrutor**

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

```csharp
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.

```csharp
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](https://docs.microsoft.com/dotnet/core/extensions/dependency-injection) and the [Scrutor documentation](https://github.com/khellang/Scrutor).