mercredi 6 avril 2011 00:30
tja
[ASP.NET MVC 3] Deep Dive Injection de Dépendance – IControllerActivator – Partie 3
Cet article fait partie d’une série consacrée à l’injection de dépendance dans MVC 3 :
Dans le dernier article je vous ai parlé de IDependencyResolver qui est un nouveau point d’extension de MVC 3 pour l’injection de dépendance. Dans cet article nous allons nous intéresser à un nouveau point d’extension de MVC 3, IControllerActivator.
Le code source de cet article se trouve ici
.
Comme je l’ai expliqué dans l’article précédent, avant le MVC 3, pour réaliser l’injection de dépendance nous avions à disposition l’interface IControllerFactory et une implémentation par défaut de DefaultControllerFactory.
Pourquoi ce point d’extension ?
Brad Wilson de l’équipe MVC donne une explication dans son post. Pour résumer, l’équipe MVC s’est aperçu que la DefaultControllerFactory réalisait deux choses: convertissait le nom du contrôleur dans son type .NET et créait une instance de ce type. C’est une violation de principe SRP et c’est pour cette raison ils ont décidé de déplacer la partie d’instanciation de contrôleur dans un nouveau service qui est IControllerActivator.
IControllerActivator et DefaultControllerActivator
La responsabilité de IControllerActivator est donc de créer un contrôleur pour un type donnée pour servir la requête en cours. L’instance de IControllerActivator est localisable via la classe du Framework MVC DependencyResolver. Il n’y a pas d’enregistrement statique comme dans le cas d’une controller factory. Ce service ne peut être enregistré qu’une seule fois. Si jamais vous l’enregistrez par le biais de IDependencyResolver de MVC 3 et par en même temps par l’ancienne méthode des classes statiques ControllerBuilder.Current.SetControllerFactory vous obtiendrez une belle erreur. Pour le démontrer :
1: container.RegisterType<IControllerFactory, UnityControllerFactory>();
2: ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container));
3: DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Cet enregistrement dans Global.asax provoque l’erreur suivante :

Bien entendu, l’erreur est assez explicite et vous ne perdrez pas trop de temps à essayer de comprendre votre erreur. Ce n’est cependant pas une bonne idée de mélanger plusieurs manières d’enregistrement de services pour le Framework MVC 3. La bonne pratique est de ne pas utiliser les classes de façade lorsque vous utilisez DependencyResolver.
Avant d’aller plus loin, voyons à quoi ressemble la signature de l’interface IControllerActivator.
1: public interface IControllerActivator {
2: IController Create(RequestContext requestContext, Type controllerType);
3: }
Comme vous pouvez le constater il n y qu’une seule méthode “Create” qui sera appelée par la DefaultControllerFactory. On peut donc dire que la DefaultControllerFactory délègue la création des contrôleurs à un service externe implémentant l’interface IControllerActivator. L’instance de IControllerActivator est localisable via DependencyResolver.
Pour aller plus loin (pour les curieux): Vous remarquerez que la signature de IControllerActivator est la même que celle de la méthode DefaultControllerFactory.GetControllerInstance. Il y a deux raison pour cela. Tout d’abord la compatibilité avec l’ancienne méthode d’enregistrement de services pour le Framework MVC 3 (IControllerFactory, DefaultControllerFactory) et la deuxième raison est que l’appel à la méthode IControllerActivator.Create est délégué justement par la méthode DefaultControllerFactory.GetControllerInstance.
Et où est dans tout ça DefaultControllerActivator ? DefaultControllerActivator est tout simplement l’implémentation par défaut de l’interface IControllerActivator et qui d’ailleurs est une classe interne de DefaultControllerFactory. C’est tout simplement l’implémentation de Activator.CreateInstance(controllerType). DefaultControllerActivator crée uniquement le contrôleur en utilisant son constructeur sans paramètres. C’est pour cette raison que par défaut dans ASP.NET MVC tous les contrôleurs doivent avoir un constructeur sans paramètres.
Si vous voulez remplacer DefaultControllerActivator vous devriez soit fournir une implémentation de IDependencyResolver qui fourni une instance personnalisée de IControllerActivator.
IControllerActivator dans la pratique
Examinons les différentes manières d’implémentation du service IControllerActivator.
1. IControllerActivator avec IDependencuResolver.
Si vous déjà utilisez DependencyResolver pour résoudre vos services il ne vous reste que d’implémenter l’interface IControllerActivator. Pour savoir comment implémenter votre propre DependencyResolver, veuillez-vous référer à mon post précédent à ce sujet : http://blogs.developpeur.org/tja/archive/2011/03/17/asp-net-mvc-3-deep-dive-injection-de-d-pendance-controller-idependencyresolver-partie-2.aspx. Voici le code pour l’implémentation de IControllerActivator.
1: public class UnityControllerActivator : IControllerActivator
2: {
3: public IController Create(RequestContext requestContext, Type controllerType)
4: {
5: return DependencyResolver.Current.GetService(controllerType) as IController;
6: }
7: }
Comme vous pouvez le constater il n’y a rien de compliqué. A l’intérieur nous faisons appel à la méthode statique de DependencyResolver pour résoudre et créer les instances de contrôleurs. Il nous suffit juste d'enregistrer la configuration dans Global.asax par exemple:
1: container.RegisterType<IControllerActivator, UnityControllerActivator>();
2: DependencyResolver.SetResolver(new UnityDependencyResolver(container));
La deuxième méthode est bien plus optimale
2. IControllerActivator sans IDependencyResolver.
Il est également possible de ne fournir que l’implémentation de IControllerActivator. Comment c’est possible ? En fait si vous jetez un coup d’œil d’un peu plus prêt sur la classe DefaultControllerFactory, vous remarquerez l’apparition d’un nouveau constructeur qui prend en paramètre une implémentation de IControllerActivator. Voici, la signature du constructeur en question :
1: public DefaultControllerFactory(IControllerActivator controllerActivator)
2: : this(controllerActivator, null, null) {
3: }
Nous ne pouvons pas utiliser l’implémentation de IControllerActivator comme dans l’exemple précédent car celle-ci repose sur l’utilisation de DependencyResolver. Nous créons donc une nouvelle implémentation qui sera basée directement sur l’utilisation de votre containeur DI préféré. Pour ma part je continue avec Unity:
1: public class UnityControllerActivator2 : IControllerActivator
2: {
3: private readonly IUnityContainer _container;
4:
5: public UnityControllerActivator2(IUnityContainer container)
6: {
7: if (container == null)
8: throw new ArgumentNullException("container",
9: "The container cannot be null. It is mandatory for controller location and creation");
10:
11: _container = container;
12: }
13:
14: public IController Create(RequestContext requestContext, Type controllerType)
15: {
16: if (controllerType != null)
17: if (!typeof(IController).IsAssignableFrom(controllerType))
18: throw new ArgumentException("Requested type is not a Controller type", "controllerType");
19:
20: var controller = (Controller)_container.Resolve(controllerType);
21:
22: return controller;
23: }
24: }
Comme vous pouvez le constater, cette implémentation est presque identique à ce qu’on faisait lorsqu’on implémentait IControllerFactory dans MVC1 et MVC2 ! Il y a juste le nom de la méthode qui change (Create au lieu de GetControllerInstance). Il ne nous reste que d’initialiser tout ça dans le fichier Global.asax :
1: ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new UnityControllerActivator2(container)));
Et tout marche ! pour les exemples n’hésitez pas à vous référez au code source d’exemple sur mon repository.
Quelle méthode dois-je utiliser ?
Comme vous pouvez le constater il y a beaucoup d’alternatives et les développeurs qui commencent tout juste avec le Framework MVC 3 seront vite confus. Pour moi, la meilleure méthode est le bon vieux système avec l’implémentation de IControllerFactory, cela évite les effets de bord, comme l’utilisation massive de DependencyResolver en tant que service locator qui est un anti-pattern. Je suis content que l’équipe MVC a introduit de nouveaux points d’extension dans le Framework, mais le service locator n’était pas nécessaire. Surtout qu’il faut prendre en compte des précautions afin que son utilisation soit optimale, sinon attention aux fuite de mémoires ! Mais cela dans mon prochain post.
// 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 :