mercredi 8 octobre 2025

Q/A Injection de dépendances



🔹 1. Qu'est-ce que l’injection de dépendances (Dependency Injection) et pourquoi est-elle utilisée ?

✅ Définition :

L’injection de dépendances (DI) est un design pattern qui consiste à fournir les dépendances d’un objet depuis l’extérieur, plutôt que de les créer à l’intérieur.

Autrement dit, au lieu de :

public class OrderService
{
    private EmailService _emailService = new EmailService(); // dépendance directe ❌
}

on écrit :

public class OrderService
{
    private readonly IEmailService _emailService;
    public OrderService(IEmailService emailService) // injection par constructeur ✅
    {
        _emailService = emailService;
    }
}

✅ Objectifs :

  • Découpler les composants (principe SOLID : D = Dependency Inversion).

  • Faciliter les tests unitaires (mocking, substitution).

  • Simplifier la maintenance et l’évolution du code.

  • Favoriser la réutilisation et la configuration centralisée des dépendances.

🧠 En résumé :

La DI permet d’écrire un code modulaire, testable et extensible, en confiant la création des objets à un conteneur IoC.


🔹 2. Quelle est la différence entre Service Locator et Dependency Injection ?

Critère Service Locator Dependency Injection
Principe L’objet va chercher ses dépendances dans un conteneur global Les dépendances sont fournies automatiquement à l’objet
Couplage Couplage fort au conteneur (anti-pattern) Couplage faible, respecte l’inversion de dépendances
Lisibilité Masque les dépendances réelles Les dépendances sont explicites
Testabilité Difficile à tester Facile à mocker et injecter

Exemple Service Locator (❌ à éviter) :

var emailService = ServiceLocator.GetService<IEmailService>();

Exemple Dependency Injection (✅) :

public class OrderService
{
    public OrderService(IEmailService emailService) { ... }
}

🧠 En résumé :

Le Service Locator cache les dépendances → difficile à maintenir.
La Dependency Injection les rend explicites → claire et testable.


🔹 3. Quels sont les trois types d’injection de dépendances ?

Type d’injection Description Exemple
Constructeur Dépendances fournies via le constructeur (le plus courant) public MyClass(IMailService mailService)
Propriété Dépendances assignées après création via des propriétés publiques public IMailService MailService { get; set; }
Méthode Dépendances passées en paramètre d’une méthode spécifique void Initialize(IMailService mailService)

Exemple global :

public class NotificationService
{
    // 1️⃣ Constructeur
    public NotificationService(IEmailService emailService) { ... }

    // 2️⃣ Propriété
    public ISmsService SmsService { get; set; }

    // 3️⃣ Méthode
    public void Init(ILogService logService) { ... }
}

🧠 Bonne pratique :

L’injection par constructeur est la plus recommandée car elle garantit que toutes les dépendances sont fournies dès la création de l’objet.


🔹 4. Comment configurer l'injection de dépendances dans ASP.NET Core ?

ASP.NET Core intègre un conteneur IoC natif via IServiceCollection et IServiceProvider.

Étapes :

  1. Déclarer une interface et son implémentation :

    public interface IEmailService
    {
        void SendEmail(string to, string message);
    }
    
    public class EmailService : IEmailService
    {
        public void SendEmail(string to, string message) => Console.WriteLine($"Email sent to {to}");
    }
    
  2. Enregistrer le service dans Program.cs ou Startup.cs :

    builder.Services.AddScoped<IEmailService, EmailService>();
    
  3. L’injecter dans un contrôleur ou un service :

    public class OrderController : Controller
    {
        private readonly IEmailService _emailService;
    
        public OrderController(IEmailService emailService)
        {
            _emailService = emailService;
        }
    
        public IActionResult Confirm()
        {
            _emailService.SendEmail("client@mail.com", "Order confirmed!");
            return Ok();
        }
    }
    

🧠 Résumé :

L’enregistrement se fait dans le IServiceCollection, et l’injection se fait automatiquement par le framework.


🔹 5. Quelle est la différence entre les portées de services : Transient, Scoped et Singleton ?

Portée Durée de vie Description Exemple
Transient À chaque injection Nouvelle instance à chaque appel AddTransient<IService, Impl>()
Scoped Par requête HTTP (ASP.NET) Même instance pour toute la requête AddScoped<IService, Impl>()
Singleton Unique pour toute l’application Instance partagée et persistante AddSingleton<IService, Impl>()

Exemple pratique :

builder.Services.AddTransient<IEmailService, EmailService>(); // Nouveau à chaque injection
builder.Services.AddScoped<IUserService, UserService>();      // 1 par requête
builder.Services.AddSingleton<ILogService, LogService>();     // Unique globale

🧠 Bonnes pratiques :

  • Transient → objets légers, stateless.

  • Scoped → services dépendant du contexte de requête (EF DbContext).

  • Singleton → services partagés, thread-safe (cache, logger).


🔹 6. Comment utiliser IServiceCollection pour enregistrer des dépendances ?

IServiceCollection est l’interface utilisée pour configurer le conteneur IoC dans .NET Core.

Exemple d’enregistrement :

public void ConfigureServices(IServiceCollection services)
{
    // Enregistre un service simple
    services.AddScoped<IEmailService, EmailService>();

    // Enregistre un service avec fabrique personnalisée
    services.AddSingleton<IMyService>(provider => new MyService("param"));

    // Enregistre une instance spécifique
    var settings = new AppSettings { Timeout = 30 };
    services.AddSingleton(settings);
}

Résolution manuelle :

var provider = services.BuildServiceProvider();
var emailService = provider.GetRequiredService<IEmailService>();

🧠 Résumé :

IServiceCollection permet d’enregistrer les dépendances, et IServiceProvider les résout à l’exécution.


🔹 7. Qu'est-ce que l’inversion de contrôle (IoC) ?

✅ Définition :

L’Inversion de Contrôle (Inversion of Control) est un principe architectural où le contrôle du flux d’exécution est inversé :
ce n’est plus votre code qui crée et gère les dépendances, mais le framework ou conteneur IoC.

Exemple sans IoC :

var emailService = new EmailService();
var orderService = new OrderService(emailService);

Exemple avec IoC :

public class OrderService
{
    private readonly IEmailService _emailService;
    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

➡️ Le conteneur IoC s’occupe de fournir l’instance EmailService.

🧠 Résumé :

IoC = principe, DI = son implémentation concrète.
Le conteneur gère le cycle de vie, la création et la destruction des objets.


🔹 8. Exemple complet d’injection de dépendances avec Microsoft.Extensions.DependencyInjection

✅ Étape 1 : Ajouter le package (si projet console)

dotnet add package Microsoft.Extensions.DependencyInjection

✅ Étape 2 : Créer les interfaces et implémentations

public interface IMessageService
{
    void Send(string message);
}

public class EmailMessageService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine($"Email envoyé : {message}");
    }
}

✅ Étape 3 : Configurer le conteneur

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddTransient<IMessageService, EmailMessageService>();

var provider = services.BuildServiceProvider();

✅ Étape 4 : Résoudre la dépendance

var msgService = provider.GetRequiredService<IMessageService>();
msgService.Send("Bonjour DI !");

🧠 Résumé :

Microsoft.Extensions.DependencyInjection fournit le même moteur d’injection que celui d’ASP.NET Core, utilisable dans toute application .NET (console, WPF, etc.).


📘 Récapitulatif synthétique

Concept Description
DI Fournit les dépendances depuis l’extérieur
Service Locator Anti-pattern qui cache les dépendances
Types d’injection Constructeur, Propriété, Méthode
ASP.NET Core DI Configurée via IServiceCollection
Durée de vie Transient, Scoped, Singleton
IoC Principe d’inversion de création et contrôle
Microsoft.Extensions.DependencyInjection Framework officiel pour gérer DI dans .NET




🔹 1. Qu'est-ce que l’Open-Closed Principle (OCP) et comment l’appliquer en C# ?

✅ Définition :

OCP = un principe SOLID qui dit :

“Les classes doivent être ouvertes à l’extension mais fermées à la modification.”

✅ Application en C# :

  • On ne modifie pas une classe existante pour ajouter un comportement.

  • On étend le comportement via :

    • Interfaces

    • Classes abstraites

    • Polymorphisme

🧩 Exemple :

public interface IDiscount
{
    decimal Apply(decimal amount);
}

public class SeasonalDiscount : IDiscount
{
    public decimal Apply(decimal amount) => amount * 0.9m;
}

public class BlackFridayDiscount : IDiscount
{
    public decimal Apply(decimal amount) => amount * 0.7m;
}

public class Order
{
    private readonly IDiscount _discount;
    public Order(IDiscount discount) => _discount = discount;
    public decimal FinalAmount(decimal amount) => _discount.Apply(amount);
}

✅ Pour ajouter un nouveau type de réduction, il suffit de créer une nouvelle classe implémentant IDiscount, sans toucher à Order.


🔹 2. Comment implémenter un pattern Factory avec l’injection de dépendances ?

✅ Objectif :

Créer des objets dynamquement tout en utilisant la DI pour injecter leurs dépendances.

🧩 Exemple :

public interface IShape { void Draw(); }
public class Circle : IShape { public void Draw() => Console.WriteLine("Circle"); }
public class Square : IShape { public void Draw() => Console.WriteLine("Square"); }

public class ShapeFactory
{
    private readonly IServiceProvider _provider;
    public ShapeFactory(IServiceProvider provider) => _provider = provider;

    public IShape Create(string shapeType) =>
        shapeType switch
        {
            "Circle" => _provider.GetRequiredService<Circle>(),
            "Square" => _provider.GetRequiredService<Square>(),
            _ => throw new ArgumentException()
        };
}

// Registration
services.AddTransient<Circle>();
services.AddTransient<Square>();
services.AddSingleton<ShapeFactory>();

✅ Avantage : on peut créer des objets avec leurs dépendances résolues automatiquement.


🔹 3. Différence entre Activator.CreateInstance() et new T()

Méthode Description Usage
new T() Compilation-time, type connu Direct, performant
Activator.CreateInstance<T>() Runtime, type dynamique Création via reflection, moins performant

🧩 Exemple :

var obj1 = new MyClass(); // Compile-time
var obj2 = Activator.CreateInstance(typeof(MyClass)); // Runtime

🧠 Résumé :

Activator = utile pour plugins, assemblies dynamiques, mais plus lent.


🔹 4. Pourquoi et comment utiliser Options Pattern dans ASP.NET Core ?

✅ Objectif :

Gérer la configuration fortement typée (appsettings.json) avec DI.

🧩 Exemple :

public class MySettings { public string ApiKey { get; set; } }

// Registration
services.Configure<MySettings>(Configuration.GetSection("MySettings"));

// Injection
public class MyService
{
    private readonly MySettings _settings;
    public MyService(IOptions<MySettings> options) => _settings = options.Value;
}

✅ Avantages : centralisation, validation, injectabilité et immutabilité.


🔹 5. Qu’est-ce qu’un Service Provider en C# et comment l’utiliser ?

✅ Définition :

IServiceProvider = contexte DI capable de résoudre les services enregistrés.

🧩 Exemple :

var provider = services.BuildServiceProvider();
var myService = provider.GetService<MyService>();
  • Méthodes : GetService<T>(), GetRequiredService<T>().

  • Sert à résoudre dynamiquement des dépendances, par ex. dans une factory.


🔹 6. Comment gérer les dépendances circulaires dans une application avec DI ?

✅ Problème :

A → B → A → … (boucle infinie)

✅ Solutions :

  1. Refactorer pour supprimer la circularité (meilleure pratique)

  2. Injecter via Func<T> ou Lazy<T> :

public class A { public A(Func<B> bFactory) { var b = bFactory(); } }
  1. Injection de propriété ou méthode plutôt que constructeur.

✅ L’idée : retarder la résolution pour casser le cycle.


🔹 7. Différence entre Func et IServiceProvider.GetService()

Concept Func IServiceProvider.GetService()
Résolution Retardée / lazy Résolution immédiate
Réutilisation Peut être appelée plusieurs fois Obtenu une seule fois
DI integration Injectable Usage ponctuel ou factory

🧩 Exemple :

public class Foo
{
    private readonly Func<Bar> _barFactory;
    public Foo(Func<Bar> barFactory) => _barFactory = barFactory;
}

🔹 8. Pourquoi HttpClient doit-il être réutilisé et non instancié à chaque requête ?

  • HttpClient crée un socket par instance.

  • Si instancié à chaque requête → épuisement des sockets (SocketException) + performances médiocres.

  • Solution : singleton ou HttpClientFactory.

services.AddHttpClient<MyService>();

✅ HttpClientFactory gère la longévité des sockets et les politiques de retry/retry-backoff.


🔹 9. Comment enregistrer des services conditionnellement dans IServiceCollection ?

🧩 Exemple :

if (useMock)
    services.AddSingleton<IMyService, MockService>();
else
    services.AddSingleton<IMyService, RealService>();
  • Autre approche : extension method avec condition

services.AddSingleton<IMyService>(provider => 
    useMock ? new MockService() : new RealService());

🔹 10. Différences entre MediatR et DI classique

Aspect DI classique MediatR
Objectif Résolution directe de dépendances Découplage et envoi de messages/commandes
Couplage Couplage explicite (service A → B) Couplage faible via médiateur
Cas d’usage Appels directs, services CQRS, Event-driven architecture
Avantage Simple, direct Facilite la testabilité, extensions, pipelines (behaviors)

🧩 Exemple MediatR :

public class CreateOrderCommand : IRequest<bool> { }
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, bool> { ... }
await mediator.Send(new CreateOrderCommand());

✅ Avantage : réduction du couplage, centralisation des pipelines (logging, validation, transaction).


📘 RÉCAPITULATIF SYNTHÉTIQUE

Concept Objectif / Utilité Exemple
OCP Étendre sans modifier Interface IDiscount
Factory + DI Créer objets dynamiquement ShapeFactory + IServiceProvider
Activator.CreateInstance Runtime Plugins / DLL externe
Options Pattern Config typée injectée IOptions
ServiceProvider Résoudre dynamiquement provider.GetService()
Dépendances circulaires Eviter boucle Lazy, Func
Func vs GetService Résolution retardée Lazy factory
HttpClient Réutilisation Singleton / HttpClientFactory
Enregistrement conditionnel Choisir implémentation runtime if(useMock) AddSingleton<>
MediatR Découplage et CQRS IRequest / IRequestHandler





🔹 1. Comment fonctionne le système d’injection de dépendances en .NET Core ?

  • .NET Core intègre un conteneur IoC (Inversion of Control) par défaut via l’interface IServiceProvider.

  • Les services sont enregistrés dans IServiceCollection lors de la configuration (Program.cs ou Startup.cs).

  • Lorsque le framework instancie un composant (Controller, Middleware, Razor Page, etc.), il résout automatiquement les dépendances à partir du conteneur.

  • Exemple de résolution :

public class MyController : ControllerBase
{
    private readonly IMyService _service;
    public MyController(IMyService service)
    {
        _service = service; // injecté automatiquement
    }
}

🔹 2. Différence entre Transient, Scoped et Singleton

Type Description Exemple typique Durée de vie
Transient Nouvelle instance à chaque injection Services légers, stateless Chaque requête ou chaque injection
Scoped Une instance par scope (par défaut, par requête HTTP) Services dépendant du contexte HTTP Par requête HTTP
Singleton Une seule instance pour toute l’application Cache, configuration, moteur de calcul Application entière

✅ Le choix de la durée de vie impacte mémoire, performance et sécurité des données partagées.


🔹 3. Enregistrer et utiliser un service personnalisé avec DI

  1. Définir le service et son interface :

public interface ITradeService { void ExecuteTrade(); }
public class TradeService : ITradeService
{
    public void ExecuteTrade() { /* logique trading */ }
}
  1. L’enregistrer dans Program.cs :

builder.Services.AddScoped<ITradeService, TradeService>();
  1. L’injecter dans un Controller :

public class TradeController : ControllerBase
{
    private readonly ITradeService _tradeService;
    public TradeController(ITradeService tradeService)
    {
        _tradeService = tradeService;
    }
}

🔹 4. Injecter une dépendance dans un Middleware ASP.NET Core

  • Les middlewares peuvent recevoir des services via injection dans le constructeur ou via Invoke :

public class LoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<LoggingMiddleware> _logger;

    public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation("Processing request");
        await _next(context);
    }
}
  • Ensuite, on ajoute le middleware :

app.UseMiddleware<LoggingMiddleware>();

🔹 5. Configurer une usine de services (Factory Pattern) avec DI

  • On peut créer une factory pour instancier des services avec logique complexe :

builder.Services.AddSingleton<ITradeService>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return new TradeService(config["TradeMode"]);
});
  • Permet de passer des paramètres runtime ou configurer dynamiquement des services.


🔹 6. Avantages d’utiliser l’injection de dépendances

Avantage Explication
Découplage Le code dépend d’une interface plutôt que d’une implémentation concrète
Testabilité Permet le mocking ou stub des services pour les tests unitaires
Réutilisation Services centralisés et configurés une seule fois
Flexibilité Facilité pour changer d’implémentation ou de durée de vie sans modifier le code client
Maintenance simplifiée Moins de code « new » dispersé, logique centralisée dans le conteneur DI

✅ Dans une application financière ou trading, DI permet de centraliser la configuration du moteur de risque, des services de pricing et de l’accès aux données, améliorant la robustesse et la testabilité.



Aucun commentaire:

Enregistrer un commentaire