Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Je pense que le titre parle de lui-même. J’ai passé du bon temps en écrivant ici mais le temps est venu de bouger. En même temps j’ai décidé de commencer à écrire en anglais donc l’occasion est parfaite. La raison principale est de m’améliorer et surtout pour avoir un retour plus large de la part de la communauté.

Pour ce qui aimerais garder contact avec moi n’hésitez pas à faire un tour sur la nouvelle plateforme de blog que Monsieur mon ami Rui Carvalho a mis rapidement en place et qui s’appelle  : Code Distillers (http://www.codedistillers.com/). Pour le moment il n’y a pas beaucoup de posts, mais cela viendra, car on a quelques projets OSS en cours.

Ceci dit, avant de partir :

Ma dernière présentation aux TechDays 2012 est en ligne :

Ma prochaine session ALT.NET est bientôt :

Codez bien et à bientôt.

 

// Thomas

Il y a un certain temps, Julien a écrit un post sur la gestion des erreurs Http dans ASP.NET MVC. Après la lecture de son post, je me suis aperçu qu’il ne traite qu’une partie de la gestion des erreurs. J’ai décidé de vous proposer une approche légèrement différente pour implémenter la gestion des erreurs http.

Les objectifs

Avant de passer à l’implémentation il faut qu’on établisse des objectifs à atteindre, où la plus grande difficulté pose la gestion des erreurs 404 dont parlait Julien dans son blog :

  1. Afficher le statut 404 lorsqu’une route correspond, un contrôleur est trouvé mais pas l’action.
  2. Afficher le statut 404 lorsqu’une route correspond mais le contrôleur n’est pas trouvé.
  3. Afficher le statut 404 lorsqu’une route ne correspond pas à nos routes définies. Ces erreurs ne doivent pas remonter jusqu’à Global.asax ou IIS car ensuite il n’est pas possible de rediriger proprement dans notre application.
  4. Afficher le statut 404 lorsqu’une ressource n’est pas trouvée. Par exemple un Id passé en paramètre d’une route n’existe pas. La manière propre (RESTFul) est indiquer ceci à l’utilisateur par un code Http.
  5. Afficher le statut 500 pour toutes les exceptions non catchées dans l’application.
  6. Afficher les codes de statuts souhaités lorsqu’on en a besoin (400, 401, etc.).
  7. Afficher une vue appropriée pour chaque type d’erreur qui est une page dynamique et non juste une page statique où on ne peut pas afficher du contenu dynamique. Ces pages doivent retourner les codes http appropriés (même si la page d’erreur s’affiche on devrait avoir le code 404 pour une ressource non trouvé et non 200 comme dans le cas d’une mauvaise gestion). Cela est important pour les moteurs de recherche qui indexent votre site web.

Passons à l’action.

Objectif n° 1

Pour couvrir l’objectif n°1 nous devons d’abord introduire un élément qui permettra la gestion des erreurs centralisée. J’évite de mettre du code dans Global.asax, car d’une au bout d’un moment les bouts de code pour traiter les différentes problématiques se mélangent entre eux, et de deux, le code n’est pas très exploitable par d’autres parties de l’application (souvent la duplication pointe son nez). Pour cela je préfère de créer un Controller pour la gestion des erreurs Http. Voici un exemple de code :

   1: public class ErrorController : BaseErrorController
   2: {
   3:     public ActionResult Http404(string url)
   4:     {
   5:         Response.StatusCode = (int)HttpStatusCode.NotFound;
   6:         var model = GetModel(url);            
   7:         return View("404", model);
   8:     }
   9:  
  10:     public ActionResult Http500(string url)
  11:     {
  12:         Response.StatusCode = (int)HttpStatusCode.InternalServerError;        
  13:         var model = GetModel(url);
  14:         return View("500", model);
  15:     }
  16:  
  17:     public ActionResult Generic(string url, int statusCode)
  18:     {
  19:         Response.StatusCode = statusCode;
  20:         var model = GetModel(url);
  21:         return View("Error", model);
  22:     }
  23:  
  24:     private NotFoundViewModel GetModel(string url)
  25:     {
  26:         var model = new NotFoundViewModel();
  27:         
  28:         model.RequestedUrl = Request.Url.OriginalString.Contains(url) & Request.Url.OriginalString != url ? Request.Url.OriginalString : url;
  29:         model.ReferrerUrl = Request.UrlReferrer != null && Request.UrlReferrer.OriginalString != model.RequestedUrl ? Request.UrlReferrer.OriginalString : null;
  30:  
  31:         return model;
  32:     }
  33: }

Ce qui est important de noter ce que la seule responsabilité de ce contrôleur est d’afficher une vue correspondante à l’erreur avec un bon code de statut HTTP. Donc par exemple, l’action Http404 affiche une vue “404” en y passant un view model personnalisé avec une Url demandé et Url d’origine et avec un bon code de statut Http qui dans notre cas et le 404 (NotFound). Le view modèle peut être différent, suivant les informations que vous voulez afficher dans la vue.

Ceci n’est cependant pas fini. Nous n’avons pas encore couvert l’objectif n°1. Pour cela nous introduisant un contrôleur de base. Tous les contrôleurs qui veulent couvrir l’objectif n° 1 doivent en hériter.

Les erreurs où l’action n’est pas trouvée doivent être attrapées dans tous les contrôleurs. Vous pouvez le faire en surchargeant la méthode HandleUnknownAction. Au lieu de le faire dans chaque contrôleur, il est plus judicieux d’introduire une classe de base dont tous les autres contrôleurs doivent hériter. En voici son implémentation :

   1: public abstract class BaseErrorController : Controller
   2: {
   3:     protected override void HandleUnknownAction(string actionName)  
   4:     {         
   5:         // Ne pas boucler s'il y a des exceptions dans ErrorController
   6:         if (GetType() != typeof(ErrorController))
   7:             HandleHttpException(HttpContext, 404);
   8:     }
   9:  
  10:     public ActionResult HandleHttpException(HttpContextBase httpContext, int statusCode)
  11:     {
  12:         var errorController = (IController)ControllerFactory.CreateDependencyCallback(typeof(ErrorController));
  13:         var errorRoute = new RouteData();
  14:  
  15:         switch (statusCode)
  16:         {
  17:             case 404 : 
  18:                 errorRoute.Values.Add("controller", "Error");
  19:                 errorRoute.Values.Add("action", "Http404");
  20:                 break;
  21:             case 500:
  22:                 errorRoute.Values.Add("controller", "Error");
  23:                 errorRoute.Values.Add("action", "Http500");
  24:                 break;
  25:             default:
  26:                 errorRoute.Values.Add("controller", "Error");
  27:                 errorRoute.Values.Add("action", "Generic");
  28:                 errorRoute.Values.Add("statusCode", statusCode);
  29:                 break;
  30:         }
  31:  
  32:         errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
  33:         errorController.Execute(new RequestContext(httpContext, errorRoute));          
  34:         
  35:         return new EmptyResult();
  36:     }
  37: }

Comme vous pouvez le voir, son implémentation est assez explicite. L’action HandleUnknownAction est évoquée à chaque fois qu’une action demandée n’a pas été trouvé. Nous invoquons ensuite la méthode HandleHttpException pour y analyser le code de statut et rediriger le flux vers le contrôleur ErrorController et une action correspondante à l’erreur. Objectif n° 1 est atteint !

Avantages
  • le code de gestion des erreurs est regroupé à un endroit. Nous respectons le principe DRY.
  • possibilité d’affichage des vues dynamiques ce qui est très avantageux lorsqu’on veut afficher des informations dynamiques.
Désavantages
  • dans l’immédiat, je n’en vois qu’un. Obliger les développeurs à penser d’hériter de cette classe de base.

Nous pouvons maintenant passe à l’objectif n° 2.

Objectif n° 2

Pour couvrir l’objectif n° 2, j’ai modifié ma ControllerFactory basée sur StructureMap pour faire de l’injection de dépendance. Si cependant vous n’utilisez pas l’injection de dépendance (j’espère que ce n’est pas le cas) vous pouvez toujours recourir au bon vieux Global.asax (attention aux problèmes de redirection). Il vaut tout de même mieux attraper les erreurs au plus prêt de leur source. Voici ma ControllerFactory :

   1: public class ControllerFactory : DefaultControllerFactory
   2: {
   3:     public static Func<Type, object> CreateDependencyCallback = type => Activator.CreateInstance(type);
   4:  
   5:     protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
   6:     {
   7:         try
   8:         {
   9:             if (controllerType == null)
  10:                 return base.GetControllerInstance(requestContext, controllerType); ;
  11:         }
  12:         catch (HttpException ex)
  13:         {
  14:             if (ex.GetHttpCode() == 404)
  15:             {
  16:                 var errorController = (IController)CreateDependencyCallback(typeof(ErrorController)); 
  17:                 ((ErrorController)errorController).HandleHttpException(requestContext.HttpContext, 404); 
  18:                 return errorController;
  19:             }
  20:             
  21:             throw ex;
  22:         }
  23:  
  24:         // check whetever is a asynchronous controller, because if handled the standard way the 404 is generetaed.
  25:         if (!CheckIsAsyncController(controllerType))
  26:         {
  27:             var controller = (Controller)CreateDependencyCallback(controllerType);
  28:             controller.ActionInvoker = (IActionInvoker)CreateDependencyCallback(typeof(ConventionActionInvoker));
  29:             return controller;
  30:         }
  31:  
  32:         return base.GetControllerInstance(requestContext, controllerType);
  33:     }
  34:  
  35:     private static bool CheckIsAsyncController(Type controllerType)
  36:     {
  37:         if (controllerType.BaseType != null && controllerType.BaseType.FullName != null && controllerType.BaseType.FullName.Contains(".Async"))
  38:             return true;
  39:         return false;
  40:     }
  41: }

Votre implémentation peut être différente mais le principe de base est le même. Ce qui est important se trouve entre les lignes 7 et 22. Lorsqu’un contrôleur n’est pas trouvé par le Framework MVC, une HttpException avec le code statut 404 est levée. Nous interceptons cette erreurs, obtenons l’instance de ErrorController de notre containeur DI et ensuite nous invoquons l’action qui va bien. Objectif n° 2 est atteint !

Avantages
  • l’utilisation de notre implémentation de ErrorController pour traiter l’exception.
Désavantages
  • un 2ème endroit où l’erreur est attrapée. En même temps je préfère cette approche car elle me donne plus de possibilité d’agir par rapport au Global.asax.

Maintenant que nous avons rempli l’objectif suivant nous pouvons passer à l’objectif n° 3.

Objectif n° 3

Celui-ci est très facile à remplir. Il suffit d’ajouter une route qui sera évoluée lorsqu’aucune autre route n’intercepte pas la requête http. Cette route doit pointer vers l’action Http404 de notre ErrorController :

   1: MvcRoute.MapUrl("{*url}")
   2:                 .WithDefaults(new { controller = "Error", action = "Http404" })
   3:                 .AddWithName("catch-all", routes)
   4:                 .RouteHandler = new DomainNameRouteHandler();

La syntaxe est différente car j’utilise les extensions MvcContrib mais je suis sur que vous savez l’écrire avec la syntaxe par défaut du Framework MVC.

Avantages
  • lorsqu’une route n’est pas trouvé pour la requête http courante, le Framework MVC redirige par défaut le problème dans Global.asax ce qu’on ne veut pas car nous voudrions afficher les erreurs d’une manière dynamique.
Désavantages
  • un 3ème endroit où l’erreur est attrapée. En même temps je préfère cette approche car elle me donne plus de possibilité d’agir par rapport au Global.asax.

Objectif n° 3 est atteint !

Maintenant que nous avons rempli l’objectif suivant nous pouvons passer à l’objectif n° 4.

Objectif n° 4

Pour traiter l’objectif n° 4. J’ai opté pour la création d’un Filtre personnalisé. Le but de ce filtre est d’attraper toutes les exceptions produites dans l’application et rediriger le problème vers notre ErrorController. Donc potentiellement toutes ces erreurs levées devraient être attrapées par notre filtre :

   1: public ActionResult Index(int id)
   2: {
   3:     // une logique d'application et ensuite levée d'exceptions car
   4:     // la logique a échoué
   5:     // ressource non trouvée
   6:     throw new HttpException(404, "Not found");
   7:     
   8:     // une erreur de traitement
   9:     throw new Exception("Erreur de traitement");
  10:     
  11:     // une autre erreur de traitement
  12:     throw new HttpException(500, "500 qui tue");
  13:     
  14:     // la requête mal formatée
  15:     throw new HttpException(400, "bad request");

Et voici l’implémentation de notre filtre :

   1: public class ErrorViewExceptionFilter :  ContainerBaseActionFilter, IExceptionFilter
   2: {
   3:     readonly int _statusCode; 
   4:     
   5:     public ErrorViewExceptionFilter(HttpStatusCodeResult prototype) : this(prototype.StatusCode) { 
   6:     }
   7:  
   8:     public ErrorViewExceptionFilter(int statusCode)
   9:     { 
  10:         _statusCode = statusCode; 
  11:     }
  12:  
  13:     public ErrorViewExceptionFilter()
  14:     {
  15:     }
  16:  
  17:     public void OnException(ExceptionContext filterContext)
  18:     {
  19:         int currentStatusCode = GetHttpCode(filterContext);
  20:         if ((currentStatusCode == _statusCode || _statusCode == 0) && !filterContext.ExceptionHandled)
  21:         {
  22:             filterContext.ExceptionHandled = true;
  23:             HandleExceptionWithViewResult(filterContext.Controller.ControllerContext, currentStatusCode);
  24:         }
  25:     }
  26:  
  27:     private int GetHttpCode(ExceptionContext filterContext)
  28:     {
  29:         var httpException = filterContext.Exception as HttpException;
  30:         if (httpException == null)
  31:             return 500; // returns 500 for non HTTP exceptions
  32:  
  33:         return httpException.GetHttpCode();
  34:     }
  35:  
  36:     private void HandleExceptionWithViewResult(ControllerContext controllerContext, int statusCode)
  37:     {
  38:         var errorController = (IController)CreateDependency<ErrorController>();
  39:         controllerContext.HttpContext.Response.TrySkipIisCustomErrors = true;
  40:         ((ErrorController)errorController).HandleHttpException(controllerContext.HttpContext, statusCode);
  41:     }
  42: }

Comme vous pouvez le constater, nous implémentons l’interface IExceptionFilter du Framework MVC ce qui nous permet d’intercepter les erreurs lorsqu’une exception se produit au sein d’un contrôleur. Attention, le filtre doit être appliqué au niveau du contrôleur. Ce qui est important c’est la méthode HandleExceptionWithViewResult. Lorsqu’une exception se produit, elle permet d’obtenir une instance de ErrorController et invoquer l’action correspondante pour afficher l’erreur. Il suffit maintenant d’enregistrer le filtre afin qu’il soit automatiquement appliqué à tous les contrôleurs. Pour cela nous allons utiliser une fonctionnalité disponible dans MVC 3 qui sont les GlobalFilters :

   1: GlobalFilters.Filters.Add(new ErrorViewExceptionFilter()); // attraper toutes les exceptions

Nous pouvons également paramétrer le filtre pour ne traiter que les erreurs souhaitées :

   1: GlobalFilters.Filters.Add(new ErrorViewExceptionFilter(new HttpNotFoundResult())); // erreurs 404
   2: GlobalFilters.Filters.Add(new ErrorViewExceptionFilter(500)); // erreurs 500

Objectif n° 4 est atteint ! 

Avantages
  • nous pouvons traiter toutes les exceptions ou les exceptions choisies d’une manière globale.
  • nous utilisons la même infrastructure (ErrorController) pour afficher les erreurs.
Désavantages
  • un 4ème endroit où l’erreur est attrapée. En même temps je préfère cette approche car elle me donne plus de possibilité d’agir par rapport au Global.asax.

Maintenant que nous avons rempli l’objectif suivant nous pouvons passer aux autres objectifs.

Objectifs n° 5, 6 et 7

Ces objectifs sont couverts par toutes les actions que nous avons mis en place dans les étapes précédentes.

Avantages
  • l’utilisation de l’infrastructure existante.
Désavantages
  • il n’y en a pas.

Conclusion

Bien que la gestion des erreurs http n’est pas complexe en soi, elle pose beaucoup de problèmes car la manière de gérer les codes d’erreur n’est pas la même suivant s’il s’agit des 404 ou d’autres types d’erreur. D’ailleurs cette gestion doit être compatible avec les prérequis du web pour éviter les problèmes dont Julien parlait dans son post initial (redirections 302 avant l’affichage de 404). Peut-être que l’implémentation que je propose n’est pas optimale mais néanmoins elle couvre fonctionnellement tous les cas de figure (j’espère). Si vous avez des suggestions n’hésitez pas à me faire un petit commentaire.

// Thomas

J’ai décidé d’écrire un autre post qui j’espère clarifiera les propos que j’ai tenus lors de mon post précédent “Etre un bon développeur c’est aussi une question d’attitude personnelle”. Ce texte a d’ailleurs provoqué une réponse que vous pouvez lire ici : “Bon développeur, question d’attitudes”. A la lecture de ce dernier, j’ai effectivement remarqué que l’auteur n’a pas tout à fait compris ce que je voulais dire. Il faut dire que j’en suis certainement pour quelque chose car le petit dialogue que j’ai cité a certainement contribué à l’ambigüité des propos que j’ai voulu partager avec vous. On ressent d’ailleurs dans sa réponse un air de “syndicalisme” si bien encré dans la culture française qui a certainement sa place et que je ne critique pas, mais il est hors de propos par rapport au message que je voulais transmettre. Je veux donc clarifier tout ceci car il y a certainement un plus grand nombre de lecteurs qui n’ont pas “décrypté” mon message codé.

Pour en revenir à la réponse qui m’a été faite et pour laquelle je remercie son auteur Eric, je remarque un point qui tout de suite saute aux yeux. Dans le titre de son texte il y a le mot clé “Développeur” mais le 2/3 de l’article parle des entreprises !!! Bien que les deux soient lié d’une manière ou d’une autre, le message initial (codé) que j’ai dirigé aux développeurs était :

Votre carrière est de votre responsabilité. Vous avez le choix, d’en prendre le contrôle ou subir. Que choisi tu ?

C’est juste ça ! Rien d’autre ! Bien sûr, que dans l’intérêt des entreprises est d’avoir des gens formés et au top, bien sûr qu’il y a des entreprises qui forment leur personnel et qui donnent du temps à la veille techno. J’irai encore plus loin, il y a des entreprises qui donnent du temps au side projects open source et qui les financent ! Oui tout ça existe, et si vous avez la chance de faire partie d’une de ses entreprises c’est une chance ! Mais encore une fois, à part une obligation légale, vous ne devriez pas être en attente qu’un saint patron vous prenne en charge et veuille bien vous former pour aller plus loin. La réalité est dure, parfois il coute moins cher d’envoyer les gens pas motivés en formation que de les licencier.

Travailler pour des entreprises qui ont compris l’intérêt d’entretenir la passion chez un développeur (je parle de notre domaine mais cela est valable pour d’autres également) est non seulement bénéfique pour sa propre carrière mais également pour l’entreprise.

Donc, si vous n’avez pas cette chance, si vous-vous ennuyez dans votre travail sur des technos bien anciennes, sachez que c’est de votre ressort de changer cet état de choses. Travailler, et apprendre pour soi n’est donc plus une obligation mais un plaisir ! Il peut y avoir plein d’aspect de la vie, qui font qu’on peut pas se permettre de travailler le soir, mais ceci est périodique. Si on est suffisamment passionné par ce qu’on fait, on trouvera toujours du temps pour le faire. Il est également important de noter que le travail fait en dehors de l’entreprise est fait pour vous, et en aucun cas, des problématique de l’entreprise doivent être traitées. Ce temps c’est le plaisir ! Si cela reste une obligation, il faudrait peut-être songer à changer de métier…

On parle du burnout. Contrairement, tout ceci est pour éviter les burnouts. Si vous êtes suffisamment passionnés par ce que vous faites cela ne peut que renforcer votre passion et votre confiance dans ce que vous faites. L’auteur de la réponse est d’ailleurs très for en spéculation et en mathématique car il sait mieux que moi comment mon temps est organisé. Juste pour clarifier, les 4h de transport sont bénéfique pour moi, je n’ai jamais lu autant de livres, écouté autant de podcasts ou regardé autant de vidéos qui m’ont fait progresser. Dans le train il est même très facile pour coder. Ceux qui me connaissent ne diront pas le contraire. Maintenant, je ne dis pas que tout le monde devrait faire pareil. J’ai dis juste que cela est possible de le faire. Je n’accepte pas d’excuse, je n’ai pas le temps…

Le chapitre sur des SSII, je passerai sans commentaire…

Je pense que le message est beaucoup plus clair maintenant.  Pour en finir, j’ai dirai que je voulais présenter le développeur comme la personne qui a le contrôle de sa propre vie. Le message devrait être positif et réveiller ceux qui sont en attente pour agir. Oui je le dirai encore une fois: Si tu ne progresses pas c’est de ta faute ! Car il faut savoir s’en donner les moyens. Se reposer sur l’entreprise ou rejeter tout la faute sur elle, c’est une bonne excuse qui arrange beaucoup de monde !

Et pour en revenir au dialogue du début :

Je préfère de travailler avec les gens passionnés que des spécialistes sans âme. Même si ces derniers sont meilleurs techniquement “un jour”.

La conclusion pour la fin :

Un bon développeur est un développeur passionné (ou tout du moins sait l’entretenir tout seul).

En fait, j’aurais du donner ce titre à mon premier post. Cela aurait évité l’ambigüité de départ.

// Thomas

Posté le jeudi 6 octobre 2011 10:57 par tja | 6 commentaire(s)
Classé sous :

Je pense que le titre en dit long sur ce que je compte écrire dans ce blog. Il y a un certain temps j’ai écris un post “Pourquoi le développeur a une “mauvaise” image ?”. Je pense que le point 4 mériterait d’être un peu plus détaillé car j’y ai un peu réfléchi et je suis arrivé à certaines conclusions que j’aimerais partager dans ce post.

Je ne pense pas qu’il faille redéfinir le mot “bon” au début de cet article. Je pense qu’à la lecture de la suite vous comprendrez mieux ce que “bon” veut dire pour moi.

Tout d’abord disons nous ceci; être développeur c’est très dur de nos jours. Toutes ces technologies qui sortent de plus en plus souvent, les nouvelles architectures, etc. Se tenir au courant de tout ceci devient presque impossible. Je pense que tout le monde aimerait apprendre de nouvelles choses comme les nouveaux langages, Frameworks, etc. Moi, aussi. Mais la journée n’a que 24h…

L’idée de se post m’est venue après avoir discuté avec un développeur lors d’un entretien d’embauche dont je ne vais citer que la partie intéressante dans le cadre de ce post  :

 

Moi : Quelle est la raison principale de ton départ de ton employeur précédent ? (phrase bateau)

Dév : En fait, j’ai fait le tour de la question lors de ma mission précédente et je ne progresse plus. De plus, on ne m’offrait aucune formation intéressante.

Moi : Ok, quels sont tes projets personnelles ?

Dév : en dehors du travail ?… ben… rien, je n’ai pas le temps.

Moi : Quel dernier livre IT tu as lu ?

Dév : ??? (en me regardant avec un air étonné “j’ai la tête à lire des livres sur le code moi ?”)

 

C’est juste un exemple, car parfois le développeur cache mieux ses motivations et ne les révèle pas aussi facilement lors d’un entretien d’embauche, mais en discutant avec d’autres développeurs avec qui vous avez l’occasion de travailler chez différents clients vous pouvez vous apercevoir que l’attitude est pour beaucoup la même.

Dans la conversation précédente j’ai exprès souligné les mots clés “je ne progresse plus” et “ne m’offrait aucune formation”.

Si tu ne progresses pas c’est de ta faute !

Premièrement, tu dois comprendre que tu es le seul responsable de ta carrière professionnelle et personne d’autre ! Ton employeur ne veut pas t’envoyer en formation ? Ne veut pas t’acheter des livres ? Ne veut pas t’envoyer dans des conférences ? Ok, ce n’est pas son obligation. C’est de ta responsabilité de le faire ! Jamais un développeur ne devrait s’attendre à ce que son employeur le fasse progresser.

Peut-être que votre employeur fait toutes ses choses là pour toi ? Mais sache qu’il te rend juste un service. Ce n’est pas une obligation (même si en France on a le droit à la formation ce n’est pas suffisant). Tu devrais trouver un moyen de le faire toi-même.

Gère ton temps pour apprendre constamment !

Le temps que tu passes dans ton travail ne peut être consacré qu'à ce que ton employeur te demande de faire. Encore une fois. Ton employeur te laisse du temps pour te former ? Pour lire des blogs ? Pour faire de la veille technologique (en dehors du cadre du projet) ? Encore une fois, il te fait une faveur !

Je pense que malgré tout ça, si on est suffisamment passionné par ce qu’on fait, on trouve toujours du temps pour apprendre et faire progresser nos connaissances. Il s’agit de nous, et notre carrière, c’est important.

Pour mieux s’organiser, je pense qu’une bonne chose est d’avoir une liste des chose à faire, à apprendre, à coder. Vous en avez une ? Tant mieux, moi, aussi. Ne t’inquiète pas si tu n’arrive pas à tout faire, mais le principe est d’en faire un peu tous les jours et de barrer des lignes au fur et à mesure de votre progression. D’ailleurs peu importe la méthode. Tu en as peut-être d’autres.

Personnellement, j’ai mis sur ma liste; des Framework à tester, des livres à acheter et à lire avec un ordre d’importance, du code que je dois écrire pour mettre en pratique ce que j’apprends, des vidéos à regarder (que je peux faire lors du temps du transport - 4h tous les jours), des blogs à écrire, en fait ce que tu veux.

Certains se disent en lisant ces lignes. Oui, mais j’ai une vie de famille et toi tu dois être un geek qui n’a rien d’autre que son travail. Faux, moi aussi j’ai une vie familiale et je fais autre chose que de travailler tout le temps. D’ailleurs à ce propos il faut savoir se relaxer également. Ne rien faire. Recharger les batteries.

Mais n’oublie pas, consacre une heure par jour pour progresser, et tu verras qu’au bout d’un certain temps, cela sera bénéfique pour toi. C’est une question d’attitude et c’est presque une obligation !

Tu aimerais te soigner chez un médecin qui n’a pas suivi l’évolution de la médecine depuis un certain temps ? Pourquoi donc la présence d’un développeur dépassé chez un client est-elle justifiée ?

Et surtout pratique ce que tu apprends !

Un chirurgien qui ne pratique pas, pense tu qu’il soit bon ? Un développeur qui ne peaufine pas son art, pense tu qu’il soit vraiment bon ? Un architecte qui ne touche plus au code ? Gare à eux !!!

Code, code, code et encore une fois code. Même si cela prend 20 minutes par jour pour faire par exemple des petits kata (TDD, algorithme, tout ce que tu veux) c’est déjà magnifique ! Mets les sur ta liste.

Investi-toi dans des projets open source, c’est un bon moyen d’apprendre non seulement la technique mais de travailler en collaboration avec des gens à travers le monde. Cela apprend également à être plus discipliné.

Il y a plein de moyens de progresser, mais n’attends pas à ce que ton employeur fasse le job à ta place.

Pour conclure, un bon développeur pour moi, est un développeur passionné par ce qu’il fait car forcément cela se voit non seulement dans son attitude mais également dans ses connaissances Sourire

 

// Thomas

Posté le mardi 4 octobre 2011 15:15 par tja | 8 commentaire(s)
Classé sous :

Lors de l’écriture de mon précédent post [ASP.NET MVC 3] Deep Dive Injection de Dépendance – ModelValidation – Partie 8 je me suis aperçu d’un petit problème avec l’implémentation de IDependencyResolver par le biais de Unity pour la résolution des services multiples. C’était le cas lors de l’enregistrement dynamique de ModelValidatorProvider qui ressemble à ceci :

   1: IUnityContainer container = new UnityContainer();
   2:            
   3: // enregistrement dynamique
   4: container.RegisterType<ModelValidatorProvider, ConventionValidatorProvider>();
   5:  
   6: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

En fait ceci tout simplement ne fonctionnait pas. Rien ne se passait. Après avoir perdu une petite demi-heure j’ai commencé à regarder l’implémentation de mon IDependencyResolver qui pour rappel est comme suit :

   1: public class UnityDependencyResolver : IDependencyResolver
   2: {
   3:     private readonly IUnityContainer _container;
   4:  
   5:     public UnityDependencyResolver(IUnityContainer container)
   6:     {
   7:         if (container == null)
   8:             throw new ArgumentNullException("container", "The container cannot be null");
   9:  
  10:         _container = container;
  11:     }
  12:  
  13:     public object GetService(Type serviceType)
  14:     {
  15:         object instance;
  16:  
  17:         try
  18:         {
  19:             instance = _container.Resolve(serviceType);
  20:         }
  21:         catch
  22:         {
  23:             instance = null;
  24:         }
  25:  
  26:         return instance;
  27:     }
  28:  
  29:     public IEnumerable<object> GetServices(Type serviceType)
  30:     {
  31:         IEnumerable<object> instances;
  32:  
  33:         try
  34:         {
  35:             instances = _container.ResolveAll(serviceType);
  36:         }
  37:         catch
  38:         {
  39:             instances = new object[] { };
  40:         }
  41:  
  42:         return instances;
  43:     }
  44: }

Ce qui nous intéresse plus particulièrement c’est la méthode GetServices qui renvoie une liste de services pour un type donné. Lors du debug pour le type ModelValidatorProvider  je m’attendais à avoir mon ConventionValidatorProvider que j’ai enregistré plus tôt mais la ligne _container.ResolveAll(serviceType) s’obstinait à renvoyer null. Un petit coup d’œil sur MSDN pour trouver la solution :

MSDN_Unity

Ce qui nous intéresse c’est la phrase soulignée en rouge. Cette méthode NE RETOURNE pas d’instance pour une registration par défaut. A chaque fois je me fais avoir par ça. Il faut dire que pour moi ceci ne respecte pas le principe de PoLS. Je m’attends à ce que toutes les instances soient retournées ! C’est ResolveAll et non ResloveAllButNotDefault !

Pour que notre registration fonctionne j’ai juste modifié la ligne d’enregistrement comme ceci :

   1: container.RegisterType<ModelValidatorProvider, ConventionValidatorProvider>("convention");

Je n’aime pas du tout ça mais au moins cela fonctionne Sourire

// Thomas

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

Le code source de cet article se trouve ici vs2010solution_43E39653.

ModelValidation et DI.

C’est un service d’enregistrement multiple. Le point d’enregistrement statique étant la collection ModelValidatorProviders.Providers.

MVC 2 : introduction de ModelValidatorProvider qui permet d’influencer la validation côté client et côté serveur. Il faut juste implémenter une classe qui dérive de celle-ci.

MVC 3 : Les validators sont localisables via le DependencyResolver.

Les fournisseurs de Model validator, analysent les modèles et retournent les validators qui s’assurent que les modèles sont valides (possible de retourner les clients validation hints consommés par du JS).

Nouveautés MVC 3 : La logique dans ModelValidatorProviderCollection qui implémente ModelValidatorProviders.Providers a été mise à jour pour consulter le DependencyResolver en faisant appel vers GetServices(typeof(ModelValidatorProvider)) et ajoutant les services trouvés à la liste des services enregistrés statiquement. Tous les validators enregistrés participent à la validation.

Afin d’écrire votre propre validator de modèle, la chose à faire est de dériver votre nouvelle classe de la classe abstraite ModelValidatorProvider et d’implémenter sa méthode unique GetValidators :

   1: public abstract class ModelValidatorProvider 
   2: {
   3:     public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
   4: }

Cette méthode est assez simple. Etant donnée le cotexte du contrôleur (ControllerContext) qui n’est rien d’autre que le contexte d’appel de la requête Http courante et le ModelMetadata qui décrit l’objet qui doit être validé vous devez retourner 0 ou plus de validators qui seront exécutés pour valider votre modèle.

Créer son propre validator de modèle.

Un validator est une classe dérivée de la classe abstraite ModelValidator. Cette classe accepte une instance de ModelMetadata et de ControllerContext comme arguments du constructeur. Pour écrire son validator de modèle il faut implémenter la méthode Validate pour la validation côté serveur et éventuellement la méthode GetClientValidationRules pour supporter la validation côté client.

Nous allons dans un premier temps implémenter notre propre validator provider et pour cela nous n’allons pas dériver directement de la classe ModelValidatorProvider mais de la classe AssociatedModelValidator. Cette classe apporte quelques fonctionnalités de base comme la lecture des attributs attachés au modèle.

Pour notre application d’exemple nous allons utiliser le même modèle que dans le post précédent : http://blogs.developpeur.org/tja/archive/2011/09/14/asp-net-mvc-3-deep-dive-injection-de-d-pendance-modelmetadata-partie-7.aspx.

Pour rappel, le modèle ressemble à ceci :

PostInput

Nous n’allons pas établir une convention spéciale de validation mais à titre d’exemple nous allons imaginer que lorsqu’une propriété du modèle s’appelle “PostTitle” nous allons vérifier que la valeur est requise, sinon la validation échoue.

Tout d’abord créons notre Provider :

ValidatorProvider

Comme vous pouvez le constater, le code est ultra simple. Lors de la validation du modèle les providers de validation ont la possibilité de renvoyer tous les validators lorsque certaines conditions sont rencontrées. Dans notre cas la condition est le nom de la propriété du modèle “PostTitle”. Lorsqu’une telle propriété est rencontrée nous allons vérifier que sa valeur est bien renseignée (ce qui revient à décorer la propriété avec l’attribut Required en temps normal). Lorsque cette condition est rencontrée nous renvoyons le validator PostTitleRequiredValidator pour valider la données de la propriété. Voici à quoi ressemble cette classe :

ModelValidator

Comme vous pouvez le constater, nous dérivons notre classe de la classe abstraite ModelValidator et implémentons les méthodes Validate pour la validation côté serveur et éventuellement GetClientValidationRules pour supporter la validation côté client.

Tout ce qui nous reste à faire maintenant c’est d’enregistrer notre validator :

1. Enregistrement statique
   1: // enregistrement statique
   2: ModelValidatorProviders.Providers.Add(new ConventionValidatorProvider());

Nous ajoutons tout simplement l’instance de notre provider dans la collection statique des providers.

2. Enregistrement dynamique
   1: IUnityContainer container = new UnityContainer();
   2:            
   3: // enregistrement dynamique
   4: container.RegisterType<ModelValidatorProvider, ConventionValidatorProvider>("convention");
   5:  
   6: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

A la ligne 5 nous enregistrons un mapping nommé qui est nécessaire pour notre implémentation de DependencyResolver avec Unity que j’explique dans ce post : [ASP.NET MVC 3] Unity et DependencyResolver – Enregistrement des services multiples.

Il ne nous reste à présent de tester notre implémentation :

TestValidator

Lorsque je clique sur le bouton “Save” j’obtiens l’erreur “Ce champ est requis !!!” qui provient de notre validator personnalisé.

Comme vous pouvez le constater il n’est pas difficile d’étendre le fonctionnement normal de MVC quant il s’agit de la validation.

Dans le prochain post je vais parler de ValueProviders.

// Thomas

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

Le code source de cet article se trouve ici vs2010solution_43E39653.

ModelMetadata introduit dans le Framework MVC2 bénéficie de quelques améliorations dans le Framework MVC3. Nous allons les voir dans les détails.

Support natif de Metadata.

ASP.NET MVC permet de créer la métadata correspondante au modèle de vue. La métadata est utilisée pour réaliser un certain nombre de tâches au Framework MVC comme par exemple la validation de données ou un affichage particulier du modèle dans une vue. Je ne vais pas rentrer dans les détails expliquant les bases de métadata mais pour avoir plus d’informations. Un article complet à ce sujet se trouve ici : http://msdn.microsoft.com/fr-fr/magazine/ee336030.aspx.

ModelMetadata

Il ne peut y avoir qu’un seul ModelMetadata provider enregistré dans le Framework MVC. C’est un enregistrement d’un service unique qui a d’ailleurs été introduit dans le MVC 2. Le point d’enregistrement statique est ModelMetadataProviders.Current.

Le ModelMetadata provider analyse le modèle pour retourner les informations de métadata comme par exemple les display names, chaines de formatage, etc.

Une classe qui dérive de ModelMetadataProvider fourni des informations métadata propos des modèles.

Nouveauté MVC 3 : Les métadata providers sont localisables via le DependencyResolver. La logique a été mise à jour pour d’abord essayer de localiser une implémentation de ModelMetadataProvider en faisant appel vers DependencyResolver.GetService(typeof(ModelMetadataProvider)). Si le service n’existe pas dans DependencyResolver il y a le fallback vers l’enregistrement statique (exception si enregistré dans les deux car un seul ModelMetadataProvider est autorisé à être enregistré).

Ecrire son propre ModelMetadataProvider basé sur les conventions.

Comme vous allez le constater l’écriture d’un ModelMetadataProvider n’est pas très difficile.

Un mot sur le ModelMetadataProvider par défaut.

Dans le Framework MVC, la classe ModelMetadataProvider définie la classe DataAnnotationsModelMetadataProvider en tant que métadata provider par défaut. Nous n’avons pas besoin d’utiliser cette classe directement. Si nous voulons développer notre propre provider de métadata basé sur des attributs nous pouvons hériter de cette classe de base.

Comme dans la plupart des composants du Framework MVC, le système de métadata est complément personnalisable. Le Framework est fourni avec une implémentation par défaut mais vous avez la possibilité de l’étendre ou de le remplacer complément.

image

En bleu, les providers implémentés par défaut.

En vert, vos providers personnalisés.

Comme vous le pouvez constater d’après ce schéma, les attributs data annotations ainsi que le DataAnnotationsModelMetadataProvider peuvent être complètement remplacés s’il ne correspondent pas à vos besoins. Pour créer votre provider vous pouvez hériter des classes de base suivantes :

  • ModelMetadataProvider : La classe abstraite de base pour tous les métadata providers.
  • AssociatedMetadataProvider : Une autre classe de base qui hérite déjà de la classe ModelMetadataProvider  et qui apporte quelques fonctionnalités. Elle implémente quelques fonctions utiles permettant d’obtenir une liste d’attributs associées avec chaque propriété du modèle. Ils sont obtenus à partir des classes configurés via MetadataType. Tout ce que vous avez à faire est d’overrider une seule méthode CreateMetadata et retourner une instance de ModelMetadata basée sur une collection d’attributs fournies, le type du modèle, nom de la propriété, etc. etc.
  • DataAnnotationsMetadataProvider : C’est le provider métadata par défaut. Si vous héritez de cette classe vous obtenez le support pour les attributs standards du System.ComponentModel. Comme dans le point précédent vous n’aurez qu’à overrider la méthode CreateMetadata.

Passons maintenant à la pratique.

La convention.

Nous allons créer un simple formulaire qui permet de rentrer un post/article dans une vue prévue à cette effet. Voici à quoi ressemble la classe du superbe modèle de vue qui contiendra les données rentrées par l’utilisateur dans la vue en question (Attention c’est violant !!!).

PostInput

Il sera plus facile maintenant de parler de conventions que nous allons appliquer pour nos modèles :

  • Champs cachés : si nous voulons afficher un champ caché le nom de la propriété du modèle en question doit commencer par le mot “Hidden”.
  • Champs multiligne : si nous voulons afficher un champs multiligne (TextArea), le nom de la propriété du modèle en question doit commencer par le mot “TextArea”
  • Affichage du nom : le nom du champ affiché dans la vue ne doit bien évidemment pas comporter les prefixes “Hidden” et “TextArea” et de plus devrait transformer le nom de la propriété de Pascal-Case en mots déparés par des espaces blancs. Donc par conséquence la propriété TextAreaPostBody devrait s’afficher en “Post Body”.

Je pense que pour commencer cela sera suffisant.

Mais d’abord vérifions comment cela se comporte avec le modèle métadata provider par défaut (DataAnnotationsModelMetadataProvider). Si on affiche le modèle dans la vue nous obtenons ceci :

FormWithoutModelMetadata

Le code de la vue est très simple :

CodePostView

Nous appelons le helper EditorForModel pour afficher noter formulaire d’édition. Cela, comme vous pouvez le constater ne correspond pas du tout à ce qu’on voudrait comme affichage défini dans les conventions ci-dessus. Nous allons donc écrire notre propre provider de métadata basé sur nos conventions.

Ecriture du provider métadata personnalisé.

Puisque le provider par défaut DataAnnotationsModelMetadataProvider ne correspond pas à nos besoin, nous allons construire notre propre provider de métadata dont le code est montrée ci-dessous :

ConventionMetadataProvider

Comme indiqué plus haut de l’article, il est plus facile de créer un provider de métadata personnalisé en héritant directement de la classe AssociatedMetadataProvider. Nous avons besoin uniquement de surcharger la méthode CreateMetadata et de retourner une nouvelle instance de ModelMetadata.

Quelques explications rapides.

  • ligne 12 : création du modèle métadata de base par rapport au paramètres fournies à la méthode CreateMetadata par le Framework MVC. Ce modèle sera retourné si les critères des filtres concernant le nom de la propriété ne correspondent pas à notre convention établie plus tôt.
  • ligne 17 : vérification si le nom de la propriété commence par le mot “Hidden”. Si c’est le cas, nous indiquons quel template faut il générer pour cette propriété par le biais de la propriété TemplateHint. La chaine que vous renseignez (ici “HiddenInput”) correspond au nom du template qui est natif au Framework MVC où personnalisé par vous. La liste de noms de templates natifs se trouve ici : http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html. Ensuite à la ligne 21 nous enlevons le prefix “Hidden” du nom de notre propriété afin qu’il ne s’affiche pas dans l’interface.
  • ligne 24 : traitement similaire que pour le “if” à la ligne 17 mais cette fois-ci pour le text area.
  • ligne 30 : transformation de noms de nos propriétés du type “PostBody” en “Post Body”.
  • ligne 31 : on retourne notre modèle basé sur nos conventions.

Ceci n’est pas suffisant. Il faut maintenant enregistrer notre provider de métadata.

Enregistrement statique.

L’enregistrement statique est très simple. Voici la ligne à ajouter dans le Global.asax (ou autre bootstrapper)

StaticModelMetaDataProvider

Ce mode statique existe depuis le MVC 2 et est disponible si vous n’avez pas besoin de DI dans votre provider. Il existe également un mode dynamique.

Enregistrement dynamique.

L’enregistrement dynamique permet de bénéficier du DI au sein de vos providers ce qui est une bonne chose.

DynamicModelMetadataProvider

Les lignes 45 et 46 ne sont pas nécessaires, donc n’en tenez pas compte.

Le code de la vue reste le même.

Dans le prochain article je vais continuer sur le sujet. Ne vous inquiétez pas, bientôt ça sera la fin de cette série !

// Thomas

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

Bien que je n’ai pas écrit depuis long temps, il faut bien terminer la série avant que MVC 4 sorte.

FilterProviders

L’enregistrement des filtres se fait grâce au point d’enregistrement statique qui est une collection FilterProviders.Providers. Cette collection fournie une méthode de façade GetFilters qui agrège des filtres de tous les providers dans une liste unique. L’ordre d’enregistrement n’est pas important.

Par défaut il y a trois fournisseurs de filtres enregistrés dans l’application :

  • Global filters (GlobalFilters.Filters).
  • Filtres attributs (FilterAttributeFilterProvider).
  • Instances de contrôleurs (ControllerInstanceFilterProvider) : et oui les contrôleurs et leurs actions sont considérés en tant que filtres.

De plus la façade GetFilters récupère toutes les instances implémentant l’interface IFilterProvider enregistrés dans le dependency resolver en utilisant la méthode DependencyResolver.Current.GetAllInstances<IFilterProvider>().

L’ordre des filtres

L’ordre des filtres est déterminé par rapport à la métadata contenue dans la classe Filter. L’ordre est d’abord déterminé par le numéro d’ordre puis par le scope.

Voyons d’abord la signature de la méthode IFilterProvider .

   1: public interface IFilterProvider 
   2: {
   3:     IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
   4: }

Cette méthode permet de récupérer l’ensemble des filtres enregistrés. La classe Filter et l’énumération FilterScope  sont définies comme ceci :

   1: public class Filter 
   2: {
   3:     public const int DefaultOrder = -1;
   4:  
   5:     public Filter(object instance, FilterScope scope, int? order = null);
   6:  
   7:     public object      Instance { get; }
   8:     public int         Order    { get; }
   9:     public FilterScope Scope    { get; }
  10: }
  11:  
  12: public enum FilterScope 
  13: {
  14:     First = 0,
  15:     Global = 10,
  16:     Controller = 20,
  17:     Action = 30,
  18:     Last = 100,
  19: }

Comme nous avons dit un peu plus tôt, le controller est un filtre également avec l’ordre = Int32.MinValue et Scope First afin de s’assurer qu’ils s’exécutent en premier.

Note : Il est impossible d’enregistrer les GlobalFilters avec le dependency resolver.

La DI dans la pratique

Nouveautés MVC3 : FilterAttributeFilterProvider permet de garantir l’injection de dépendances dans les filtres.

La responsabilité de la collection des FilterProviders est de retourner les instances des filtres au Framework MVC en s’assurant de la DI où possible. Voici un exemple d’un provider des filtres avec Unity :

   1: public class UnityFilterAttributeFilterProvider : FilterAttributeFilterProvider
   2: {
   3:     private readonly IUnityContainer _container;
   4:  
   5:     public UnityFilterAttributeFilterProvider(IUnityContainer container)
   6:     {
   7:         if (container == null)
   8:             throw new ArgumentNullException("container", "The container cannot be null.");
   9:  
  10:         _container = container;
  11:     }
  12:  
  13:     protected override IEnumerable<FilterAttribute> GetControllerAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
  14:     {
  15:         var attributes = base.GetControllerAttributes(controllerContext, actionDescriptor);
  16:         
  17:         foreach (var attribute in attributes)
  18:         {
  19:             _container.BuildUp(attribute.GetType(), attribute);
  20:         }
  21:  
  22:         return attributes;
  23:     }
  24:  
  25:     protected override IEnumerable<FilterAttribute> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
  26:     {
  27:         var attributes = base.GetActionAttributes(controllerContext, actionDescriptor);
  28:         foreach (var attribute in attributes)
  29:         {
  30:             _container.BuildUp(attribute.GetType(), attribute);
  31:         }
  32:  
  33:         return attributes;
  34:     }
  35: }

Ce qui est intéressant de remarquer ce que dans les surcharges des méthodes GetControllerAttributes et GetActionAttributes nous utilisons l’instance de notre containeur Unity pour injecter de dépendances des services dans des attributs (filtres) existant tant qu’au niveau du contrôleur qu’au niveau des ses actions. Ceci est possible grâce à la méthode BuildUp de Unity.

Le Framework MVC est responsable de la création des filtres donc impossible d’utiliser l’injection de dépendance par défaut. Il faut passer par l’injection de setters. Voici un exemple d’un filtre nécessitant l’injection de dépendance :

   1: public class InjectedFilterAttribute : ActionFilterAttribute
   2: {
   3:     [Dependency]
   4:     public IOrderService OrderService { get; set; }
   5:  
   6:     public override void OnResultExecuted(ResultExecutedContext filterContext)
   7:     {
   8:         filterContext.HttpContext.Response.Write(String.Format("<p>The filter says 2 + 3 is {0}.</p>", OrderService.CalculateVAT(10)));
   9:     }
  10: }

Le seul désavantage de cette technique est qu’on doit décorer le setter par lequel l’injection de dépendance doit se faire avec l’attribut Dependency ce qui nous couple un peu au containeur DI utilisé. Mais c’est le moindre mal par rapport au bénéfices qu’on tire de l’injection de dépendance.

Rappel MVC2 : Pour de l’injection de dépendance on passait par ControllerActionInvoker.

Il ne nous reste que d’enregistrer notre provider des filtre dans le bootstrapper de l’application :

   1: // DI in Filters
   2: // Supprimer les anciens providers de filtres.
   3: var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
   4: FilterProviders.Providers.Remove(oldProvider);
   5:  
   6: // Provider de filtres vient d'un enregistrement statique
   7: var provider = new UnityFilterAttributeFilterProvider(container);
   8: FilterProviders.Providers.Add(provider);
   9:  
  10: // Ou d'un enregistrement de Dependency Resolver.
  11: container.RegisterInstance<IFilterProvider>("attributes", provider);
  12: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

Dans un post future je vais donner un cas d’utilisation concret d’un tel provider des filtres.

// Thomas

Depuis des années j’en entends parler de cette conférence mais jamais en négatif. Cette année j’ai décidé de vérifier par moi même. J’ai donc acheté les billets, réservé l’hôtel et suis parti à la découverte. Je ne suis jamais allé en Norvège donc en même temps cette visite me permet d’avoir un aperçu de ce pays et de revenir une autre fois pour le visiter. En attendant la cible principale est la conférence NDC 2011.

J’embarque donc dans l’avion (retard 2h) et j’arrive à l’aéroport de Oslo vers minuit. Bien qu’à Paris on ait plein de moyens de transport différents pour se rendre de l’aéroport à la capitale et vice-versa, à Oslo la gare ferroviaire est directement dans l’aéroport. C’est agréable de ne pas se demander où aller ou de trimbaler très loin là valise. Les taxis sont à éviter car vu les prix en Norvège et la distance à parcourir (50km) vous pouvez facilement exploser votre budget. Les prix des taxis à Paris c’est rien à côté ! La première impression qui vous frappe quand vous montez dans le train c’est la propreté (qui d’ailleurs se retrouve également dans les rues) ! De plus la finition bois, alu ajoute un effet supplémentaire de standing, cool ! De plus je ne vois aucune dégradation (tags, etc.) laissée par des passagers comme ce qu’on peut voir très souvent ici. Enfin, à l’arrivée du train, je continue mon chemin à l’hôtel qui n’est qu’à 10 min de la gare.

Mais bon, je suis là pour vous parler des mes impressions quant à la conférence et non ma visite de Oslo.

Premier jour – mercredi

C’est le début de la conférence. En arrivant un petit déj scandinave inclus dans le prix du billet et le départ pour la Keynote de Scott Guthrie. Pour cette journée je privilégie les conférences autour de l’architecture, tests unitaires et de l’agile.

9h – 10h Cloud Computing and Azure

Keynote de Scott Guthrie. A vrai dire la partie la moins intéressante et la plus ennuyeuse de toute la conférence. Des petites solides pour promouvoir Azure faire un peu de promo et voilà. Rien de génial donc je ne vais pas en dire davantage. Cela ressemble énormément à certaines sessions TechDays en France SourireC’est normal en même temps, il fait parti de MS.

Après le keynote j’ai enfin pu me balader un peu entre les stands et taper quelques discussion avec les gens que j’ai d’abord suivi sur twitter. Il y avait des stands de sponsors de NDC principalement, mais également des stands pour vous servir de la glace, des hotdogs et du café (nespresso bien sûr).

Il est temps de se rendre à la première conférence mais j’ai juste trop de mal à me décider où aller. Il y a plein de speakers et conférences intéressantes entre Uncle bob et Kevlin Henney il est très difficile de choisir.

 

10h20 – 11h20 - Things You Can Learn from 101 Things I Learned in Architecture School : Kevlin Henney

J’ai donc choisi d’aller voir la conférence de Kevlin Henney sur l’architecture. Il faut avouer que l’homme a l’habitude de donner des conférences car il est très à l’aise et gère magnifiquement bien son timing. De plus ce qu’il raconte est très captivant et intéressant.

Le but de cette session et de faire le parallèle entre ce qu’il a apprit en lisant un livre sur l’architecture des bâtiments et l’architecture de logiciels. Il voulait remettre en cause la manière de penser et de révéler quelques vérités intéressantes (désolé si elles sont sorties du contexte) :

“Les limites encouragent la créativité” – plus il y aura de limites, plus on sera créatif.

“Gérez votre ego ! L’architecte logiciel est uniquement un titre attractif pour les recruteurs” – Il expliquent que la hiérarchie entre les architectes logiciels et les développeurs est trop importante. Chaque architecte logiciel devrait être à l’écoute des développeurs des leurs idées car tout seul il fera moins bien que lorsqu’il travaille collectivement. Il n’a pas la science infuse et ni le monopole sur la créativité.

“Scrum est une collection des règles très ennuyeuses que tu dois changer en les inspectant continuellement et adaptant” – Il ne faut jamais adapter l’équipe aux règles Scrum, mais adapter Scrum à la manière de travailler de l’équipe et du business.

“La beauté des logicielles réside dans la harmonie entre les composants et non dans les composants eux-mêmes” – Oui, il faut voir la beauté en regardant l’ensemble et non juste les briques qui composent le logiciel. Car justement la beauté réside dans la manière les briques s’assemblent et travaillent ensemble.

“Chaque décision d’architecture doit au moins être justifiée par deux raisons différentes” – Il ne faut pas prendre de décisions à la légère. Si on arrive à justifier notre décision de deux manières différentes alors il y a beaucoup de chances que nous avons pris la bonne décision.

“Nous somme au 21ème siècle et tout le monde se fout de savoir qui étaient/sont vos parents alors appliquons la même chose aux classes. N’utilisez pas l’héritage” – Beaucoup de développeurs utilise l’héritage à outrance, il vaut mieux privilégier la composition.

Toute cette session était bien entendu basée sur des exemples concrets de la vraie vie. Il y’en avait beaucoup d’autres !

 

14h40 – 12h40 - Designing Software with SOLID principles : Daniel González García

Un autre choix difficile à faire vue les sessions qui se tiennent en parallèle. Mais au final c’est une session également très intéressante concernant les principes de développement SOLID avec des exemples concrets. Quoi dire de plus ? Il n’y a pas eu de nouveauté pour ceux qui connaissent très bien ces principes mais un petit rafraichissement n’est pas de trop. J’ai fait une petite introduction là dessus, que je n’ai pas pu terminer d’ailleurs, mais ceux qui ne connaissent pas pourront avoir une idée de quoi il s’agit : http://blogs.developpeur.org/tja/archive/2009/10/25/architecture-est-ce-que-votre-code-est-s-o-l-i-d-e-introduction.aspx.

Deux phrases important à se rappeler :

“Non respect de la loi LOD, casse l’encapsulation”

“Séparation de concepts. Agissez que sur vos données propres” – Il s’agit de ne pas agir ou prendre des décisions par rapport aux données des objets avec lesquels on collabore.

 

Et voilà, c’est la pause déjeuner. Bien sûr on ne peut pas dire que c’est un déj 4 étoiles mais cela est suffisant pour se sustenter correctement et être prêt pour la suite qui s’annonce pas très mal. J’ai pu discuter avec des gens intéressant, avoir une petite discussion avec Uncle Bob au stand des glaces ou avec des développeurs norvégiens. D’ailleurs quand vous parler avec les norvégiens vous vous vite apercevez que l’anglais c’est leur deuxième langue, car ils parlent tous très très bien cette lanque. Cela ne m’étonne pas que NDC et les conférences en anglais ont été vite adoptées dans ce pays.

 

Il faut choisir une autre conférence pour commencer l’après midi. Potentiellement il y a deux conférences qui m’intéressent. Une sur le DDD (Domain Driven Design) et une autre sur les tests unitaires avec BDD (Behaviour Driven Development). Il y a également celle de Douglas Crockford sur EcmaScripts qui peut être intéressante mais n’y connaissant rien je préfère de laisser tomber. La conférence de ScottGu’ sur ASP.NET MVC 3 et EF Code First me parait trop basique donc finalement je choisi d’aller voir l’équipe de SpecFlow et le BDD.

 

15h – 16h - Building .NET applications with BDD : Gaspar Nagy et Jonas Bandi

Quoi dire de plus ? SpecFlow présenté par leurs créateurs ! Génial ! Je ne vais pas me répéter car assistant à la même conférence que Rui, il a déjà fait le boulot de décrire ce qu’on a vu et entendu pendant cette conférence. Voici le lien : http://www.rui.fr/event/nec-2011-day-1-specflow-pragmatic-bdd-for-net/2011/06/08/.

Si vous voulez voir les slides de cette conférence, voici le lien : http://blog.jonasbandi.net/2011/06/our-ndc-2011-slides-specflow-bdd.html.

 

Et voilà, il est temps de choisir une conférence suivante. A vrai dire c’est le créneaux où le choix à faire est le plus difficile. Entre Rob Ashton et “Document databases with ASP.NET MVC” ou Ian Robinson et “Business Architecture Foundations of IT” ou encore par exemple Hadi Hariri (JetBrains) “Dynamic in a Static World” j’ai finalement choisi :

 

16h20 – 17h20 - Introducing The FLUID Principles : Kevlin Henney Anders Norås

Titre provocateur ! Excellent Kevlin de la sessions précédente accompagné cette fois ci par Aders Noras que je ne connais pas bien. Ils commencent par faire quelques blagues (qui d’ailleurs ne s’arrêtent pas pendant la durée de la sessions) et déroulent le sujet pour introduire les principes FLUID ! C’est une première mondiale SourireDe quoi s’agit t-il ? C’est une contradiction de SOLID SourireEn fait les principes FLUID c’est :

Functional

Loose

Unit Testable

Introspective

Dempotent (d’ailleurs beaucoup de rire dans la salle comment il ont faire rentrer le mot Idempotent)

 

En gros, à travers les exemples dans divers langages de programmation il démontrent chaque principe afin de fabriquer le software à l’épreuve du temps.

Vous trouverez plus d’informations dans les slides de la conférence qui sont disponibles ici : http://www.slideshare.net/anoras/ndc-2011-the-fluid-principles.

 

Une dernière conférence de la journée à venir. Hadi Hariri présente “Refactoring Legacy Code Bases” qui m’intéresse, Shay Friedman “ASP.NET MVC 3 Vs. Ruby on Rails 3”  ou Uncle Bob sur “The Transformation Priority Premise”. Finalement je décide d’accompagner Rui à la conférence de Jon Skeet

 

17h40 – 18h40 – Async 101 : Jon Skeet

A vrai dire cette conférence m’a tué SourireL’auteur de C# in Depth sait de quoi il parle ! Ca a commencé doucement en présentant les différentes façons d’utilisation de async dans le code C# mais à vrai dire je me suis un peu perdu vers la fin lorsqu’il a abordé l’exécution de plusieurs méthodes async en parallèle ce qui d’ailleurs n’est pas supporté out of the box par le framework. Il a du pour cela écrire une extension de Linq qui marche très bien mais qui n’était pas si facile à comprendre à ce stade de la journée Sourire. Voilà j’en dirai pas plus à ce sujet. Je vous invite à consulter son blog où vous trouverez beaucoup d’informations à ce sujet Sourire: http://msmvps.com/blogs/jon_skeet/.

 

Bilan de la première journée

Après cette première journée, je suis très content de constater que la conférence est très intéressant et traite des sujet pointus avec des gens qui savent de quoi ils parlent. J’ai eu quelques regrets de ne pas pouvoir me cloner et assister à plusieurs conférences en même temps, mais heureusement bientôt il y aura des webcasts disponibles en téléchargement. Ce qui m’a plu également est le fait de pouvoir choisir les conférences au dernier moment. Il y avait des places pour tout le monde donc on pouvait facilement changer d’avis au dernier moment. La disponibilité des intervenants et leur envie de rencontrer des gens pour discuter était également un plus non négligeable car il était possible des les questionner sur d’autres sujets intéressant.

Il est maintenant temps de rentrer à l’hôtel et de se préparer pour la journée suivante.

 

// Thomas

Posté le mercredi 15 juin 2011 13:44 par tja | 0 commentaire(s)
Classé sous :

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

Ca fait long temps que je n’ai pas écrit sur ASP.NET MVC 3 et vu que ma série concernant l’injection de dépendance n’est pas terminée, j’en profite pour vous parler de vues/moteurs de vues dans MVC 3.

Le code source de cet article se trouve ici vs2010solution_43E39653.

Tout d’abord commençons avec un petit rappel concernant les moteurs de vue.

IViewEngine/IView

Depuis la version 1 de MVC nous avons la possibilité d’enregistrer nos moteurs de vues dans une collection statique ViewEngines.Engines. L’ordre d’enregistrement est très important car le premier moteur qui arrive à afficher la vue gagne.

Nouveauté MVC 3 : Les méthodes FindView et FindPartialView de la classe ViewEngineCollection ont été mis à jour pour prendre en charge la nouvelle fonctionnalité d’injection de dépendance. Le Framework MVC vérifie les moteurs de vues enregistrés dans la collection statique ViewEngines.Engines mais également tous les moteurs enregistrés dans le DependencyResolver grâce à la méthode DependencyResolver.Current.GetAllInstances<IViewEngine>(). Les moteurs de vues enregistrés dans DependencyResolver sont prioritaires par rapport à ceux enregistrés dans la collection statique.

Si vous avez un moteur de vues customisé :

   1: public class CustomViewEngine : IViewEngine {
   2:     // votre implémentation de IViewEngine 
   3: }

vous pouvez l’enregistrer dorénavant de cette manière :

   1: var container = new UnityContainer();
   2: container.RegisterType<IViewEngine, CustomViewEngine>("CustomViewEngine");
   3: DependencyResolver.SetResolver(new UnityMvcServiceLocator(container));

La responsabilité des moteurs de vues est de générer les vues. Dans le cas des moteurs Razor ou de WebForms les classes sont générées à partir de la source de la vue (fichier .aspx ou .cshtml). Ces classes dérive implicitement des classes de bases dont le moteur de vues est dépendant (ViewPage, ViewMasterPage, ViewUserControl ou WebViewPage).

ViewPage

Avant MVC 3 les classes générées pour les vues ne supportaient pas l’injection de dépendance car leur création était trop ancrée dans l’implémentation des moteurs de vues.

Nouveauté MVC 3 : Une mise à jour a été fait dans les moteurs de vues pour supporter l’injection de dépendance. La création de vues est donc basée sur le DependencyResolver qui lorsqu’il n’arrive pas à localiser une vue concrète passe la main au fonctionnement standard Activator.CreateInstance() comme c’était le cas dans les versions précédentes.

L’injection de dépendance se passe via les propriétés et non le constructeur comme dans d’autres cas. C’est à cause d’une limitation technique dans ASP.NET et la manière dont les classes de vues sont générées à partir du markup.

Pour illustrer le fonctionnement de l’injection de dépendance nous allons créer un petit exemple. Le but de l’exemple et de vous démontrer ce qu’il est possible de faire et non de dire que c’est de cette manière là qu’il faut le faire car il faut faire très attention avec ce gendre d’artéfacts.

Etape 1 : Création de la classe dérivée qui contiendra le service qu’on veut utiliser dans la vue

   1: public class InjectedViewPage : WebViewPage
   2: {
   3:     [Dependency]
   4:     public IOrderService OrderService { get; set; }
   5:  
   6:     public override void Execute()
   7:     {
   8:     }
   9: }

Nous dérivons tout simplement de la classe WebViewPage et ajoutons une propriété par laquelle l’implémentation concrète de la dépendance IOrderService sera injectée. L’attribut [Dependency] est utilisé par notre containeur DI Unity afin d’identifier les propriétés où l’injection doit être faite.

Etape 2 : Ajouter une action dans le contrôleur qui renvoie la vue avec le service injecté.

   1: public ActionResult TestInjectedView()
   2: {
   3:     return View();
   4: }

Cela est très simple. Juste une action en plus. Remarquez que je ne passe aucune donnée à la vue.

Etape 3 : Ajouter la vue

   1: @inherits UI.InjectedViewPage
   2: @{
   3:     ViewBag.Title = "TestInjectedView";
   4: }
   5:  
   6: <h2>TestInjectedView</h2>
   7: <p>
   8: Resultat de l'appel au service @@IOrderService avec un paramètre = 10<br /><br />
   9: @OrderService.CalculateVAT(10);
  10: </p>

A la ligne 1 : nous indiquons que nous voulons que la vue hérite de notre class InjectedViewPage.

A la ligne 9 : nous utilisons tout simplement le service qui a été injecté par le DependencyResolver.

Nous pouvons maintenant tester notre application en accédant à l’url (dans mon cas) http://localhost/Home/TestInjectedView :

InjectedViewPageResult

Comme vous pouvez constater l’injection du service c’est correctement passée car on affiche un résultat correct.

Attention : vous devez absolument utiliser le DependencyResolver pour que cela fonctionne.

   1: container.RegisterType<IControllerActivator, UnityControllerActivator>();
   2: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

Version améliorée avec IViewPageActivator

Ce qui mérite d’être mentionné c’est l’introduction d’une nouvelle classe de base BuildManagerCompiledView et du nouveau service IViewPageActivator.

BuildManagerCompiledView  peut être utile aux développeurs qui créent les moteurs de vues utilisant le BuildManager afin de convertir les fichiers de markup dans des classes.

IViewPageActivator instancie les classes de vues qui ont été compilées à partir des fichiers de markup. IViewPageActivator est localisable via DependencyResolver. Son enregistrement est unique et il n’y a pas de point d’enregistrement statique.

En voici là signature :

   1: public interface IViewPageActivator {
   2:     object Create(ControllerContext controllerContext, Type type);
   3: }

La logique a été mise à jour pour consulter DependencyResolver en appelant GetSerivce(typeof(IViewPageActivator)) et utiliser le service s’il est présent. S’il n’y a pas d’instance de IViewPageActivator, DependencyResolver créera un type concret en appelant GetService(viewPageType). Si cela ne réussit pas alors le comportement de MVC 2 sera utilisé avec Activator.CreateInstance.

Pour résumer, l’interface IViewPageActivator a été introduite pour laisser plus de contrôle aux développeurs sur la manière dont le pages sont instanciées avec l’injection de dépendance. Comme vous pouvez les constater sur les images ci-dessous, deux classes en été modifiées WebFormView et RazorView qui possèdent un paramètre supplémentaire dans le constructeur IViewPageActivator qui permet l’injection de dépendance.

WebFormView :

WebFormView

RazorView

RazorView

Un petit exemple illustre cette nouvelle fonctionnalité :

Etape 1 : Création d’une classe implémentant IViewPageActivator

Nous allons l’appeler UnityViewPageActivator car Unity sera utilisé pour fournir les dépendances nécessaires.

   1: public class UnityViewPageActivator : IViewPageActivator
   2: {
   3:     public object Create(ControllerContext controllerContext, Type type)
   4:     {
   5:         return DependencyResolver.Current.GetService(type);
   6:     }
   7: }

Rien de magique, nous implémentant la méthode Create de l’interface IViewPageActivator qui sera appelée lors de la création des vues. Nous utilisons DependencyResolver comme un service locator à l’intérieur de la méthode qui lui même utilise Unity pour résoudre les services nécessaires.

Etape 2 : Modification de Global.asax pour enregistrer notre classe d’implémentation

Ici, rien de spécial non plus. L’enregistrement est très simple :

   1: // View Page activator
   2: container.RegisterType<IViewPageActivator, UnityViewPageActivator>();
   3: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

Il n’y a pas de modification à apporter à la classe InjectedViewPage ni à la vue TestInjectedViewPage.cshtml. Quand on lance l’application nous allons obtenir le même résultat que dans l’exemple suivant. Dans ce cas d’utilisation de IViewPageActivator très simple il n’y a aucune différence par rapport à l’exemple précédent. Nous n’avons effectuer aucune opération supplémentaire dans la méthode Create à part de retourner une instance de vue avec DependencyResolver. Dans des cas plus compliqués vous pouvez imaginer prendre la main sur la création des vues et effectuer dans traitements. Personnellement je ne vois pas concrètement ce que ça pourrait être mais si vous avez des idées n’hésitez pas à me faire un retour là dessus.

Conclusion

Comme vous pouvez le constater, dans ASP.NET MVC 3 vous pouvez dorénavant bénéficier de l’injection de dépendances dans des vues. C’est bien… par contre la vraie question est, à quoi cela peut servir ?

Injecter un service dans une vue, autant utiliser un service locator directement ce qui est une mauvaise pratique en soi! On peut potentiellement utiliser une service qui accède à une base de données par exemple, faire en gros tout et n’importe quoi. Si on utilise un service dans une vue alors potentiellement il y a de la logique qui est déportée vers la vue. Au bout d’un temps cela peut être un enfer comme j’en ai beaucoup vu dans les applications ASP.NET classiques où la logique métier s’est mélangée à la présentation dans la vue. Quid de la testabilité ? Alors que c’est beaucoup plus propre de faire comme avant et de fournir un modèle de vue bien formaté. Il faut donc faire attention lorsqu’on utilise cette extension du Framework MVC. Personnellement je ne l’utilise pas cette fonctionnalité car j’estime qu’elle encourage les mauvaises pratiques mais c’est bien de savoir qu’elle existe ! Avez vous des cas concret où cela serait utile ?

La prochaine fois je ferai un post sur l’injection de dépendance dans les filtres.

// Thomas

Un jour je discutais avec des collègues de la perception dont beaucoup ont du métier de développeur. Juste pour rappel Rui a écrit un article intéressant sur un sujet similaire, donc n’hésitez pas à le lire.

Le but de mon petit post est plutôt d’essayer d’avoir un retour de votre part sur la question. Comment voyez-vous le métier de développeur ?

En tout cas le constat est clair. Le métier de développeur a perdu de sa valeur aux yeux de tout le monde. Quelles en sont les raisons ? En voici quelques unes (liste non exhaustive bien sur)  :

1. Le recrutement

  • pour beaucoup de recruteurs le développement c’est le premier niveau où tout le monde commence. Forcément le plus bas au niveau des salaires par rapport à d’autres métiers “plus valorisants” comme le chef de projet par exemple.
  • Le poste du développeur est souvent considéré comme un passage obligé pour les débutants. Combien de fois nous avons tous entendu à l’entretien d’embauche que si on travaille bien on peut même devenir chef de projet plus tard. Comme si le fait de passer chef de projet était une évolution naturelle du développeur.

2. SSII

  • le développeur c’est la matière première pour faire du fric pour les SSII. Au final peu importe le profil, d’une manière générale le développeur sera toujours casé quelque part.
  • le développeur est de temps en temps falsifié. Combien de fois il se voit rajouter les compétences sur son CV dont il ne dispose pas juste pour être retenu par le client final. Cela peut parfois passer à l’entretien mais bien souvent c’est la désillusion pour le candidat et la déception pour le client.

3. Clients

  • pour beaucoup de clients le développeur est juste un “pisseur” de code, la matière de base pour fabriquer “le produit”.
  • généralement si tu es développeur c’est soit tu débutes, soit tu n’as pas d’ambition ou des compétences nécessaires pour faire autre chose. Par exemple passer CHEF DE PROJET ! Wow…

4. Développeur himself

  • bien souvent le développeur est l’instrument de sa propre image. Mais cela dépend de la personnalité de chacun.

Quel est votre ressenti là dessus ? Est-ce pareil dans d’autre pays ?

Je compte faire d’autres posts sur ce sujet mais d’abord je voulais avoir votre avis sur la question.

 

// Thomas

Posté le jeudi 19 mai 2011 13:32 par tja | 8 commentaire(s)
Classé sous :

Si vous avez la “chance” de travailler avec WCF REST Starter Kit, vous-vous heurterez très souvent à des limites, qui non seulement vous frustrent mais également qui vous laissent un sentiment de “pas fini” ou fait à l’arrache. Comme excuse, on peut dire que WCF Rest Starter Kit est en Preview 2 donc il a le droit d’être pas fini, mais à vrai dire je ne vois pas son avenir en rose avec bientôt une arrivée des vraies nouvelles API pour faire du REST (WCF Web API) made by Glenn Block. En attendant ceux qui sont en Framework 3.5 comme moi en ce moment n’ont le choix que de faire avec l’ancien.

Bon, allons dans le vif du sujet.

Une des extensions de WCF REST Starter Kit est l’introduction de RequestInterceptor qui est un composant s’exécutant au niveau du channel et qui a accès à la stack entière de l’appel au service. A la différence d’autres points d’extension, request interceptors sont exécutés par un channel unique et très tôt dans le channel pipline WCF et avant tous les autres composants d’extension. C’est important à avoir en tête.

Pour résumer, le RequestInterceptor est un point d’extension qui permet donc d’intercepter l’appel vers votre service où vous pouvez effectuer certaines actions comme par exemple :

  • Authentification
  • Caching
  • Centent-Type dispaching
  • X-HTTP-Method-Override

Cela permet de gérer le code en un seul endroit au lieu de le faire dans les opérations des implémentations des services (enfin avant ça on pouvait le faire ailleurs).

Comme son nom indique, WCF REST Starter Kit est là pour faire des service RESTFul. Techniquement c’est une exposition de service en webHttpBinding (protocole http comme son nom indique) et la sérialisation des message soit en JSON, soit en XML.

Pour un cas bien particulier, un des services REST devait être également exposé en SOAP. Vue que par défaut WCF (pour la partie Http) communique en SOAP alors ce ne devrait pas être un problème. WCF REST Starter kit est une couche qui dérive des implémentations de base qui elles communiquent en SOAP. D’ailleurs si vous exposez le même service avec basicHttpBinding, les clients SOAP peuvent consommer le service sans aucun problème.

Le problème

J’ai donc implémenté mon AuthenticationRequestInterceptor qui me servait à parcourir les Headers de la requête entrante pour identifier le client appelant et le laisser effectuer l’appel ou pas. Là où ça coince ce qu’on m’a demandé d’inspecter le contenu de message SOAP pour récupérer la clé d’identification.

Tentative 1 : Lecture directe

Dans votre RequestInterceptor vous avez accès au contexte de l’appel sous forme de l’objet RequestContext. Une des propriétés qui nous intéresse, c’est RequestMessage. La méthode qui nous intéresse c’est la méthode GetReaderAtBodyContents() qui permet d’obtenir un reader XML afin de lire le contenu de l’enveloppe Body de SOAP. Je ne vais pas vous détailler tout le code nécessaire car ce n’est pas le plus important. En tout cas je fais mon test unitaire pour pour valider que tout fonctionne, et ça passe. Je fais en suite un petit test en live et là…ça passe pas ! J’ai un message “This message cannot support the operation because it has been read”. WTF

ExceptionSOAPBodyRead

La ligne dans mon AuthenticationRequestInterceptor de code qui était en cause était la suivante :

   1: XmlReader reader = requestContext.RequestMessage.GetReaderAtBodyContents();

En fait l’exception ne se produit pas sur cette ligne, mais plus tard lorsque WCF essaie de le lire en interne pour le désérialiser. Quelle en est la cause ? Si on se réfère à l’excellent article WCF Messaging Fundamentals nous apprenons dans la section “Message Lifetime” que la classe Message (dans notre propriété requestContext.RequestMessage) afin de supporter le streaming le corps du message (body) ne peut être traité qu’une seule fois. Pour cela vous avez une propriété State qui défini l’état courant du message :

   1: public enum MessageState
   2: {
   3:     Created,
   4:     Read,
   5:     Written,
   6:     Copied,
   7:     Closed
   8: }

 

Le seul état valide pour traiter le message est “Created”. Dans tous les autres états vous aurez l’exception ci-dessus. Donc l’appel de la méthode requestContext.RequestMessage.GetReaderAtBodyContents() modifie l’état du message en “Read” d’où l’exception. Plus loin dans l’article on apprend que le seul moyen de traiter le message est de créer un copie en mémoire et de récréer un nouveau message.

Tentative 2 : Copier le message

Donc si on veut traiter le message plusieurs fois on a le choix de créer une copie avec la méthode CreateBufferedCopy() Cette fois ci je me dis que je suis sur la bonne piste. Je donc écris le code pour copier le message qui ressemble à ceci (pseudo code) :

   1: var messageBuffer = requestContext.RequestMessage.CreateBufferedCopy(Int32.MaxValue);
   2: var originalMessage = messageBuffer.CreateMessage();
   3: var processMessage = messageBuffer.CreateMessage();
   4: // lire le message d'origine
   5:  
   6: requestContext.RequestMessage = originalMessage;
Sauf que cela ne marche pas ! La propriété requestContext.RequestMessage est en lecture seule ! POURQUOI CETTE LIMITATION ? !!!

Tentative 3: IDispatchMessageInspector

Un point d’extension intéressant de WCF qui est similaire à celui de RequestInterceptor. Je me suis dit que finalement je pourrais me brancher ici. La signature de l’interface est la suivante :

   1: public interface IDispatchMessageInspector
   2: {
   3:     object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
   4:     void BeforeSendReply(ref Message reply, object correlationState);
   5: }

Comme on peut le constater nous avons accès directement à l’objet Message, donc pas de problème avec la propriété en lecture seule. Le seul problème, comme je le dis il ne s’exécute qu’après les RequestInterceptor ! Donc je ne peux rien en faire.

Tentative 4 : Reflection

C’est le désespoir d’en arriver là. Mais au pire, je me suis dit qu’avec la Reflection je pourrais arriver à faire ce que je veux :)

Tout d’abord, l’inspection du code avec Reflector (version pas encore expirée). Je me suis dit qu’au lieu d’appeler directement la méthode requestContext.RequestMessage.GetReaderAtBodyContents() qui modifie l’état du message, je vais appeler une méthode en interne.

Reflector

En jaune – l’appel classique, l’état du message est modifié et on ne peut rien faire avec le message.

En rouge – ce que je veux appeler.

Un petit code vite fait :

   1: var method = requestContext.RequestMessage.GetType().GetMethod("OnGetReaderAtBodyContents", BindingFlags.Instance | BindingFlags.NonPublic);
   2: var bodyReader = (XmlDictionaryReader)method.Invoke(requestContext.RequestMessage, null);
   3: var ms = new MemoryStream();
   4: XmlWriter xmlWriter = XmlWriter.Create(ms);
   5: XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter);
   6: writer.Flush();
   7: ms.Position = 0;
   8: XmlReader xmlReader = XmlReader.Create(ms);
   9: if (xmlReader.ReadToDescendant("accessKeyId"))
  10:     authorization = xmlReader.ReadString();

Comme vous le voyez j’essaie d’appeler la méthode interne OnGetreaderAtBodyContents()et lire le message sans modifier l’état du message. Le test unitaire passe. je lance mon client SOAP et la booom, exception “The reader cannot be null”. A un moment donné le XmlReader a été disposé et voilà, WCF ne peut plus lire le message. Pas bon.

Tentative 5 : Reflection version 2.

Si on ne peut pas évoquer même par la reflection les méthodes, autant lire le contenu directement des propriétés. Après des recherches avec Reflector (version non encore expirée Sourire) j’ai vu que WCF lisait principalement une propriété Buffer qui est sur une propriété privé MessageData de la classe Message. On va faire pareil :

   1: var p = requestContext.RequestMessage.GetType().GetProperty("MessageData", BindingFlags.Instance | BindingFlags.NonPublic);
   2: var messageData = p.GetValue(requestContext.RequestMessage, null);
   3: var bufferProperty = messageData.GetType().GetProperty("Buffer", BindingFlags.Instance | BindingFlags.Public);
   4: var bufferPropertyValue = (ArraySegment<byte>)bufferProperty.GetValue(messageData, null);
   5: var memoryStream = new MemoryStream(bufferPropertyValue.Array, bufferPropertyValue.Offset, bufferPropertyValue.Count);
   6: memoryStream.Position = 0;
   7:  
   8: var xmlReader = XmlDictionaryReader.CreateTextReader(memoryStream, XmlDictionaryReaderQuotas.Max);
   9: while (xmlReader.Read())
  10: {
  11:     var temp = xmlReader.ReadString();
  12:     if (!String.IsNullOrEmpty(temp) && xmlReader.LocalName == "accessKeyId")
  13:     {
  14:         authorization = temp;
  15:         break;
  16:     }
  17: }

Le test unitaire passe ! Le client SOAP passe ! Yes ça marche Sourire.

Conclusion

S’il n’y avait pas cette limite sous la forme de la propriété en lecture seule pour requestContext.RequestMessage je m’en serait sorti avec 2 tentatives. Sinon il m’a fallu 3 autres pour arriver à garder mon code cohérent et ne pas disperser le code d’authentification à plusieurs endroits. WCF Starter kit a d’autres limites que ça. Mais ce qui est frustrant ce que vous avez en main un framewrok qui pourrait être bien agréable à utiliser pour faire du REST mais de temps en temps on se heurte aux incohérences comme celle-ci. Finalement je m’en suis sorti mais à quel prix Sourire

 

// Thomas

Ce post sera un peu différent de ce que j’ai l’habitude de faire mais si cela ne vous servira pas au moins je vais l’avoir sous forme de note pour plus tard.

Comme depuis un certain temps je suis à fond dans tout ce qui est web service et leur exposition en REST, j’ai décidé d’essayer le kit WCF pour le REST. Mon premier reflexe est d’ajouter le mécanisme d’injection de dépendance dans WCF pour faciliter le découplage, etc. etc. J’ai donc défini ma propre ServiceHostFactory, mon ServiceHost etc. Je lance pour vérifier que tout marche bien et là BOOM sur la méthode base.OnOpening() de la ServiceHost :

Service 'RuntimeType' implements multiple ServiceContract types, and no endpoints are defined in the configuration file. WebServiceHost can set up default endpoints, but only if the service implements only a single ServiceContract. Either change the service to only implement a single ServiceContract, or else define endpoints for the service explicitly in the configuration file.

ServiceHost

En fait le problème vient du fait que je n’appelle pas le bon constructeur. Sur la class WebServiceHost2 vous avez un constructeur supplémentaire dont la signature est la suivante :

public WebServiceHost2(System.Type serviceType, bool dummy, params System.Uri[] baseAddresses)

Le paramètre “dummy” fait son apparition et bien qu’à priori ce paramètre n’inspire pas ma confiance il faut lui passer la valeur “true”.ServiceHost2

Maintenant tout se passe bien Sourire

// Thomas

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

IDependencyResolver_signature

IControllerActivator

IControllerActivator_signature

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 :

HomeController_IController

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_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.

HomeController_Controller

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 Sourire

Amen.

// Thomas

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 vs2010solution_43E39653.

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 :

Erreur_enregistrment_double

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

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

Dans cet article nous allons nous intéresser plus particulièrement à l’enregistrement de notre containeur DI auprès du Framework MVC 3. Mais avant de rentrer dans le vif du sujet, nous allons faire un petit rappel sur le fonctionnement actuel tel qu’il est connu depuis la version MVC 1.

Le code source pour cet article est présent sur mon Repo ici vs2010solution_43E39653

IControllerFactory

La création des contrôleurs dans le Framework ASP.NET MVC est l’endroit où on peut prendre la main pour introduire l’injection de dépendance. Ce point d’extension est présent depuis le Framework MVC 1 est peut être toujours utilisé dans la version MVC 3. Il s’agit de l’interface IControllerFactory qui est responsable de localiser et de créer des contrôleurs. Elle a était clairement introduite pour permettre l’injection de dépendance des contrôleurs.

IControllerFactory est un point d’enregistrement unique. Il existe également le point d’enregistrement statique pour ce service qui est ControllerBuilder.Current.SetControllerFactory.

En interne dans le Framework MVC, l’implémentation par défaut de IControllerFactory est la DefaultControllerFactory. Son implémentation est basée sur Activator.CreateInstance qui crée le contrôleur suivant la convention bien connue (récupération du nom de contrôleur de la requête http, omission du suffixe “Controller” et génération du type correspondant pour la méthode Activator.CreateInstance(Type controllerType). Donc dans le fonctionnement par défaut, un contrôleur ne pouvait avoir que le constructeur sans paramètres.

Pour modifier ce comportement, et par conséquent permettre l’injection de dépendance dans les contrôleurs nous devions :

  1. Créer notre propre controller factory soit par l’implémentation de l’interface IControllerFactory ou par la dérivation de la classe DefaultControllerFactory. Bien souvent la dérivation était suffisante pour garder le comportement de base comme fallback. Notre contrôleur factory prend en paramètre du constructeur l’instance de containeur DI (unity, structuremap, ninject, autofac, etc…) qui sert à la résolution du contrôleur et le passage des dépendances.
  2. Dans le cas de l’interface il fallait implémenter toutes les méthodes. Dans le cas de la dérivation il fallait au moins surcharger la méthode IController CreateController(RequestContext requestContext, string controllerName). La bonne pratique voudrait également qu’on surcharge la méthode void ReleaseController(IController controller).
  3. Un fois notre implémentation terminée nous indiquons au Framewrok MVC que pour la localisation et la création des instances des contrôleurs nous voulons utiliser notre propre controller factory qu’on vient de créer. Ceci est fait par le point d’enregistrement statique ControllerBuilder.Current.SetControllerFactory généralement appelée dans le Global.asax.

Je ne vais pas rappeler le code qui permet de créer sa propre controller factory supportant l’injection de dépendance car il y a de nombreux posts sur internet qui le démontrent. Le but ici est le MVC 3 et son fonctionnement.

DefaultControllerFactory

Comme mentionné plus haut dans l’article, si vous n’implémentez pas l’interface IControllerFactory  ou ne dérivez pas la classe DefaultControllerFactory alors le Framework MVC 3 utilisera par défaut la classe DefaultControllerFactory. Des modifications ont été apportées à cette classe pour introduire l’injection de dépendance. La première modification été l’introduction de l’interface IControllerActivator. Nous allons en parler dans l’article suivant, mais pour le moment tout ce qu’il faut savoir ce que cette nouvelle interface est introduite pour la création des contrôleur. Ensuite, l’implémentation par défaut de cette interface est la classe DefaultControllerActivator. Si vous ne fournissez pas votre implémentation de IControllerActivator alors la classe DefaultControllerActivator sera utilisée. La particularité de cette classe, est qu’elle prend en paramètre l’implémentation de IDependencyResolver pour résoudre et créer les contrôleurs. Un petit schéma permettra de mieux comprendre ce qui se passe:

DefaultControllerFactory

A noter que la classe DependencyResolver n’implémente pas IDependencyResolver. C’est un point d’enregistrement statique qui utilise en interne statiquement soit votre implémentation de IDependencyResolver, soit une implémentation par défaut sous forme de DefaultDependencyResolver (création avec Activator.CreateInstance), soit DelegateBaseDependencyResolver (résolution par de délégués, factories).

Enregistrement de votre containeur DI dans le Framework MVC 3

Toute cette théorie c’est beau ! Mais passons un peu à la pratique pour comprendre comment cela se passe dans la vraie vie. Pour cela j’ai préparé une toute petite application dont l’architecture n’a rien de transcendant :

SchémaAppliMvc

Note pour les débutants: C’est ne architecture “Domain centric” donc centrée sur le domaine. C’est ce qu’on utilise le plus souvent lorsqu’on utilise l’injection de dépendance.

Ce qu’on veut c’est de consommer dans le contrôleur MVC “HomeController” une instance implémentant IOrderService fourni au run-time.

Voyons le contrôleur par défaut :

   1: public class HomeController : Controller
   2:     {
   3:         public ActionResult Index()
   4:         {
   5:             ViewBag.Message = "Welcome to ASP.NET MVC!";
   6:  
   7:             return View();
   8:         }
   9:  
  10:         // d'autres actions
  11:     }

Nous allons le modifier pour lui injecter le service IOrderService afin qu’il puisse l’utiliser pour effectuer différentes opérations. La nouvelle version ressemble à ceci :

   1: public class HomeController : Controller
   2: {
   3:     private IOrderService _orderService;
   4:  
   5:     // constructeur pour permettre la reception de notre service.
   6:     public HomeController(IOrderService orderService)
   7:     {
   8:         if (orderService == null)
   9:             throw new ArgumentNullException("orderService", "Le service de commande ne peut pas être null");
  10:  
  11:         _orderService = orderService;
  12:     }
  13:     // actions....
  14: }

Si on essaie d’exécuter notre application nous obtenons une belle erreur :

ScreenErrorParametlessCtr

Tout ceci est normal, car tant que ne n’avons pas fourni notre implémentation de IDependencyResolver, en interne, la DefaultControllerFactory utilisera le DefaultDependencyResolver qui fait appel tout simplement à Activator.CreateInstance ce qui nécessite d’avoir un constructeur sans paramètres.

Il y a trois différentes manières d’enregistrer 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.

Nous allons voir chacune des ces méthodes.

1. Implémentation de IDependencyResolver avec Unity.

La première manière d’enregistrer notre containeur DI auprès du Framework MVC 3 et l’implémentation de IDependencyResolver. J’ai choisi Unity sans une raison particulière. Je trouve que c’est un bon containeur DI et largement utilisé dans des entreprises. Nous allons donc créer une classe UnityDependencyResolver pour créer les contrôleurs et leur fournir les dépendances nécessaires:

   1: public class UnityDependencyResolver : IDependencyResolver
   2: {
   3:     private readonly IUnityContainer _container;
   4:  
   5:     public UnityDependencyResolver(IUnityContainer container)
   6:     {
   7:         if (container == null)
   8:             throw new ArgumentNullException("container", "The container cannot be null");
   9:  
  10:         _container = container;
  11:     }
  12:  
  13:     public object GetService(Type serviceType)
  14:     {
  15:         object instance;
  16:  
  17:         try
  18:         {
  19:             instance = _container.Resolve(serviceType);
  20:         }
  21:         catch
  22:         {
  23:             instance = null;
  24:         }
  25:  
  26:         return instance;
  27:     }
  28:  
  29:     public IEnumerable<object> GetServices(Type serviceType)
  30:     {
  31:         IEnumerable<object> instances;
  32:  
  33:         try
  34:         {
  35:             instances = _container.ResolveAll(serviceType);
  36:         }
  37:         catch
  38:         {
  39:             instances = new object[] { };
  40:         }
  41:  
  42:         return instances;
  43:     }
  44: }

Comme vous le voyez ce n’est pas compliqué. En paramètre du constructeur de notre classe UnityDependencyResolver nous lui passons une instance de notre containeur Unity. Ce qui est important de noter ce que la méthode object GetService(Type serviceType) doit retourner null si le service ne peut pas être résolu par Unity, et la méthode IEnumerable<object> GetServices(Type serviceType) doit retourner une collection vide. Si ce n’est pas respecté vous allez avoir une exception au run-time.

Il ne nous reste que d’enregistrer notre IDependencyResolver dans le fichier Global.asax avec la méthode DependencyResolver.SetResolver():

   1: IUnityContainer container = new UnityContainer();
   2:  
   3: // enregistrer les types dans unity (ex: IOrderService, OrderService)
   4: var bootstrapper = new Bootstrapper(container);
   5: bootstrapper.Start();
   6:  
   7: DependencyResolver.SetResolver(new UnityDependencyResolver(container));

Après l’exécution de l’application, le contrôleur est créé par notre  UnityDependencyResolver et reçoit automatiquement en paramètre l’instance du service enregistré par Unity :

ExécutionIDependencyResolver

Et comme c’est à prévoir il n’y a plus d’erreur :

Screen

2. Implémentation de IServiceLocator

Une autre manière d’enregistrer notre containeur DI auprès du Framework MVC 3 et l’implémentation de IServiceLocator ou de ServiceLocatorImplBase qui implémente tout simplement l’interface en question. Cette interface ainsi que la classe de base se trouvent dans la librairie Microsoft.Practices.ServiceLocation. Pour la question de facilité, il vaut mieux dériver de ServiceLocatorImplBase  ce que nous faisons dans la classe UnityServiceLocator que voici:

   1: public class UnityServiceLocator : ServiceLocatorImplBase
   2: {
   3:     private readonly IUnityContainer _container;
   4:  
   5:     public UnityServiceLocator(IUnityContainer container)
   6:     {
   7:         _container = container;
   8:     }
   9:  
  10:     protected override object DoGetInstance(Type serviceType, string key)
  11:     {
  12:         object instance;
  13:  
  14:         try
  15:         {
  16:             instance = _container.Resolve(serviceType, key);
  17:         }
  18:         catch
  19:         {
  20:             instance = null;
  21:         }
  22:  
  23:         return instance;
  24:     }
  25:  
  26:     protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
  27:     {
  28:         IEnumerable<object> instances;
  29:  
  30:         try
  31:         {
  32:             instances = _container.ResolveAll(serviceType);
  33:         }
  34:         catch
  35:         {
  36:             instances = new object[] { };
  37:         }
  38:  
  39:         return instances;
  40:     }
  41: }

Nous devons surcharger deux méthodes :

  • DoGetInstance(Type serviceType, string key) pour la résolution de services d’enregistrement unique.
  • DoGetAllInstances(Type serviceType) pour la résolution de services d’enregistrement multiple.

Ensuite c’est similaire à l’implémentation de IDependencyResolver. En paramètre du constructeur de notre classe UnityServiceLocator nous lui passons une instance de notre containeur Unity. Ce qui est important de noter ce que la méthode object DoGetInstance(Type serviceType, string key) doit retourner null si le service ne peut pas être résolu par Unity, et la méthode IEnumerable<object> DoGetAllInstances(Type serviceType) doit retourner une collection vide. Si ce n’est pas respecté vous allez avoir une exception au run-time.

Il ne nous reste que d’enregistrer notre IServiceLocator dans le fichier Global.asax avec la méthode DependencyResolver.SetResolver():

   1: IUnityContainer container = new UnityContainer();
   2:  
   3: // enregistrer les types dans unity (ex: IOrderService, OrderService)
   4: var bootstrapper = new Bootstrapper(container);
   5: bootstrapper.Start();
   6:  
   7: DependencyResolver.SetResolver(new UnityServiceLocator(container));

Une petite explication s’impose. Si on regarde la signature de la méthode qui permet de réaliser l’enregistrement de IServiceLocator on ne voit la méthode qui prend en paramètre le type object :

   1: public static void SetResolver(object commonServiceLocator)

On pourrait se poser la question pourquoi alors il faut implémenter IServiceLocator alors que le paramètre n’est qu’un type object? Le choix de l’équipe MVC était judicieux et ceci pour ne pas créer la dépendance sur la librairie Microsoft.Practices.ServiceLocation où l’interface est définie et le Framework MVC 3 ! Mais si on regarde l’implémentation de la méthode public static void SetResolver(object commonServiceLocator) de DependencyResolver, nous découvrons que la Reflection est utilisée pour inspecter notre objet afin de vérifier que les méthodes GetInstance et GetAllInstances sont présentes (lignes 7 et 8 de l’extrait de code ci dessous). Voici un extrait de ce code :

   1: public void InnerSetResolver(object commonServiceLocator) {
   2:     if (commonServiceLocator == null) {
   3:         throw new ArgumentNullException("commonServiceLocator");
   4:     }
   5:     
   6:     Type locatorType = commonServiceLocator.GetType();
   7:     MethodInfo getInstance = locatorType.GetMethod("GetInstance", new[] { typeof(Type) });
   8:     MethodInfo getInstances = locatorType.GetMethod("GetAllInstances", new[] { typeof(Type) });
   9:     
  10:     if (getInstance == null ||
  11:         getInstance.ReturnType != typeof(object) ||
  12:         getInstances == null ||
  13:         getInstances.ReturnType != typeof(IEnumerable<object>)) {
  14:         throw new ArgumentException(
  15:             String.Format(
  16:                 CultureInfo.CurrentCulture,
  17:                 MvcResources.DependencyResolver_DoesNotImplementICommonServiceLocator,
  18:                 locatorType.FullName
  19:             ),
  20:             "commonServiceLocator"
  21:         );
  22:     }

Si vous avez fait attention alors vous pourriez-vous poser une autre question. Comment ceci peut-marcher puisque nous n’avons pas surchargé ces méthodes dans la classe UnityServiceLocator  mais DoGetInstance et DoGetAllInstance? Pas d’inquiétude. Un coup de Reflector permet de vérifier qu’en fait nos méthodes font bien appel en interne aux méthodes dont DependencyResolver a besoin :

Reflector

Donc tout vas pour le mieux !

Si on exécute l’application tout marche comme prévu !

3. Enregistrement par les délégués.

La dernière manière de permettre l’injection de dépendance est passer par les fonction ad-hoc. Ceci peut être réalisé directement dans Global.asax :

   1: IUnityContainer container = new UnityContainer();
   2:  
   3: // enregistrer les types dans unity (ex: IOrderService, OrderService)
   4: var bootstrapper = new Bootstrapper(container);
   5: bootstrapper.Start();
   6:  
   7: DependencyResolver.SetResolver(
   8:                 t => (t.IsClass && !t.IsAbstract) || container.IsRegistered(t)? container.Resolve(t) : null,
   9:                 t => (t.IsClass && !t.IsAbstract) || container.IsRegistered(t)? container.ResolveAll(t) : new object[] { });

Quel méthode d’enregistrement utiliser ?

Je dirai que cela n’a pas d’importance car de toute façon au final tous ces enregistrement sont transformés en IDependencyResolver par le Framework MVC 3. Donc ce que vous manipulez au final c’est bien IDependencyResolver. Il est intéressant de noter que vous pouvez utiliser la classe DependencyResolver en tant que service locator classique (mais à éviter car l’utilisation de service locator est un anti-pattern). Dans l’exemple ci-dessous je récupère l’instance de mon service en utilisant cette technique :

DependencyResolverLocation

What Next ?

Dans le prochain post je parlerai en détails de IControllerActivator.

 

A bientôt Sourire

 

// Thomas

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

Ce post je devais le publier avant ma session avec Rui sur MVC 3. Finalement je l’ai retardé pour faire une suite après TechDays.

Sans rentrer dans les détails d’implémentation, je voulais juste regrouper les informations principales concernant les Layouts dans Razor.

Pour plus de détails, n’hésitez pas à consulter les sites suivants :

Introduction

Pour ceux qui connaissent ASP.NET classique, la notion de Layout n’aura rien de magique. Le concept est très similaire au MasterPages de ASP.NET qui permettent de définir un Template graphique commun de votre site, et de l’hériter sur toutes ou une partie des pages.

Dans le cas du moteur de vues Razor on ne parle pas de MasterPages comme dans le moteur aspx. On utilise la notion des Layout.

Les méthodes RenderBody, RenderPage et RenderSection

C’est méthodes c’est pratiquement tout ce que vous devez savoir. Pour avoir une meilleure vision du fonctionnement de ces méthodes j’ai fait un petit schéma ci-dessous. J’ai volontairement créé 2 pages Layout car ce n’est pas si rare de voir dans le milieu des entreprises des Layouts communs aux différents départements et au dessus de tout ça un Layout commun pour l’entreprise.

LayoutSchema

Note: Si vous vous demandez pourquoi certaines pages sont préfixées avec le caractère “_”, c’est parce qu’elle sont en quelque sorte protégées. Vous ne pouvez pas naviguer vers ce pages pour afficher leur contenu.

Le résultat final se rapproche de ceci (désolé pour les couleurs Sourire):

LayoutPage

Ce Layout est bien basé sur le schéma précédent. La version orange est lorsqu’on affiche le “Service Informatique”. Si on clique sur le lien “Service des ressources humaines” nous obtenons un contenu bleu comme ceci:

LayoutPageHR

Remarquez que le @RenderSection menu ne s’affiche pas par rapport à la version du “Service Informatique”. Je vais expliquer ceci plus loin dans l’article.

Pour mieux comprendre le découpage qui est réalisé entre différentes pages des Layout et de pages des contenu, j’ai apporté de petites annotations sur la page du “Service Informatique” :

DescribedLayoutPage

RenderBody()

La méthode RenderBody() est utilisée dans les pages des Layout pour afficher les contenu, soit des sous Layouts, soit des pages de contenu. La position de la méthode RenderBody() dans la page de Layout, détermine la position où les données des pages de contenu (ou sous Layouts) seront affichées.

Attention : Une page de Layout ne peut faire appel qu’une seule fois à la méthode RenderBody().

Voici un exemple de la page de Layout “_MainLayout.cshtml”:

CodeRenderBody

A leur tour, les sous pages de Layout “_DepartmentITLayout.cshtml” et “_DepartmentHRLayout.cshtml” ont chacune également un appel à RenderBody() pour afficher les informations des pages de contenu.

RenderPage()

L’appel à cette méthode permet d’insérer un bloc de contenu dans une page web. En paramètre il faut passer le nom du fichier dont le contenu vous voulez afficher à cet endroit. Comme vous pouvez le constater sur l’extrait de code précédent nous insérons l’entête et le pied de page en appelant respectivement @RenderPage(“_header.cshtml”) et @RenderPage(“_footer.cshtml”).

RenderSection()

Cette méthode permet de diviser le contenu des pages en sections. Chaque section est contenue dans un bloc de section dans une page de contenu. Dans notre exemple nous avons deux sections “Menu” et “Widget” déclarées dans des pages de contenu.

Si on prend comme exemple la page de contenu “ItDep.cshtml”, nous avons 2 sections déclarées de la manière suivante :

CodeRenderSection

Pour que ces sections soient affichées, dans la pages de Layout (ici “_DepartmentITLayout.cshtml”) nous faisons appel à la méthode RenderSection() en passant en paramètre le nom de la section qu’on souhaite afficher:

CodeRenderSection2

C’est aussi simple que ça !

Sections optionnelles

Qu’est-ce qui se passe si votre page de contenu ne déclare pas de section, alors que celle-ci est appelée au niveau de la page de Layout ? C’est la cas dans la page de contenu “HrDep.cshtml” où la section menu n’est pas déclarée. Dans ce cas là, vous avez la possibilité dans la page de Layout d’indiquer qu’un section n’est pas obligatoire en spécifiant le paramètre required : false. Comme dans l’exemple ci-dessous:

CodeRenderSectionOptional

Une alternative existe pour obtenir le même résultat avec le bloc de code suivant  :

   1: @if (IsSectionDefined("menu")) {
   2:     @RenderSection("menu")
   3: }

_ViewStart.cshtml

Afin de mieux respecter DRY (Don’t Repeat Yourself) principle il est possible de définir les valeurs par défaut qui s’appliquent à toutes les vues dans l’application ASP.NET MVC 3. C’est grâce au fichier _ViewStart.cshtml. Par défaut vous avez juste la déclaration de Layout pour toutes les pages. Ce qu’il faut également savoir ce que cette page défini également un scope d’action. C’est-à-dire que si vous voulez limiter l’action de cette page à uniquement les vues d’un contrôleur, vous pouvez en faire copie et la placer dans le répertoire correspondant.

Conclusion

Ceux qui connaissent les fonctionnement des MasterPages dans les moteurs de vue ASP.NET, ne seront pas perdus. Razor est très simple à utiliser, clair en lecture et offre plus de possibilités d’optimisation du rendu en passant par les helpers et les fonctions que nous verrons dans les posts suivants.

Le petit projet d’exemple est disponible sur mon repo hg ici vs2010solution_43E39653

 

// Thomas

J’en profite pour rebondir sur le post de Rui Techdays Paris 2011: Petit bilan et suite avec qui j’ai eu l’occasion de coanimer la session sur ASP.NET MVC 3. Tout d’abord je voudrais le remercier de m’avoir proposé la préparation de cette session.

P1020350

P1020341P1020355

Nous avons prévu beaucoup de démos mais malheureusement une heure n’est pas suffisante pour démontrer toutes les nouveautés de ASP.NET MVC 3. Ma partie concernant plus particulièrement l’injection de dépendance nécessiterai plus d’explications que ce que j’ai pu donner pendant les 15 minutes qui lui ont été consacrées.

Mais tout ceci n’est pas perdu. Nous somme en train de travailler avec Rui pour mettre en forme toutes les démos que nous avons préparées et d’écrire quelques tutoriaux que vous pourrez bientôt découvrir sur nos blogs. Les sujets à venir sont les suivants:

- Razor

- L’injection de dépendances dans les détails (une dizaine de posts à venir)

- Les nouveautés MVC 3 dans la pratique.

Pour une meilleure compréhension, le code source accompagnera chaque post.

 

De plus nous sommes en train de mettre en place un projet Open Source “ObjectNull” (basé sur StackOverflow) qui recensera les bonnes pratiques de programmation et les patterns avec ASP.NET MVC 3, NHibernate, Mongo, EF. Il sera accompagné de full série de tests unitaires. Je suis sur que Rui fera bientôt un post officiel à ce sujet Clignement d'œil

 

Pour terminer, j’aimerais remercier la communauté ALT.NET et les personnes que j’ai pu rencontrer (même si finalement je n’ai pas réussi à passer du temps sur le stand Sourire). Pour plus de détails sur ALT.NET aux TechDays n’hésitez pas à lire le post de Mathias.

 

// Thomas

L’idée de ce post est de regrouper les éléments de syntaxe du nouveau moteur de vues Razor sans rentrer dans les détails de l’implémentation. Vous pouvez trouver le guide officiel ici.

Syntaxe de Razor – les bases

Principalement la syntaxe de Razor est caractérisée par le caractère “@”. Nous allons parcourir quelques exemples de base pour découvrir sa syntaxe. Le but principal est de rendre les vues plus lisibles grâce aux enchainements du code et de HTML plus fluides.

Razor dispose des trois parseurs :

  • Core parseur
  • Parseur de Markup
  • Parseur de Code.

Les trois parseurs travaillent ensemble pour analyser les vues de Razor.

Les opérateurs de base

Razor fait usage de différents opérateurs qui vous sont certainement connus de C# ou VB.NET.

.                  >=

()                 +

[]                 -  

=                  *

!                  /

==                 &&

!=                 ||

<>                 +=

<=                 -=

Je pense qu’il n’est pas nécessaire de détailler leur signification Clignement d'œil

Expressions

L’expression la plus simple est d’afficher un  message encodé :

   1: <p>
   2:     @ViewBag.Message
   3: </p>

vous avez également la version non encodée :

   1: <p>
   2:     @Html.Raw(ViewBag.Message)
   3: </p>

pour clairement indiquer à Razor qu’il doit traiter quelque chose comme une expression vous devez l’entourer avec des parenthèses :

   1: <span>WGE@(widgetNumber)</span>

pour mélanger les expressions avec le texte rien n’est plus simple :

   1: <p>Bonjour @prenom. Votre nome est @nom.</p>

Remarquez que le parseur de Razor est assez intelligent et ne traite pas le “.” après l’expression comme si on essayait d’appeler une méthode ou une propriété de l’expression.

Blocks de code

Le code de block simple et multi ligne est contenu entre les crochets précédés par un caractère “@”:

   1: @{
   2:     var @nom = "JASKULA";
   3:     var @prenom = "Thomas";
   4: }
   5:  
   6: <p>Bonjour @prenom. Votre nome est @nom.</p>

 

Variables

Pour déclarer les variables utilisez le mot clé “var” ou la syntaxe classique fortement typée. Le mot clé “new” est également autorisé.

   1: var message = "Razor est coooooool";
   2: string title = "Syntaxe de Razor";
   3: int number = 1558;
   4: var stringBuilder = new System.Text.StringBuilder();

Vous pouvez également utiliser tous les types standard du Framework.NET :

   1: var date = DateTime.Now.Year;

Chaines de caractères

Quelques considération à prendre en compte lors de la manipulation des chaines de caractères.

Si dans votre chaine des caractères il y a les backslash “\” ou les guillemets, vous pouvez utiliser l’opérateur “@” pour indiquer la chaine verbatim.

   1: @{
   2:     var folder = @"C:\Files\ASP\MVC";
   3: }
   4: <p>Le dossier de travail est : @folder</p>

Pour les textes avec les guillemets, vous devez utiliser l’opérateur de chaine verbatim est de doubler les guillemets comme dans l’exemple ci dessous :

   1: @{
   2:     var citation = @"Thomas a dit : ""Utilisez Razor est vous serez libérés.""";
   3: }
   4: <p>@citation</p>

Ce qui donne :

Thomas a dit : "Utilisez Razor est vous serez libérés."

A l’intérieur du bloc de code, si vous voulez afficher le texte plein vous pouvez utiliser les caractères “@:” :

   1: @{
   2:     
   3:         @:Ceci est un texte plein.
   4: }

Ce qui aura pour conséquence l’affichage du texte “Ceci est un texte plein” à l’écran.

 

Pour les adresses email, Razor est assez intelligent et reconnait les formats simples sans les traiter en tant que début du code block.

   1: <p>Votre email est thomas@jaskula.com</p>

 

Pour échapper le caractère “@”, vous devez le doubler tout simplement :

   1: <p>Dans Razor utilisez @@title pour afficher la veleur de title</p>

Ce qui donnera à l’affichage le texte suivant :

Dans Razor utilisez @title pour afficher la veleur de title

Commentaires

Dans Razor pour indiquer les commentaires vous commencez avec les caractères “@*” et terminez avec “*@”. A l’intérieur de blocks de code vous avez la possibilité d’utiliser la syntaxe de commentaire de votre langage de programmation. Quelques exemples :

   1: @{
   2:     @* Ceci est un commentaire *@
   3:     int age = 85;
   4:     
   5:     @* Tout est commenté *@
   6:     @* int age = 85; *@
   7:     
   8:     @* Ceci est le commentaire multiligne
   9:         une autre ligne *@
  10:     
  11:     @* multiligne également
  12:         int age = 85;
  13:         fin du commentaire *@
  14:     
  15:     // commentaire avec la syntaxe C#
  16:     
  17:     /* commentaire multiligne
  18:         avec la syntaxe C# */
  19: }

Instructions conditionnelles et les boucles

If…then…else
   1: @{
   2:     var products = new string[] { "produit 1", " produit 2", "produit 3" };
   3:     
   4:     if (products.Count() > 0){
   5:         <p>Le premier produit est : @products[0]</p>
   6:     }else{
   7:         <p>Il n'y a pas de produits en stock.</p>
   8:     }
   9: }
switch
   1: @{
   2:     var weekDay = 1;
   3:     string message = "";
   4:     switch (weekDay)
   5:     {
   6:         case 1:
   7:             message = "C'est lundi";
   8:             break;
   9:         case 2:
  10:             message = "C'est mardi";
  11:             break;
  12:         default:
  13:             message = "Jour inconnu.";
  14:             break;
  15:     }
  16:     
  17:     <p>@message</p>
  18: }
for
   1: @{
   2:     var products = new string[] { "product 1", "product 2", "product 3", "product 4" };
   3:  
   4:     int cnt = products.Count();
   5:     
   6:     for (var i = 0; i < cnt; i++)
   7:     {
   8:         <p>Le produit courant est : @productsIdea</p>
   9:     }
  10: }
foreach
   1: @{
   2:     var products = new string[] { "product 1", "product 2", "product 3", "product 4" };
   3:     
   4:     foreach (var product in products)
   5:     {
   6:         <p>Le produit courant est : @product</p>
   7:     }
   8: }

Jusqu’à maintenant rien de spécial. C’est similaire à ce que vous faites en C#.

try…catch…
   1: @{
   2:     var products = new string[] { "product 1", "product 2", "product 3", "product 4" };
   3:     
   4:     try{
   5:         <p>Le cinquième produit est : @products[4]</p>
   6:     }catch(IndexOutOfRangeException ex){
   7:         <p>Une erreur est survenue. Ce produit n'existe pas !</p>
   8:         <p>Details de l'erreur : @ex.Message</p>
   9:     }
  10: }

Try catch, je ne vois pas trop l’utilité car nos vues risque de devenir comme certains connaissent des WebForms Clignement d'œil

Conclusion

Comme vous pouvez le constater, la syntaxe des Razor est vraiment simple et intuitive. Dans le prochain post j’aborderai les notions de Layout sous Razor

 

// Thomas

Plus de Messages Page suivante »


Les 10 derniers blogs postés

- « Naviguer vers le haut » dans une librairie SharePoint par Blog de Jérémy Jeanson le 10-07-2014, 13:21

- PowerShell: Comment mixer NAGIOS et PowerShell pour le monitoring applicatif par Blog Technique de Romelard Fabrice le 10-07-2014, 11:43

- ReBUILD 2014 : les présentations par Le blog de Patrick [MVP Office 365] le 10-06-2014, 09:15

- II6 Management Compatibility présente dans Windows Server Technical Preview avec IIS8 par Blog de Jérémy Jeanson le 10-05-2014, 17:37

- Soft Restart sur Windows Server Technical Preview par Blog de Jérémy Jeanson le 10-03-2014, 19:43

- Non, le certificat public du CA n’est pas un certificat client !!! par Blog de Jérémy Jeanson le 10-03-2014, 00:08

- Windows Server Technical Preview disponible via MSDN par Blog de Jérémy Jeanson le 10-02-2014, 19:05

- Focus Sauvegardes SharePoint par Le blog de Patrick [MVP Office 365] le 10-02-2014, 13:11

- Technofolies, votre évènement numérique de l'année par Le Blog (Vert) d'Arnaud JUND le 09-26-2014, 18:40

- Xamarin : From Zero to Hero par Fathi Bellahcene le 09-24-2014, 17:35