Principe des conteneurs IoC
Un conteneur d'inversion de contrôle (IoC) agit comme un médiateur entre les composants d'une application. Imaginez une prise électrique : les appareils se connectent quand ils ont besoin d'énergie et se déconnectent pour économiser des ressources. De même, un conteneur IoC fournit les dépendances nécessaires à un objet uniquement lorsqu'elles sont requises.
L'utilisation d'un conteneur IoC offre plusieurs avantages :
- Améliore la maintenabilité du code.
- Facilite les tests unitaires, le débogage et le diagnostic des pannes.
- Promeut la réutilisabilité des composants.
- Découple les composants, augmentant ainsi la flexibilité du système.
Configuraton et enregistrement de services dans AutoFac
AutoFac est un conteneur IoC pour la plateforme .NET. Son utilisation principale repose sur deux opérations : l'enregistrement (register) et la résolution (resolve). Voici comment l'utiliser.
Enregistrement de base et injection par constructeur
Pour enregistrer un service, on utilise un ContainerBuilder. On peut enregistrer un type concret, l'associer à une interface ou utiliser une expression lambda pour un contrôle plus fin.
// Exemple d'enregistrement et de résolution
var constructeur = new ContainerBuilder();
constructeur.Register( c => new ServiceConcret() ).As<iservice>();
var conteneur = constructeur.Build();
var service = conteneur.Resolve<IService>();
</iservice>
L'injection de dépendances via le constructeur est gérée automatiquement. AutoFac résout les paramètres du constructeur à partir des types enregistrés.
// Un service qui dépend d'un autre
constructeur.Register( c => new ServiceDependant( c.Resolve<IDependance>() ) );
Enregistrements avancés
On peut également injecter des dépendances via les propriétés ou choisir un constructeur spécifique.
// Injection de propriété
constructeur.Register( c => new ServiceAvecPropriete() { Prop = c.Resolve<IDependance>() } );
// Sélection d'un constructeur particulier
constructeur.RegisterType<ServiceMultiConstructeur>()
.UsingConstructor(typeof(IDependance));
Pour différencier plusieurs implémentations d'une même interface, on peut utiliser des noms (Named) ou gérer l'ordre d'enregistrement. Par défaut, la dernière implémentation enregistrée pour une interface écrase les précédentes.
// Enregistrement nommé
constructeur.RegisterType<RepoBD>().Named<IRepository>("bd");
constructeur.RegisterType<RepoTest>().Named<IRepository>("test");
var repoTest = conteneur.ResolveNamed<IRepository>("test");
AutoFac peut aussi scanner un assembly pour enregistrer automatiquement tous les types qui implémentent certaines interfaces, selon des filtres.
constructeur.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces();
Gestion du cycle de vie des composants
La durée de vie (lifetime) d'un composant détermine comment et quand une instance est créée et partagée. AutoFac propose plusieurs stratégies.
- InstancePerDependency (par défaut) : Une nouvelle instance est créée pour chaque appel à
Resolve. - SingleInstance : Une seule instance est partagée dans tout le conteneur (singleton).
- InstancePerLifetimeScope : Une instance unique est partagée au sein d'une même portée (
LifetimeScope). Des portées différentes obtiennent des instances différentes.
// Exemple de portée de vie
constructeur.RegisterType<MonService>().InstancePerLifetimeScope();
using (var portee = conteneur.BeginLifetimeScope())
{
var service1 = portee.Resolve<MonService>();
var service2 = portee.Resolve<MonService>();
// service1 et service2 sont la même instance
}
Les objets implémentant IDisposable sont disposés automatiquement lorsque leur portée de vie se termine.
Événements du cycle de vie
AutoFac déclenche des événements lors des différentes phases de la vie d'un composant :
OnActivating: Avant que l'instance ne soit retournée. Permet de remplacer l'instance ou d'effectuer une initialisation précoce.OnActivated: Après la création de l'instance.OnRelease: Lorsque le composant est sur le point d'être libéré.
constructeur.RegisterType<MonService>()
.OnActivating(e => e.Instance.Initialiser())
.OnActivated(e => Console.WriteLine("Service activé"))
.OnRelease(e => Console.WriteLine("Service libéré"));
Gestion des dépendances cycliques
Les dépendances cycliques (A dépend de B, et B dépend de A) peuvent poser problème. AutoFac peut en résoudre certaines formes, par exemple en utilisant l'injection de propriété et en spécifiant une durée de vie singleton pour l'un des services.
// Résolution partielle d'une dépendance cyclique
constructeur.RegisterType<ClasseB>()
.PropertiesAutowired(PropertyWiringFlags.AllowCircularDependencies)
.SingleInstance();
constructeur.Register(c => new ClasseA(c.Resolve<ClasseB>()));