jeudi 20 janvier 2011

Extensions possibles de l’ASP.NET SessionHelper

Certains articles présentent des méthodes intéressantes pour gérer au mieux les variables Session d’ASP.NET. La technique classique consiste à créer une classe simple appelée généralement SessionHelper contenant directement les accès aux variables session encapsulés dans des propriétés :

image

Le code précédent mériterait évidemment des améliorations, notamment au niveau du get qui lève une exception si la variable session correspondante n’a pas précédemment été affectée, mais l’idée générale est là. Les avantages d’un tel code sont les suivants :

  • Accès fortement typé aux variables Session
  • Vision claire de l’ensemble des variables Session manipulées
  • Possibilité d’intervenir systématiquement lors de l’accès à une variable Session, par exemple pour écrire dans un log
  • Possibilité d’extensions : organisation en catégories, validation avant enregistrement en session, etc.

Néanmoins, il est curieux de constater que ce principe intéressant ne soit généralement pas étendu à d’autres fonctionnalités basées sur des dictionnaires en ASP.NET, telles que le ViewState, l’Application, le Cache, les Cookies ou encore le Profile. Pour tous ces éléments sauf le ViewState, la réalisation du helper correspondant est triviale à quelques détails prés (utiliser Application.Lock() et Application.Unlock() autour des accès à Application, utiliser Request et Response pour les Cookies, ou encore configurer l’expiration des variables dans le Cache). Pour le ViewState en revanche, une réflexion supplémentaire est nécessaire, l’idéal étant peut-être d’avoir un helper par page.

En outre, quelques éléments simples permettent de gérer simplement l’utilisation de ces helpers dans le cadre d’un développement en équipe :

  • Définition d’extraits de code pour les propriétés des helpers
  • Utilisation de diagrammes de couches pour interdire l’utilisation des dictionnaires d’ASP.NET en dehors des classes helper. Par exemple :

image

jeudi 13 janvier 2011

C# 4.0, mot-clé dynamic et interopérabilité

Voici un exemple typique d’accès à une propriété qui, apparemment, n’existe pas :

image

Pourtant, à l’exécution, on constate que le membre Marker est bien défini, mais qu’il s’agit d’un membre dynamique défini sur un objet COM :

image

Ce membre n’est pas défini dans la coclass ni dans la bibliothèque de types pour cette objet COM, sinon il serait résolu à la compilation. D’autre part, puisqu’il s’agit de classes définies dans des assemblies tiers, il n’est pas possible d’y ajouter facilement ces membres. Enfin, si ces membres sont réellement définis dynamiquement sur l’objet COM, il devient nécessaire d’utiliser le mot-clé dynamic. Voici comment ce mot-clé permet de sauver la situation :

image

En déclarant une variable de type dynamic, l’accès à Marker.Color ne provoque pas de vérification de la présence des membres lors de la compilation, mais lors de l’exécution. Il s’agit là d’une amélioration du C# 4.0 utile pour l’interopérabilité, de même que les paramètres nommés et optionnels.

samedi 8 janvier 2011

Evènements et Pattern Observer

Le Design Pattern Observer est sans doute l’un de design patterns les plus utilisés en .NET, du moins si on considère que les évènements .NET sont une implémentation de ce design pattern. Il consiste à définir un objet (l’observateur) capable de scruter un autre objet (le sujet) afin de réagir à un changement d’état du sujet. Le cas typique est celui du clic sur un bouton, où le sujet de l’observation est le bouton lui-même et l’observateur est le formulaire qui réagit lorsque lorsque le clic survient. Concrètement, les évènements .NET (héritant de MulticastDelegate) sont des délégués particuliers car ils héritent de Delegate. A ce titre, pour faire fonctionner un évènement en .NET, il faut toujours 4 éléments :

  1. La déclaration de l’évènement
  2. La levée de l’évènement
  3. L’attachement de l’évènement à une méthode en réponse
  4. La définition de la méthode en réponse.

image

En reprenant l’exemple du clic sur le bouton et avec une application Windows Forms, (1) et (2) sont définis dans la classe Button elle-même et (3) et (4) sont définis automatiquement lorsqu’on double-clique sur l’évènement souhaité dans la fenêtre de propriétés en mode design de la Form. Les évènements apportent en outre une simplicité syntaxique, la gestion de multiples gestionnaires d’évènements (par l’utilisation de += et –= en C#) et Visual Studio aide le développeur, notamment lors de l’attachement (3) et de la génération de la méthode en réponse (4).

Les évènements .NET sont conçus pour traiter la plupart des cas d’utilisation, mais pas tous. Par exemple :

  • La méthode GetInvocationList() disponible sur les évènements ne peut pas être appelée en dehors de la classe où est défini l’évènement. Par conséquent, il est parfois difficile ou impossible (assemblies tiers) de récupérer la liste des gestionnaires associés à un évènement.
  • Il est difficile d’envisager une manipulation des gestionnaires de l’évènement, notamment pour modifier l’ordre dans lequel ils sont appelés.
  • Il difficile d’ajouter de la logique métier lors de la levée de l’évènement (2), par exemple pour n’appeler que les gestionnaires satisfaisant une certaine condition.

En fin de compte, l’évènement en .NET est une fonctionnalité intéressante et nécessaire, mais il manque des fonctionnalités de gestion de l’évènement lui-même. C’est là que le pattern Observer peut venir en renfort, pour prendre le contrôle total du mécanisme évènementiel. Une interprétation possible et minimale de ce pattern permet d’aboutir au diagramme de classes suivant :

image

Et, en exemple d’utilisation :

image

Sachant que SomeOperations() appelle Notify(), qui appelle Update() sur chaque élément de la liste observers, appeler SomeOperations() permet de notifier les deux IObserver définis dans Main. Notify() correspond donc à une levée de l’évènement (2) et Update() correspond au gestionnaire de l’évènement (4). En partant de ce pattern, de nouvelles possibilités sont offertes par rapport aux évènements .NET. Voici quelques exemples :

  • La liste observers peut devenir une pile, un arbre, un dictionnaire ou toute autre structure de données. Ceci permet de structurer les observateurs.
  • IObserver peut très bien contenir d’autres méthodes que Update. Ceci permet de lancer plusieurs méthodes en réponse pour chaque observateur.
  • Notify() peut décider de notifier certains observateurs ou non en fonction par exemple d’un état interne. On peut également concevoir différents modes de notification ou encore contrôler l’ordre de notification.
  • En définissant bool au lieu de void comme type de retour de Update, on peut imaginer une chaîne de notification que chaque observateur peut interrompre. Sur le principe, cela correspond à la propriété Handled de RoutedEventArgs utilisée pour les Routed Events en WPF.
  • Au delà de la notification asynchrone déjà disponible dans les évènements .NET par les méthodes spéciales BeginInvoke et EndInvoke, il devient possible de paralléliser les différentes notifications en utilisant des threads dans la méthode Notify().

mardi 4 janvier 2011

Covariance, contravariance et différences de Framework .NET

En choisissant comme cible le Framework .NET 3.5 dans un projet,  :

image

L’assembly mscorlib.dll référencée est logiquement celle du Framework 2.0. Ainsi, lorsqu’on consulte par exemple la définition du délégué Action<T>, on obtient :

image

En revanche, en définissant comme cible le Framework .NET 4.0, le délégué Action<T> devient Action<in T> comme suit :

image

Il s’agit de l’application typique de contravariance sur les paramètres générique en C# 4.0, les notions de covariance et de contravariance sont elles-mêmes connues depuis longtemps et directement liées au polymorphisme. En l’occurence pour Action<in T>, la contravariance du paramètre générique indique qu’il est possible d’utiliser ce délégué avec des paramètres génériques héritant de T. Ainsi, sachant que IOException hérite de Exception, le code suivant est valide en C# 4.0 mais pas en C# 3.5 :

image

A l’inverse, entre le Framework 3.5 et le Framework 4.0, IEnumerable<T> est devenu IEnumerable<out T>, ce qui indique que T est covariant. Ainsi, le code suivant devient valide en C# 4.0 :

image

Dans le cas d’Action<in T> le paramètre est contravariant parce qu’il est logique qu’une méthode attendant une Exception en paramètre fonctionne toujours en fournissant un objet d’un type dérivé d’Exception. Dans le cas d’IEnumerable<out T>, il est logique qu’un ensemble d’objets de type IOException puisse être considéré comme un ensemble d’objets d’un type parent d’IOException.

C# 4.0 introduit donc une nouvelle signification pour les mots-clés in et out qui est largement exploitée dans le Framework .NET 4.0. Cette évolution doit être considérée lors de la création d’interfaces génériques et de délégués génériques, en respectant en général les règles suivantes :

  • Pour un délégué générique, les paramètres génériques utilisés pour les paramètres de méthode en entrée sont le plus souvent contravariant (mot-clé in) et le paramètre générique correspondant au type de retour de méthode est le plus souvent covariant (mot-clé out). L’exemple typique est celui de Func<in T, out TResult>.
  • Pour une interface générique, si le paramètre générique est utilisé comme type de retour d’une méthode, alors il est souvent covariant. Par exemple, IEnumerable<out T> utilise T en retour de GetEnumerator() :

image

  • Pour une interface générique, si le paramètre générique est utilisé comme type de paramètre en entrée d’une méthode, alors il est souvent contravariant. Par exemple, IComparable<in T> définit la méthode CompareTo(T other) :

image

samedi 1 janvier 2011

Au coeur d’ASP.NET

Tout développeur ASP.NET sait que chaque requête aboutit à la création d’un nouveau thread pour traiter cette requête. Mais qui crée ce thread et comment? De même, que se passe-t-il avant le moment où la page affiche nos contrôles et traite nos évènements? Qui crée et gère les différents évènements du cycle de vie de la page ASP.NET? Force est de constater qu’ASP.NET donne l’impression d’un iceberg dont nous ne voyons qu’une toute petite partie, et que de nombreuses erreurs pourraient être évitées si nous avions une compréhension plus importante des mécanismes internes.

Pourtant, il est plutôt facile d’en savoir plus sur le fonctionnement interne d’ASP.NET, tout simplement parce que le processus de traitement des pages est connu et documenté, et parce que des outils tels que Visual Studio ou Reflector permettent de mettre les mains dans le cambouis pour la partie .NET.

Pour résumer, lorsque le serveur Web reçoit une requête, celui-ci analyse l’extension de la ressource demandée pour savoir comment traiter la requête. Dans le cas d’une page ASP.NET (extension .aspx par défaut), c’est l’assembly aspnet_isapi.dll (non managée) qui est sollicitée (exemple d’association de mappage avec IIS 7.5) :

image

Le serveur Web, après avoir chargé aspnet_isapi, inscrit la requête dans une queue par un appel asynchrone vers le Worker Process d’ASP.NET. Celui-ci traite successivement les requêtes de la queue. Lorsqu’un traitement est terminé, la réponse à la requête est envoyée du Worker Process vers aspnet_isapi.

Concrètement, voici ce qu’on obtient avec Visual Studio 2010 lorsqu’on crée une application Web ASP.NET et qu’on stoppe dans le Page_Load de la page Default.aspx :

image

Dans cette pile des appels, on voit clairement les étapes majeures :

  • L’appel à des classes de threading .NET (namespace System.Threading de mscorlib) depuis du code non managé (certainement aspnet_isapi). Ici, c’est le ThreadPool qui est utilisé : c’est le mécanisme le plus logique pour inscrire un appel asynchrone dans une queue. A la fin de cette étape, WaitCallback_Context intervient ; on constate que l’appel asynchrone transmet le traitement au serveur Web, en lui fournissant un socket :

    image

  • L’intervention du serveur Web de développement ASP.NET WebDev.WebHost40.dll. Cette assembly contient des classes pour matérialiser les échanges classiques d’un serveur Web (Host, Request, Messages, Connection etc.). Ici, on voit clairement qu’elle est sollicitée pour que l’hôte traite la requête. Plus précisément, OnSocketAccept reconstruit une connexion Web à l’aide du socket et utilise l’hôte pour démarrer le traitement :

image

L’intervention de WebDev.WebHost40.dll aboutit à la méthode Process, où plusieurs vérifications, notamment sur la requête, sont déjà effectuées. Par exemple, TryParseRequest appelle IsBadPath, qui détecte entre autres les URLs mal formées et retourne le cas échéant un code d’erreur HTTP en stoppant le traitement. Ceci explique pourquoi il est parfois difficile de déboguer un problème d’URL mal formée, et pourquoi il est inutile de tester la validité de l’URL de la requête dans le code d’une page ASP.NET. Si les vérifications passent, PrepareResponse inscrit déjà le code HTTP 200 (succès) et Process transmet la requête à HttpRuntime  :

image

  • Le passage dans System.Web.dll marque l’entrée réelle dans les classes du Framework .NET. HttpRuntime est sollicitée en premier. La requête provenant du serveur Web est transmise jusqu’à ProcessRequestInternal, où elle sert à créer l’objet majeur d’ASP.NET HttpContext :

image

Dans cette même méthode, on trouve également l’utilisation des HttpHandlers, un autre concept majeur relatif à ASP.NET :

image

Le traitement de la requête est transmis de façon asynchrone au HttpHandler représentant l’application. Celui-ci est construit en récupérant le type déclaré dans le fichier Global.asax (héritant toujours d’HttpApplication). C’est ainsi qu’ASP.NET sait quelle est l’application Web concernée dans la suite des appels. HttpApplication traite la requête en parcourant un ensemble d’étapes (qui sont toutes des types imbriqués à HttpApplication implémentant HttpApplication.IExecutionStep). Ces étapes sont ajoutées lors de l’initialisation (méthode BuildSteps appelée depuis InitInternal) de l’HttpApplication de la façon suivante :

image

Les étapes sont de 2 types : des étapes d’exécution et des évènements. Une classe appelée ApplicationStepManager est utilisée par sa méthode ResumeSteps pour parcourir les différentes étapes, sachant que l’étape “principale” est CallHandlerExecutionStep :

image 

L’intervention d’HttpHandler s’achève en délégant le traitement de la requête au Handler de la page déterminé à l’étape MapHandlerExecutionStep. Ceci mène directement à l’assembly (ici, App_Web_inl2fuua.dll) générée automatiquement pour l’application Web pour traiter la page demandée (ici, Default.aspx). Le traitement de la page y est simplement transmis à la classe Page :

image

Avant de lancer le traitement de la requête, Page affecte un certain nombre de variables de classe provenant du contexte HTTP dans la méthode SetIntrinsics (notamment Request, Response et Application). Puisque toutes les pages ASP.NET héritent de Page, ceci permettra par exemple d’accéder indifféremment à la requête HTTP depuis n’importe quelle page en récupérant “Context.Request”, ou directement “Request”. De plus, la méthode SetIntrinsics appelle TemplateControl.HookUpAutomaticHandlers qui utilise la méthode GetDelegateInformationWithNoAssert pour rechercher par réflexion les gestionnaires d’évènements déclarés dans la Page :

image

Chaque appel à GetDelegateInformationFromMethod recherche un EventHandler ayant le nom exact spécifié dans la Page, et l’inscrit dans un dictionnaire s’il le trouve (le dictionnaire est ensuite mis en cache). Ceci explique que les évènements du cycle de vie de la page ASP.NET doivent être déclarés avec la bonne syntaxe et la bonne signature, sans avoir besoin de les attacher manuellement à la Page. L’extrait de code précédent montre également les évènements spécifiques à la Page, les autres étant valides seulement pour les contrôles.

Avant d’effectuer le traitement de la Page caractérisé par la méthode ProcessRequestMain, Page récupère diverses informations supplémentaires tells que la culture courante, le StyleSheetTheme ou encore le type de PostBack. Par exemple, Page recherche le champ “__PreviousPage”. Ensuite, ProcessRequestMain parcours effectivement le cycle de vie de la Page en effectuant les étapes du cycle, en levant les évènements correspondants (ceux détectés et inscrits dans le dictionnaire en cache) au fur et à mesure et en inscrivant les messages dans la Trace ASP.NET. Lors du traitement effecif, Page commence par effectuer la pré-initialisation :

image

Ceci explique comment un évènement Page_PreInit dans une page permet effectivement de modifier le thème et la page maître juste avant leur application par le Framework ASP.NET. En allant un peu plus loin, voici un extrait de ProcessRequestMain autour du Load :

image

La méthode LoadRecursive est en fait définie dans la classe System.Web.UI.Control. Elle cherche simplement à lancer l’évènement Page_Load s’il existe et à charger ensuite les enfants du contrôle. En partant de la Page, ceci permet de parcourir toute l’arborescence des contrôles de la page. De plus, ceci explique que le Page_Load de la Page soit appelé avant le Page_Load des contrôles :

image

Lorsque LoadRecursive appelle la méthode OnLoad, le dictionnaire (en cache) des gestionnaires d’évènements est sollicité :

image

Ce qui appelle (enfin !) le gestionnaire Page_Load :

image 

En explorant plus en profondeur HttpRuntime, HttpApplication et Page, toutes les fonctionnalités d’ASP.NET pourraient être expliquées : l’application des Master Pages, le fonctionnement du ViewState et du ControlState, l’application des thèmes, le databinding, etc.