vendredi 15 avril 2011 15:01
tja
[ASP.NET MVC 3] Deep Dive Injection de Dépendance – Eviter les pièges – Partie 4
Cet article fait partie d’une série consacrée à l’injection de dépendance dans MVC 3 :
Dans les deux derniers postes nous avons parlé de la partie la plus importante pour l’injection de dépendance dans le Framework ASP.NET MVC 3. L’introduction de IDependencyResolver et de IControllerActivator a sacrifié une fonctionnalité très importante et ESSENTIELLE dont le manque peut causer des GRAVES problèmes comme
LES FUITES DE MEMOIRE !
Ce post a donc pour but de vous mettre en garde afin que vous évitiez les pièges et donc les problèmes liés à l’utilisation de IDependencyResolver et de IControllerActivator ainsi que de certains types de containeurs DI.
Attention: Ceci est vrai pour certains types de containeurs DI. J’en parle à la fin de cet article.
Un peu de théorie DI
Sans rentrer dans du BLABLA, un de principe le plus importants à connaitre est le “triple R” (RegisterResolveRelease) qui d’ailleurs a été nommé il n’y a pas si long temps de ça. Pour résumer, nous devions faire uniquement 3 choses avec le containeur DI :
- Register : enregistrer les composants dans le containeur DI.
- Resolve : résoudre le composant racine.
- Release : libérer les composants du containeur DI.
Si on ne respecte pas ce principe lorsqu’on utilise un containeur DI, alors il y a des risques qu’il soit mal utilisé.
Maintenant qu’on sait ceci, la suite sera plus évidente.
IDependencyResolver et IControllerActivator dans tout ça ?
Revoyons un peu la signature des ces 2 interfaces
IDependencyResolver

IControllerActivator

Qu’est-ce qui manque dans la définition de ses interfaces ?
Il n’y a pas de méthode “Release” !
Pour résumer cela veut tout simplement dire que je ne peux pas respecter le principe RRR de l’injection de dépendance. Il est donc possible de résoudre un service/composant dans le containeur DI mais impossible de le nettoyer. Pour l’équipe MVC ceci n’est pas un problème car ils vont appeler la méthode “Dispose” sur tous les services qui implémentent l’interface IDisposable. Cela cependant ne marchera pas tout le temps. Pourquoi ?
Problème 1 :
Admettons que j’ai un contrôleur comme ça :

Mon contrôleur n’implémente pas IDisposable donc à aucun moment la méthode Dispose n’est appelée.
Problème 2 :
Admettons que service de commande implémente IDisposable.

OrderService IMPLEMENTE IDisposable. Le problème est que le Framework MVC ne le sait pas, car déjà mon HomeController dans lequel le service est injecté ne le sait pas non plus. C’est le plus important avantage dans l’utilisation d’un containeur DI ! Il s’occupe et manage la vie des composants et non celle du service client. Si demande résolution de HomeController à partir de mon containeur, alors toutes les dépendances seront créées et si des dépendances implémentant l’interface IDisposable sont trouvées alors le containeur (certains containeurs) trackera (gardera les références vers les objets) tout le graphe d’objets. Si ensuite j’avais la possibilité d’appeler la méthode “Release” sur l’instance de mon contrôleur HomeController alors le containeur saura comment disposer de mon service de commande OrderService. MAIS si la méthode “Release” n’est jamais appelée, mon OrderService ne sera jamais disposé et notre application web consommera de plus en plus de mémoire, connections à la base de données et toutes les ressources qui devrait être libérées avec la méthode Dispose.
Problème 3:
Admettons maintenant que mon contrôleur override la méthode Dispose de la classe de base Controller pour libérer les dépendances qu’il consomme.

Cela pose plusieurs problèmes. D’abord comment disposer correctement les dépendances ? Imaginez que mon contrôleur prend 3 autre dépendances en constructeur en plus de IOrderService. Je dois donc boucler dans la méthode Dispose et vérifier si une de dépendances implémente IDisposable pour la disposer ? Cela n’a pas de sens ! La vie des composants sont gérés par le containeur DI qui les crée, donc le service qui le consomme n’a aucun moyen de savoir s’il peut le disposer car la dépendance peut être partagée entre plusieurs services clients.
Quels containeurs DI sont concernées ?
Bien évidement ce problème concerne plus particulièrement certains type de containeurs DI. Je ne les connait pas tous mais ceux que j’utilise les plus souvent sont : CastleWindsor, Unity, StructureMap. Je ne vais pas rentrer dans le détail du fonctionnement de chacun d’eux mais ce qu’il faut savoir ce qu’il y a des containeurs qui trackent les instances pour la vie d’objets Transcient (une nouvelle instance est servie pour chaque appel au containeur) et d’autres qui ne le font pas. Dans la liste de ceux que j’utilise, les composants qui trackent les instances sont :
- CastleWindsor
- Unity
Pour StructureMap cela ne pose donc pas de problème. Il faut cependant penser à ce que les instances disposable soit décorées dans des classes “proxy” qui font leur dispositions en interne. Ensuite, c’est des classes proxy qui devrait être consommées par des clients. Cela peut poser plus ou moins de problèmes.
Pour CastleWindsor et Unity on doit leur indiquer à un moment qu’ils peuvent disposer ces instances afin d’éviter les fuites de mémoire. Justement pour cela il faut une méthode “Release”, faute de quoi, les instances traineront en mémoire !
Workarounds
Bien évidemment il existe des contournements pour palier à ce problème lorsque le containeur que vous utilisez tracke les instances Transcient. Dans le cas de Windsor vous pouvez enregistrer vous instances avec le cycle de vie PerWebRequest. Avec Unity, vous pouvez créer votre propre cycle de vie. Vous pouvez créer un décorateur pour votre controlleur ou d’utiliser un child container.
Mais tout ça est ridicule ! Pourquoi nous devons penser à fabriquer des contournements afin d’utiliser notre conaineur DI préféré avec MVC 3 alors que justement la team MVC nous voulait faciliter l’injection de dépendance dans MVC 3.
Conclusion
Comme vous pouvez le constater l’injection de dépendance dans MVC 3 avec IDependencyResolver et IControllerActivator n’est pas utilisable en soi, en tout cas pas pour tout les types de containeurs DI. C’est également un piège pour les développeurs qui ne connaissent pas bien le fonctionnement de leur containeurs DI préférés.
Pour ma part je n’utiliserai ni IDependencyResolver ni IControllerActivator et je resterai avec la bonne vieille IControllerFactory sur laquelle nous avons la méthode ReleaseController ce qui permet de libérer les composants de notre containeur DI préférée.
Mon conseil est donc restez avec IControllerFactory 
Amen.
// Thomas
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :