Dependency Injection avancée

Découvrez les techniques avancées de l'injection de dépendances pour créer un code plus modulaire et testable. Apprenez à utiliser les containers DI et les design patterns pour améliorer la mainten...

Olivier Dupuy
21 juillet 2025

84

Vues

0

Commentaires

2

Min de lecture

La Dependency Injection (DI) est devenue un pilier fondamental du développement moderne en .NET. Si les concepts de base sont largement connus, les aspects avancés de cette technique méritent une attention particulière pour construire des applications robustes et maintenables. Dans cet article, nous explorerons les concepts avancés de la DI et leur mise en œuvre pratique dans l'écosystème .NET moderne.

Concepts théoriques avancés

Au-delà de l'injection de dépendances basique, plusieurs concepts avancés méritent notre attention :

  • Durée de vie des services (Scoped, Singleton, Transient)
  • Injection de dépendances conditionnelles
  • Décorateurs et chaînes de responsabilité
  • Factory patterns avec DI

Implémentation des durées de vie avancées


public interface IService { }

// Service de base public class MyService : IService { }

// Configuration dans Program.cs services.AddSingleton(); // Une seule instance pour toute l'application services.AddScoped(); // Une instance par scope (ex: par requête HTTP) services.AddTransient(); // Nouvelle instance à chaque injection

Injection conditionnelle

L'injection conditionnelle permet de choisir dynamiquement quelle implémentation utiliser selon le contexte :


public class ServiceResolver
{
    private readonly IServiceProvider _serviceProvider;
    
    public ServiceResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

public IService ResolveService(ServiceType type) { return type switch { ServiceType.Production => _serviceProvider.GetService(), ServiceType.Development => _serviceProvider.GetService(), _ => throw new ArgumentException("Invalid service type") }; } }

Patterns avancés de DI

Decorator Pattern avec DI


public interface INotificationService
{
    Task SendNotificationAsync(string message);
}

public class LoggingDecorator : INotificationService { private readonly INotificationService _inner; private readonly ILogger _logger;

public LoggingDecorator( INotificationService inner, ILogger logger) { _inner = inner; _logger = logger; }

public async Task SendNotificationAsync(string message) { _logger.LogInformation("Sending notification: {Message}", message); await _inner.SendNotificationAsync(message); _logger.LogInformation("Notification sent successfully"); } }

Gestion des erreurs et performance

La gestion appropriée des erreurs dans un système utilisant DI est cruciale :


public class ServiceCollection
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.TryAddSingleton();
        
        // Gestion des erreurs avec circuit breaker
        services.AddTransient(sp =>
        {
            try
            {
                var service = sp.GetRequiredService();
                return new CircuitBreakerDecorator(service);
            }
            catch (Exception ex)
            {
                // Fallback service
                return new FallbackService();
            }
        });
    }
}

Tests unitaires avec DI

Les tests unitaires doivent prendre en compte la DI :


public class NotificationServiceTests
{
    [Fact]
    public async Task SendNotification_ShouldLogAndSendMessage()
    {
        // Arrange
        var mockLogger = new Mock>();
        var mockInnerService = new Mock();
        var decorator = new LoggingDecorator(
            mockInnerService.Object,
            mockLogger.Object);

// Act await decorator.SendNotificationAsync("Test message");

// Assert mockInnerService.Verify( x => x.SendNotificationAsync("Test message"), Times.Once); mockLogger.Verify( x => x.LogInformation(It.IsAny(), "Test message"), Times.Once); } }

Bonnes pratiques

  • Éviter la Service Locator Pattern en faveur de l'injection de constructeur
  • Utiliser des interfaces pour découpler les composants
  • Respecter le principe de responsabilité unique
  • Documenter les durées de vie des services
  • Éviter les dépendances circulaires

Cas d'usage réels

Voici un exemple complet d'une architecture utilisant la DI avancée :


public interface IUserService
{
    Task GetUserAsync(int id);
}

public class CachingUserService : IUserService { private readonly IUserService _inner; private readonly ICache _cache; private readonly ILogger _logger;

public CachingUserService( IUserService inner, ICache cache, ILogger logger) { _inner = inner; _cache = cache; _logger = logger; }

public async Task GetUserAsync(int id) { var cacheKey = $"user_{id}"; if (_cache.TryGet(cacheKey, out User user)) { _logger.LogInformation("Cache hit for user {Id}", id); return user; }

user = await _inner.GetUserAsync(id); await _cache.SetAsync(cacheKey, user); return user; } }

// Configuration services.AddScoped(); services.Decorate();

Conclusion

La maîtrise des concepts avancés de la Dependency Injection permet de construire des applications plus modulaires, testables et maintenables. Les patterns et pratiques présentés ici constituent une base solide pour implémenter la DI de manière efficace dans vos projets .NET. N'oubliez pas que la DI n'est qu'un outil parmi d'autres et doit être utilisée judicieusement en fonction des besoins spécifiques de votre application.

Partager cet article
42
12

Commentaires (0)

Rejoignez la discussion

Connectez-vous pour partager votre avis et échanger avec la communauté

Première discussion

Soyez le premier à partager votre avis sur cet article !

À propos de l'auteur
Olivier Dupuy

Développeur passionné et créateur de contenu technique. Expert en développement web moderne avec ASP.NET Core, JavaScript, et technologies cloud.

Profil
Articles similaires
API versioning strategies
02 août 2025 0
C# & .NET
Cryptographie post-quantique
02 août 2025 0
C# & .NET
Géolocalisation et cartes interactives
02 août 2025 0
C# & .NET
Navigation rapide
Commentaires (0)