Cet article fait partie d’une série consacrée à l’injection de dépendance dans MVC 3 :

Faisant suite à la session TechDays sur ASP.NET MVC 3 que j’ai coanimmé avec Rui, j’ai enfin trouvé plus de temps pour écrire une série de posts sur l’injection de dépendance telle qu’elle était dans MVC 3.

Puisque c’est un post d’introduction je vais parler rapidement ce qu’est l’injection de dépendance et les problèmes qu’elle permet de régler. Posons donc notre première question :

“Quel grand problème permet de régler l’injection de dépendance ?”

Pour répondre simplement, nous pouvons affirmer que le plus grand problème est la gestion des dépendances entre les objets. Quand une classe prend une dépendance sur un service elle prend la dépendance sur toutes les dépendances de ce service. Ce qui a pour conséquence d’avoir la dépendance sur une grappe d’objets entière.

L’idée générale de l’injection de dépendance est de construire des applications faiblement couplées (en étant très bref) en déléguant la composition, la création et la gestion des vies des objets à un Framework externe tels que les containeurs DI.

L’injection de dépendance (DI) et IoC (Inversion of control)

Lorsqu’il s’agit de “casser” les dépendances dans vos application, beaucoup de développeurs utilisent interchangeablement la notion de DI et IoC pour parler de la même chose. Faisons le point là-dessus :

  • IoC (Inversion of Control) : Les objets ne crée pas d’autres objets sur lesquelles ils s’appuient pour réaliser leur travail. Ils reçoivent les objets dont ils ont besoin d’une source externe.
  • DI (Dependency Injection) : Veut simplement dire que le passage de dépendances entre les objets se fait sans aucune intervention d’objets (objet, ne crée pas d’autres objets et ne passe pas d’autres objets non plus). Cela se passe typiquement par le biais d’un Framework qui passe les dépendances (objets) par les constructeurs, les propriétés ou les méthodes.

Une autre notion importante à aborder pour comprendre les motivations de l’équipe MVC d’avoir fait évolué la manière de faire de l’injection de dépendance dans MVC 3 et celle de Common Service Locator (CSL).

Common Service Locator (CSL)

Si vous utilisez des containeurs DI dans vos applications, vous créez en quelque sorte une dépendance entre le DI en question et votre application. L’équipe Microsoft's patterns & practices a fourni sur CodePlex une librairie appelée Common Service Locator (CSL) qui est une abstraction au dessus de tout containeur DI ou Service Locator. Elle contient une interface partagée pour la location des services dans vos applications. A l’aide de cette librairie votre application peut avoir accès indirectement aux composants/dépendances sans dépendre sur les références en dur vers une implémentation d’un containeur DI ou un autre.

Est-ce que dans MVC 3 la location de service a quelque chose à voir avec la bibliothèque CSL ?

Oui ! En fait dans les préreleases de MVC 3, l’équipe de développement s’est posé la question s’il n’était pas plus facile tout simplement d’inclure CSL dans MVC 3. Finalement ils ne l’ont pas fait (ce que personnellement je trouve bien) car il ne voulait pas faire dépendre le Framework MVC 3 sur une librairie externe qui ne fait pas partie officiellement du Framewrok.NET. Donc au lieu de créer une référence en dur sur la librairie CSL ils se sont inspirés pour créer leur propre interface de location de service :

Nouveauté ! IDependencyResolver

L’intention de IDependencyReolver est de simplifier la manière de résoudre/localiser les dépendances dans le Framework MVC 3. Avec cette nouvelle interface vous pouvez intégrer plus subtilement n’importe lequel Framework d’injection de dépendance dans vos applications. Cette interface est également le point central de registration de votre containeur DI préféré. Regardons de plus prêt l’interface en question :

   1: public interface IDependencyResolver {
   2:     object GetService(Type serviceType);
   3:     IEnumerable<object> GetServices(Type serviceType);
   4: }

Note : La différence principale avec le CSL est que les implémentations de IDependencyResolver doivent toujours retourner null de GetService() si le service n’est peut pas être localisé (CSL lance une exception). Sur le même principe GetServices() doit retourner une collection vide (CSL aussi). Si votre implémentation de IDependencyResolver lance une exception ou si null est retourné au lieu de la collection vide, cela sera rapporté à l’utilisateur en tant que run-time error.

Nous allons décrire cette interface dans la suite de cet article. Il est maintenant important de comprendre comment grâce à cette interface MVC peut consommer des services. Comme le décrivait Brad Wilson dans les séries consacrées à l’injection de dépendance et surtout dans le post d’introduction, il y a trois manières très importantes dont MVC consomme les services avec les service locator. Ces concepts sont très importants afin de maitriser l’injection de dépendance dans MVC 3 :

  1. Localiser une instance d’un service unique.
  2. Localiser un service enregistré plusieurs fois.
  3. Création d’objets divers.

Repassons en revue chaque point.

1. Localiser une instance d’un service unique.

Pour résoudre une instance d’un service enregistré unique, MVC fera appel à la méthode IDependencyReolver.GetService(). Un exemple d’un service unique est la ControllerFactory (IControllerFactory).

2. Localiser un service enregistré plusieurs fois.

Logiquement pour localiser un service enregistré plusieurs fois se fait grâce à l’appel à la méthode IDependencyReolver.GetServices(). Typiquement le point d’extension se comporte comme une liste de ses services, ou comme une façade qui “trouve” le bon service à utiliser. Dans MVC il y a plusieurs services qui peuvent être enregistrés plusieurs fois. Un exemple :

  • IViewEngine (sous forme de façade, une liste de views engines ViewEngines.Engines est parcourue pour savoir si une vue peut être fourni).
  • ModelValidatorProviders.Providers (façade, interroge chaque service et agrège les réponses).
3. Création d’objets divers.

C’est lorsqu’on utilise un comportement de DI. C’est utile lorsqu’un objet créé a une dépendance sur les services enregistrés dans DependencyResolver (comme les contrôleurs). Il serait donc approprié de créer cet objet par le DependencyResolver. Si le DependencyResolver n’arrive pas à fournir toutes les dépendances alors un fallback sera fait vers le comportement par défaut (appel à Activator.CreateInstance()).

Deux autres services de création d’objets existent également :

  • ControllerActivator
  • ViewPageActivator

J’en parlerai dans des articles suivants.

DependencyResolver pour l’enregistrement statique de SL.

C’est le point d’enregistrement statique pour les dependency resolvers (implémentations de vos containeurs DI préférés). Voyons son implémentation :

   1: public class DependencyResolver {
   2:     public static IDependencyResolver Current { get; }
   3:     public static void SetResolver(IDependencyResolver resolver);
   4:     public static void SetResolver(object commonServiceLocator);
   5:     public static void SetResolver(Func<Type, object> getService,
   6:     Func<Type, IEnumerable<object>> getServices);
   7: }

Il y a également les méthodes d’extension statique :

   1: TService GetService<TService>(this IDependencyResolver resolver) {
   2:     return (TService)resolver.GetService(typeof(TService));
   3: }
   4:  
   5: IEnumerable<TService> GetServices<TService>(this IDependencyResolver resolver) {
   6:     return resolver.GetServices(typeof(TService)).Cast<TService>();
   7: }

Comme vous pouvez le constater en analysant la classe DependencyResolver, vous avez trois manières d’enregistrement de votre containeur DI :

  • En fournissant une implémentation de IDependencyResolver.
  • En fournissant une implémentation de IServiceLocator de CSL (il n’y a pas de dépendance dans MVC sur CSL donc la réflexion est utilisée pour vérifier que c’est une implémentation de IServiceLocator).
  • Ad-hoc resolver basé sur des fonctions avec des signatures qui correspondent.

Toutes ces registrations seront transformées en IDependencyResolver lorsqu’on appelle DependencyResolver.Current. Un wrapper sera créé au besoin pour que le code ne manipule qu’une seule interface IDependencyResolver.

Note : Par défaut il y a toujours un SL (qui ne fait qu’appeler Activator.CreateInstance()) qui n’est pas configurable. C’est pour les utilisateurs qui ne souhaitent pas utiliser DI.

LifeTime Managment

Les services consommés par MVC seront localisés une fois et placés dans un cache pour la durée de vie de l’application.

Services consommés par les classes ne sont pas trackés par MVC donc c’est à l’auteur de containeur ou de l’application de gérer la vie des objets.

Point d’extension dans MVC 3

Ci dessous un petit tableau des points d’extension principaux disponible dans MVC 3. Comme vous pouvez le constater le points d’extension de MVC 2 ont bénéficié d’un support de IDependencyResolver, mais il y a également 3 nouveau points d’extension qui n’apparaissent qu’avec MVC 3  :

Description Statique Dynamique Unique Multiple Nouveau dans MVC 3
IControllerFactory oui - oui -  
IControllerActivator - oui oui - oui
IViewPageActivator - oui oui   oui
IViewEngine, IView oui - - oui  
IFilterProvider oui oui - oui oui
ModelValidatorProvider oui - oui -  
ModelMetadataProvider oui - oui -  
ValueProviderFactory, IValueProvider oui oui - oui  
IModelBinderProvider oui oui - oui  

Tout ces points seront traités dans la série d’articles à venir.

A bientôt pour la prochaine série Clignement d'œil

 

// Thomas