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:
- Create a
ServiceCollection
and register the services. - Build a
ServiceProvider
. - 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.