L'une des raisons du succès d'ASP.NET est qu'il réduit les barrières à l'entrée pour les développeurs Web. Vous n'avez pas besoin d'avoir un doctorat en informatique pour écrire du code ASP.NET. De nombreux développeurs ASP.NET que je rencontre au travail sont autodidactes et écrivaient des feuilles de calcul Microsoft® Excel® avant d'écrire C# ou Visual Basic®. Aujourd'hui, ils écrivent des applications Web et, dans l'ensemble, ils méritent des éloges pour le travail qu'ils accomplissent.
Mais le pouvoir implique des responsabilités, et même les développeurs ASP.NET expérimentés peuvent commettre des erreurs. Au cours de nombreuses années de conseil sur des projets ASP.NET, j'ai découvert que certaines erreurs sont particulièrement susceptibles d'entraîner des défauts récurrents. Certaines de ces erreurs peuvent avoir un impact sur les performances. D'autres erreurs peuvent entraver l'évolutivité. Certains bugs peuvent également faire perdre un temps précieux aux équipes de développement dans leur recherche de bugs et de comportements inattendus.
Voici 10 pièges qui peuvent poser problème lors de la sortie des applications de production ASP.NET et les moyens de les éviter. Tous les exemples proviennent de ma propre expérience dans la création de véritables applications Web dans de vraies entreprises et, dans certains cas, je fournis le contexte en décrivant certains des problèmes rencontrés par l'équipe de développement ASP.NET au cours du processus de développement.
LoadControl et mise en cache de sortie Il existe très peu d'applications ASP.NET qui n'utilisent pas de contrôles utilisateur. Avant les pages maîtres, les développeurs utilisaient des contrôles utilisateur pour extraire le contenu commun, tel que les en-têtes et les pieds de page. Même dans ASP.NET 2.0, les contrôles utilisateur constituent un moyen efficace d'encapsuler le contenu et le comportement et de diviser la page en régions dont la mise en cache peut être contrôlée indépendamment de la page dans son ensemble (un processus appelé segments). ).
Les contrôles utilisateur peuvent être chargés de manière déclarative ou forcée. Le chargement forcé repose sur Page.LoadControl, qui instancie un contrôle utilisateur et renvoie une référence de contrôle. Si le contrôle utilisateur contient des membres d'un type personnalisé (par exemple, une propriété publique), vous pouvez caster la référence et accéder au membre personnalisé à partir de votre code. Le contrôle utilisateur de la figure 1 implémente une propriété nommée BackColor. Le code suivant charge le contrôle utilisateur et attribue une valeur à BackColor :
protected void Page_Load(object sender, EventArgs e){// Charge le contrôle utilisateur et ajoute-le à la page Control control = LoadControl("~/MyUserControl.ascx") ;PlaceHolder1 .Controls.Add(control);//Définir sa couleur d'arrière-plan ((MyUserControl)control).BackColor = Color.Yellow;}
Le code ci-dessus est en fait très simple, mais c'est un piège dans lequel tomber le développeur imprudent. Pouvez-vous trouver la faille ?
Si vous avez deviné que le problème est lié à la mise en cache des sorties, vous avez raison. Comme vous pouvez le constater, l'exemple de code ci-dessus se compile et s'exécute correctement, mais si vous essayez d'ajouter l'instruction suivante (qui est parfaitement légale) à MyUserControl.ascx :
<%@ OutputCache Duration="5" VaryByParam="None" %>
Ensuite, la prochaine fois que vous exécuterez la page, vous verrez une InvalidCastException (oh joie !) et le message d'erreur suivant :
"Impossible de convertir un objet de type 'System.Web.UI.PartialCachingControl' en type 'MyUserControl'."
Par conséquent, ce code s'exécute correctement sans la directive OutputCache, mais échoue si la directive OutputCache est ajoutée. ASP.NET n'est pas censé se comporter de cette façon. Les pages (et les contrôles) doivent être indépendantes de la mise en cache de sortie. Alors, qu’est-ce que cela signifie ?
Le problème est que lorsque la mise en cache de sortie est activée pour un contrôle utilisateur, LoadControl ne renvoie plus de référence à l'instance de contrôle, mais renvoie une référence à une instance de PartialCachingControl, qui peut ou non encapsuler l'instance de contrôle, selon que l'instance de contrôle est activée ou non. la sortie du contrôle est le cache. Par conséquent, si un développeur appelle LoadControl pour charger dynamiquement un contrôle utilisateur et convertit la référence du contrôle afin d'accéder aux méthodes et propriétés spécifiques au contrôle, il doit faire attention à la manière dont il procède afin que le code s'exécute indépendamment de l'existence ou non d'un contrôle. Directive OutputCache.
La figure 2 illustre la manière correcte de charger dynamiquement un contrôle utilisateur et de convertir la référence de contrôle renvoyée. Voici un résumé de son fonctionnement :
• S'il manque une directive OutputCache dans le fichier ASCX, LoadControl renvoie une référence MyUserControl. Page_Load convertit la référence en MyUserControl et définit la propriété BackColor du contrôle.
• Si le fichier ASCX inclut une directive OutputCache et que la sortie du contrôle n'est pas mise en cache, LoadControl renvoie une référence au PartialCachingControl dont la propriété CachedControl contient une référence au MyUserControl sous-jacent. Page_Load convertit PartialCachingControl.CachedControl en MyUserControl et définit la propriété BackColor du contrôle.
• Si le fichier ASCX inclut une directive OutputCache et que la sortie du contrôle est mise en cache, LoadControl renvoie une référence au PartialCachingControl dont la propriété CachedControl est vide. Notez que Page_Load ne se poursuit plus. La propriété BackColor du contrôle ne peut pas être définie car la sortie du contrôle provient du cache de sortie. En d’autres termes, il n’existe aucun MyUserControl sur lequel définir des propriétés.
Le code de la figure 2 s'exécutera indépendamment de la présence ou non d'une directive OutputCache dans le fichier .ascx. Même si cela semble un peu plus compliqué, cela évitera des erreurs gênantes. Simple ne veut pas toujours dire facile à entretenir.
Retour au début Sessions et mise en cache de sortie En parlant de mise en cache de sortie, ASP.NET 1.1 et ASP.NET 2.0 présentent tous deux un problème potentiel qui affecte les pages de cache de sortie sur les serveurs exécutant Windows Server™ 2003 et IIS 6.0. J'ai personnellement vu ce problème se produire deux fois sur un serveur de production ASP.NET, et les deux fois, il a été résolu en désactivant la mise en mémoire tampon de sortie. Plus tard, j'ai appris qu'il existe une meilleure solution que de désactiver la mise en cache des sorties. Voici à quoi cela ressemblait lorsque j'ai rencontré ce problème pour la première fois.
Ce qui s'est passé, c'est qu'un site Web (appelons-le ici Contoso.com, qui gère une application publique de commerce électronique dans un petit domaine Web ASP.NET) a contacté mon équipe pour se plaindre d'une erreur de « cross-threading ». Les clients utilisant le site Web Contoso.com perdent souvent soudainement les données qu'ils ont saisies, mais voient à la place les données relatives à un autre utilisateur. Après une petite analyse, nous avons constaté que la description du cross-threading n'est pas exacte ; l'erreur « cross-session » est plus appropriée. Il semble que Contoso.com stocke les données dans l'état de session et que, pour une raison quelconque, les utilisateurs se connectent occasionnellement et de manière aléatoire aux sessions d'autres utilisateurs.
L'un des membres de mon équipe a écrit un outil de diagnostic qui enregistre les éléments clés de chaque requête et réponse HTTP, y compris l'en-tête Cookie. Il a ensuite installé l'outil sur le serveur Web de Contoso.com et l'a laissé fonctionner pendant quelques jours. Les résultats sont très évidents. Environ une fois sur 100 000 requêtes, ASP.NET attribue correctement un ID de session à une toute nouvelle session et renvoie l’ID de session dans l’en-tête Set-Cookie. Il renvoie ensuite le même ID de session (c'est-à-dire le même en-tête Set-Cookie) lors de la requête immédiatement adjacente suivante, même si la requête était déjà associée à une session valide et que l'ID de session dans le cookie a été soumis correctement. En effet, ASP.NET fait sortir les utilisateurs de manière aléatoire de leurs propres sessions et les connecte à d'autres sessions.
Nous avons été surpris et avons cherché à savoir pourquoi. Nous avons d’abord vérifié le code source de Contoso.com et, à notre grand soulagement, le problème n’était pas là. Ensuite, pour nous assurer que le problème n'était pas lié à l'hôte de l'application dans le domaine Web, nous avons laissé un seul serveur en marche et arrêté tous les autres. Le problème persiste, ce qui n’est pas surprenant puisque nos logs montrent que les en-têtes Set-Cookie correspondants ne proviennent jamais de deux serveurs différents. ASP.NET génère accidentellement des ID de session en double, ce qui est incroyable car il utilise la classe .NET Framework RNGCryptoServiceProvider pour générer ces ID, et les ID de session sont suffisamment longs pour garantir que le même ID n'est jamais généré deux fois (au moins lors de la prochaine session). ne sera pas généré deux fois en des milliards d’années). Au-delà de cela, même si RNGCryptoServiceProvider génère par erreur des nombres aléatoires répétés, cela n'explique pas pourquoi ASP.NET remplace mystérieusement l'ID de session valide par un nouveau (qui n'est pas unique).
Sur une intuition, nous avons décidé de nous pencher sur la mise en cache de sortie. Lorsque OutputCacheModule met en cache une réponse HTTP, il doit faire attention à ne pas mettre en cache l'en-tête Set-Cookie ; sinon, une réponse mise en cache contenant un nouvel ID de session connectera tous les destinataires de la réponse mise en cache (et l'utilisateur dont la demande a généré la réponse mise en cache). à la même séance. Nous avons vérifié le code source ; la mise en cache de sortie est activée sur les deux pages. Nous avons désactivé la mise en cache des sorties. En conséquence, l’application a fonctionné pendant plusieurs jours sans un seul problème entre sessions. Après cela, il a fonctionné sans aucune erreur pendant plus de deux ans. Dans une autre entreprise avec une application différente et un ensemble de serveurs Web différent, nous avons vu exactement le même problème disparaître. Tout comme sur Contoso.com, la suppression du cache de sortie résout le problème.
Microsoft a confirmé plus tard que ce comportement provenait d'un problème dans OutputCacheModule. (Une mise à jour a peut-être été publiée au moment où vous lisez cet article.) Lorsque ASP.NET est utilisé avec IIS 6.0 et que la mise en cache en mode noyau est activée, OutputCacheModule ne parvient parfois pas à supprimer l'en-tête Set-Cookie des réponses mises en cache qu'il transmet. vers Http.sys. Voici la séquence spécifique d'événements qui entraîne l'erreur :
• Un utilisateur qui n'a pas récemment visité le site (et n'a donc pas de session correspondante) demande une page pour laquelle la mise en cache de sortie est activée, mais dont la sortie n'est pas actuellement disponible. dans la cache.
• La requête exécute le code qui accède à la session la plus récemment créée par l'utilisateur, provoquant le renvoi du cookie d'ID de session dans l'en-tête Set-Cookie de la réponse.
• OutputCacheModule fournit une sortie à Http.sys, mais ne peut pas supprimer l'en-tête Set-Cookie de la réponse.
• Http.sys renvoie les réponses mises en cache lors des requêtes suivantes, connectant par erreur d'autres utilisateurs à la session.
La morale de l'histoire : l'état de session et la mise en cache des sorties en mode noyau ne font pas bon ménage. Si vous utilisez l'état de session dans une page avec la mise en cache de sortie activée et que l'application s'exécute sur IIS 6.0, vous devez désactiver la mise en cache de sortie en mode noyau. Vous bénéficierez toujours de la mise en cache de sortie, mais comme la mise en cache de sortie en mode noyau est beaucoup plus rapide que la mise en cache de sortie normale, la mise en cache ne sera pas aussi efficace. Pour plus d'informations sur ce problème, consultez support.microsoft.com/kb/917072.
Vous pouvez désactiver la mise en cache de sortie en mode noyau pour une page individuelle en incluant l'attribut VaryByParam="*" dans la directive OutputCache de la page, bien que cela puisse entraîner une augmentation soudaine des besoins en mémoire. Une autre approche, plus sécurisée, consiste à désactiver la mise en cache en mode noyau pour l'ensemble de l'application en incluant l'élément suivant dans web.config :
Vous pouvez également utiliser un paramètre de registre pour désactiver globalement la mise en cache de sortie en mode noyau, c'est-à-dire désactiver la mise en cache de sortie en mode noyau pour tous les serveurs. Voir support.microsoft.com/kb/820129 pour plus de détails.
Chaque fois que j'entends un client signaler des problèmes de session déroutants, je lui demande s'il utilise la mise en cache de sortie sur des pages. S'ils utilisent la mise en cache de sortie et que le système d'exploitation hôte est Windows Server 2003, je leur recommanderais de désactiver la mise en cache de sortie en mode noyau. Le problème est généralement résolu. Si le problème n'est pas résolu, le bug existe dans le code. Soyez vigilant !
Retour en haut
Durée de vie du ticket d'authentification par formulaire Pouvez-vous identifier le problème avec le code suivant ?
FormsAuthentication.RedirectFromLoginPage(nom d'utilisateur, true);
Ce code peut sembler correct, mais ne doit jamais être utilisé dans une application ASP.NET 1.x à moins que du code ailleurs dans l'application compense les effets négatifs de cette instruction. Si vous ne savez pas pourquoi, continuez à lire.
FormsAuthentication.RedirectFromLoginPage effectue deux tâches. Premièrement, lorsque FormsAuthenticationModule redirige l'utilisateur vers la page de connexion, FormsAuthentication.RedirectFromLoginPage redirige l'utilisateur vers la page initialement demandée. Deuxièmement, il émet un ticket d'authentification (généralement stocké dans un cookie, et toujours stocké dans un cookie dans ASP.NET 1.x) qui permet à l'utilisateur de rester authentifié pendant une période de temps prédéterminée.
Le problème réside dans cette période. Dans ASP.NET 1.x, la transmission d'un autre paramètre faux à RedirectFromLoginPage génère un ticket d'authentification temporaire qui expire après 30 minutes par défaut. (Vous pouvez modifier le délai d'expiration à l'aide de l'attribut Timeout dans l'élément de web.config.) Cependant, passer un autre paramètre true émettra un ticket d'authentification permanent valable 50 ans. Cela crée un problème car si quelqu'un vole cette authentification ! ticket, ils peuvent utiliser l’identité de la victime pour accéder au site Internet pendant la durée du ticket. Il existe de nombreuses façons de voler des tickets d'authentification : sonder le trafic non crypté sur les points d'accès sans fil publics, créer des scripts sur des sites Web, obtenir un accès physique à l'ordinateur de la victime, etc. - donc transmettre true à RedirectFromLoginPage est plus sécurisé que de désactiver votre site Web. Pas beaucoup mieux. Heureusement, ce problème a été résolu dans ASP.NET 2.0. RedirectFromLoginPage accepte désormais les délais d'attente spécifiés dans web.config pour les tickets d'authentification temporaires et permanents de la même manière.
Une solution consiste à ne jamais transmettre true dans le deuxième paramètre de RedirectFromLoginPage dans les applications ASP.NET 1.x. Mais cela n'est pas pratique car les pages de connexion comportent souvent une case « Me garder connecté » que l'utilisateur peut cocher pour recevoir un cookie d'authentification permanent plutôt que temporaire. Une autre solution consiste à utiliser l'extrait de code dans Global.asax (ou le module HTTP si vous préférez), qui modifie le cookie contenant le ticket d'authentification permanent avant qu'il ne soit renvoyé au navigateur.
La figure 3 contient un tel extrait de code. Si cet extrait de code se trouve dans Global.asax, il modifie la propriété Expires du cookie d'authentification Forms permanent sortant afin que le cookie expire après 24 heures. Vous pouvez définir le délai d'expiration à n'importe quelle date en modifiant la ligne commentée "Nouvelle date d'expiration".
Vous trouverez peut-être étrange que la méthode Application_EndRequest appelle une méthode Helper locale (GetCookieFromResponse) pour vérifier le cookie d'authentification pour la réponse sortante. La méthode Helper est une solution de contournement pour un autre bogue dans ASP.NET 1.1 qui entraînait l'ajout de cookies parasites à la réponse si vous utilisiez le générateur d'index de chaîne de HttpCookieCollection pour rechercher des cookies inexistants. L'utilisation d'un générateur d'index entier comme GetCookieFromResponse résout le problème.
Retour en haut État d'affichage : le tueur silencieux des performances Dans un sens, l'état d'affichage est la meilleure chose qui soit. Après tout, l’état d’affichage permet aux pages et aux contrôles de conserver leur état entre les publications. Par conséquent, vous n'avez pas besoin d'écrire du code pour empêcher le texte d'une zone de texte de disparaître lorsque vous cliquez sur un bouton, ni pour réinterroger la base de données et relier le DataGrid après une publication, comme vous le feriez dans un ASP traditionnel.
Mais l’état d’affichage présente un inconvénient : lorsqu’il devient trop volumineux, il devient un tueur silencieux de performances. Certains contrôles, tels que les zones de texte, prennent des décisions en fonction de l'état d'affichage. D'autres contrôles (notamment DataGrid et GridView) déterminent leur état d'affichage en fonction de la quantité d'informations affichées. Je serais intimidé si un GridView affichait 200 ou 300 lignes de données. Même si l'état d'affichage ASP.NET 2.0 représente environ la moitié de la taille de l'état d'affichage ASP.NET 1.x, un mauvais GridView peut facilement réduire la bande passante effective de la connexion entre le navigateur et le serveur Web de 50 % ou plus.
Vous pouvez désactiver l'état d'affichage pour des contrôles individuels en définissant EnableViewState sur false, mais certains contrôles (en particulier DataGrid) perdent certaines fonctionnalités lorsqu'ils ne peuvent pas utiliser l'état d'affichage. Une meilleure solution pour contrôler l’état d’affichage consiste à le conserver sur le serveur. Dans ASP.NET 1.x, vous pouvez remplacer les méthodes LoadPageStateFromPersistenceMedium et SavePageStateToPersistenceMedium de la page et gérer l’état d’affichage comme vous le souhaitez. Le code de la figure 4 montre un remplacement qui empêche l'état d'affichage d'être conservé dans les champs masqués et le conserve dans l'état de session. Le stockage de l'état d'affichage dans l'état de session est particulièrement efficace lorsqu'il est utilisé avec le modèle de processus d'état de session par défaut (c'est-à-dire lorsque l'état de session est stocké dans un processus de travail ASP.NET en mémoire). En revanche, si l'état de session est stocké dans la base de données, seuls les tests peuvent montrer si le maintien de l'état d'affichage dans l'état de session améliore ou diminue les performances.
La même approche est utilisée dans ASP.NET 2.0, mais ASP.NET 2.0 offre un moyen plus simple de conserver l'état d'affichage dans l'état de session. Tout d'abord, définissez un adaptateur de page personnalisé dont la méthode GetStatePersister renvoie une instance de la classe SessionPageStatePersister du .NET Framework :
public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public override PageStatePersister GetStatePersister () {return new SessionPageStatePersister(this.Page ) ; }}
Ensuite, enregistrez l'adaptateur de page personnalisé comme adaptateur de page par défaut en plaçant le fichier App.browsers dans le dossier App_Browsers de votre application comme suit :
(Vous pouvez nommer le fichier comme vous le souhaitez, à condition qu'il porte une extension .browsers.) Ensuite, ASP.NET charge l'adaptateur de page et utilise le SessionPageStatePersister renvoyé pour conserver tous les états de la page, y compris l'état d'affichage.
L’un des inconvénients de l’utilisation d’un adaptateur de page personnalisé est qu’il s’applique globalement à chaque page de l’application. Si vous préférez conserver l'état d'affichage de certaines pages en état de session mais pas d'autres, utilisez la méthode illustrée dans la figure 4. En outre, vous pouvez rencontrer des problèmes en utilisant cette méthode si l'utilisateur crée plusieurs fenêtres de navigateur au cours de la même session.
Retour en haut
État de session SQL Server : un autre tueur de performances
ASP.NET facilite le stockage de l'état de la session dans la base de données : il suffit d'activer un commutateur dans web.config et l'état de la session est facilement déplacé vers la base de données principale. Il s'agit d'une fonctionnalité importante pour les applications exécutées dans le domaine Web, car elle permet à chaque serveur du domaine de partager un référentiel commun d'état de session. L'activité supplémentaire de la base de données réduit les performances des requêtes individuelles, mais l'évolutivité accrue compense la perte de performances.
Tout cela semble bien, mais les choses changent si l'on considère quelques points :
• Même dans les applications qui utilisent l'état de session, la plupart des pages n'utilisent pas l'état de session.
• Par défaut, le gestionnaire d'état de session ASP.NET effectue deux accès (un accès en lecture et un accès en écriture) au magasin de données de session dans chaque requête, que la page demandée utilise ou non l'état de session.
En d'autres termes, lorsque vous utilisez l'option d'état de session SQL Server™, vous payez un prix (deux accès à la base de données) pour chaque requête, même pour les requêtes de pages qui n'ont rien à voir avec l'état de session. Cela a un impact négatif direct sur le débit de l’ensemble du site Web.
Figure 5 Éliminer les accès inutiles à la base de données d'état de session
Alors, que devez-vous faire ? C'est simple : désactivez l'état de session dans les pages qui n'utilisent pas l'état de session. C'est toujours une bonne idée, mais c'est particulièrement important lorsque l'état de la session est stocké dans une base de données. La figure 5 montre comment désactiver l'état de session. Si la page n'utilise pas du tout l'état de session, incluez EnableSessionState="false" dans sa directive Page, comme ceci :
<%@ Page EnableSessionState="false" ... %>
Cette directive empêche le gestionnaire d'état de session de lire et d'écrire dans la base de données d'état de session à chaque requête. Si la page lit les données de l'état de session mais n'écrit pas de données (c'est-à-dire qu'elle ne modifie pas le contenu de la session de l'utilisateur), définissez EnableSessionState sur ReadOnly comme suit :
<%@ Page EnableSessionState="ReadOnly" ... %>
Enfin, si la page nécessite un accès en lecture/écriture à l'état de session, omettez la propriété EnableSessionState ou définissez-la sur true :
<%@ Page EnableSessionState="true" ... %>
En contrôlant l'état de la session de cette manière, vous garantissez qu'ASP.NET accède à la base de données d'état de session uniquement lorsque cela est réellement nécessaire. L'élimination des accès inutiles aux bases de données est la première étape dans la création d'applications hautes performances.
À propos, la propriété EnableSessionState est publique. Cette propriété est documentée depuis ASP.NET 1.0, mais je vois encore rarement des développeurs en profiter. Peut-être parce que ce n'est pas très important pour le modèle d'état de session par défaut en mémoire. Mais c'est important pour le modèle SQL Server.
Retour au début Rôles non mis en cache L'instruction suivante apparaît souvent dans le fichier web.config d'une application ASP.NET 2.0 et dans les exemples qui présentent le gestionnaire de rôles ASP.NET 2.0 :
Mais comme indiqué ci-dessus, cette affirmation a un impact négatif significatif sur les performances. Savez-vous pourquoi ?
Par défaut, le gestionnaire de rôles ASP.NET 2.0 ne met pas en cache les données de rôle. Au lieu de cela, il consulte le magasin de données de rôle chaque fois qu'il a besoin de déterminer à quel rôle, le cas échéant, appartient l'utilisateur. Cela signifie qu'une fois qu'un utilisateur est authentifié, toutes les pages qui exploitent les données de rôle (par exemple, les pages qui utilisent des plans de site avec des paramètres de découpage de sécurité activés et les pages dont l'accès est restreint à l'aide de directives d'URL basées sur les rôles dans web.config) provoqueront le rôle. manager pour interroger le magasin de données de rôle. Si les rôles sont stockés dans une base de données, vous pouvez facilement éviter d'accéder à plusieurs bases de données pour chaque demande. La solution consiste à configurer le gestionnaire de rôles pour mettre en cache les données de rôle dans les cookies :
Vous pouvez utiliser d'autres attributs
Retour au débutSérialisation des propriétés du fichier de configuration
Le service de profil ASP.NET 2.0 fournit une solution toute faite au problème de gestion de l'état par utilisateur, tel que les préférences de personnalisation et les préférences linguistiques. Pour utiliser le service de profil, vous définissez un profil XML contenant les attributs que vous souhaitez conserver au nom d'un utilisateur individuel. ASP.NET compile ensuite une classe qui contient les mêmes propriétés et fournit un accès fortement typé aux instances de classe via les propriétés du fichier de configuration ajoutées à la page.
La flexibilité du profil est si grande qu'elle permet même d'utiliser des types de données personnalisés comme propriétés de profil. Cependant, il existe un problème que j’ai personnellement constaté et qui pousse les développeurs à commettre des erreurs. La figure 6 contient une classe simple nommée Posts et une définition de profil qui utilise Posts comme attribut de profil. Cependant, cette classe et ce fichier de configuration produisent un comportement inattendu au moment de l'exécution. Pouvez-vous comprendre pourquoi ?
Le problème est que Posts contient un champ privé appelé _count, qui doit être sérialisé et désérialisé afin de geler et recongeler complètement l'instance de classe. Toutefois, _count n'est ni sérialisé ni désérialisé, car il est privé et le gestionnaire de profils ASP.NET utilise la sérialisation XML par défaut pour sérialiser et désérialiser les types personnalisés. Le sérialiseur XML ignore les membres non publics. Par conséquent, les instances de Posts sont sérialisées et désérialisées, mais chaque fois qu'une instance de classe est désérialisée, _count est réinitialisé à 0.
Une solution consiste à faire de _count un champ public au lieu d'un champ privé. Une autre solution consiste à encapsuler _count avec une propriété publique en lecture/écriture. La meilleure solution consiste à marquer les publications comme sérialisables (à l'aide de SeriallesslyAttribute) et à configurer le gestionnaire de profils pour qu'il utilise le sérialiseur binaire .NET Framework pour sérialiser et désérialiser les instances de classe. Cette solution conserve la conception de la classe elle-même. Contrairement aux sérialiseurs XML, les sérialiseurs binaires sérialisent les champs, qu'ils soient accessibles ou non. La figure 7 montre la version corrigée de la classe Posts et met en évidence la définition modifiée du profil qui l'accompagne.
Une chose que vous devez garder à l'esprit est que si vous utilisez un type de données personnalisé comme propriété de profil et que ce type de données comporte des données membres non publiques qui doivent être sérialisées afin de sérialiser complètement une instance du type, utilisez serializeAs=" Binary" dans les propriétés de la déclaration de propriété et assurez-vous que le type lui-même est sérialisable. Sinon, la sérialisation complète ne se produira pas et vous perdrez du temps à essayer de déterminer pourquoi le profil ne fonctionne pas.
Haut de la page Saturation du pool de threads Je suis souvent très surpris par le nombre réel de pages ASP.NET que je vois lorsque j'exécute une requête de base de données et que j'attends 15 secondes ou plus pour que les résultats de la requête soient renvoyés. (J'ai également attendu 15 minutes avant de voir les résultats de ma requête !) Parfois, le retard est une conséquence inévitable de la grande quantité de données renvoyées ; Mais quelle qu'en soit la raison, les longues requêtes de base de données ou tout type d'opérations d'E/S longues entraîneront une diminution du débit dans les applications ASP.NET.
J’ai déjà décrit ce problème en détail, je n’entrerai donc pas dans les détails ici. Il suffit de dire qu'ASP.NET s'appuie sur un pool de threads limité pour gérer les requêtes. Si tous les threads sont occupés en attendant la fin d'une requête de base de données, d'un appel de service Web ou d'une autre opération d'E/S, ils seront libérés lorsqu'une opération est terminée. terminée Avant qu'un thread ne soit émis, les autres demandes doivent être mises en file d'attente et en attente. Lorsque les requêtes sont mises en file d’attente, les performances chutent considérablement. Si la file d'attente est pleine, ASP.NET entraîne l'échec des requêtes suivantes avec une erreur HTTP 503. Ce n'est pas une situation que nous aimerions voir sur une application de production sur un serveur Web de production.
La solution réside dans les pages asynchrones, l'une des fonctionnalités les meilleures mais peu connues d'ASP.NET 2.0. Une demande de page asynchrone démarre sur un thread, mais lorsqu'elle démarre une opération d'E/S, elle retourne à ce thread et à l'interface IAsyncResult d'ASP.NET. Une fois l'opération terminée, la demande informe ASP.NET via IAsyncResult, et ASP.NET extrait un autre thread du pool et termine le traitement de la demande. Il convient de noter que lorsque des opérations d'E/S se produisent, aucun thread du pool de threads n'est occupé. Cela peut améliorer considérablement le débit en empêchant les demandes d'autres pages (pages qui n'effectuent pas de longues opérations d'E/S) d'attendre dans la file d'attente.
Vous pouvez tout savoir sur les pages asynchrones dans le numéro d'octobre 2005 de MSDN® Magazine. Toute page liée aux E/S plutôt qu'à la machine et qui prend beaucoup de temps à s'exécuter a de bonnes chances de devenir une page asynchrone.
Lorsque je parle aux développeurs de pages asynchrones, ils répondent souvent par "C'est super, mais je n'en ai pas besoin dans mon application". Ce à quoi je réponds par "L'une de vos pages doit-elle interroger la base de données ?"
services Web ? Avez-vous vérifié les compteurs de performances ASP.NET pour connaître les statistiques sur les requêtes en file d'attente et les temps d'attente moyens ?Même
si votre application fonctionne correctement jusqu'à présent, à mesure que la taille de votre client augmente, la charge peut augmenter.
des applications ASP.NET réelles nécessitent des pages asynchrones. N'oubliez pas ceci !
Retour en haut Usurpation d'identité et autorisation ACL Ce qui suit est une simple directive de configuration, mais elle fait briller mes yeux chaque fois que je la vois dans web.config :
Cette directive permet l'usurpation d'identité côté client dans les applications ASP.NET. Il attache un jeton d'accès représentant le client au thread traitant la demande afin que les contrôles de sécurité effectués par le système d'exploitation portent sur l'identité du client plutôt que sur l'identité du processus de travail. Les applications ASP.NET nécessitent rarement une simulation ; mon expérience me dit que les développeurs activent souvent la simulation pour de mauvaises raisons. Voici pourquoi.
Les développeurs autorisent souvent l'usurpation d'identité dans les applications ASP.NET afin que les autorisations du système de fichiers puissent être utilisées pour restreindre l'accès aux pages. Si Bob n'a pas l'autorisation d'afficher Salaries.aspx, le développeur activera l'usurpation d'identité afin que Bob puisse être empêché d'afficher Salaries.aspx en définissant la liste de contrôle d'accès (ACL) pour refuser à Bob l'autorisation de lecture. Mais il existe le danger caché suivant : l’usurpation d’identité n’est pas nécessaire pour l’autorisation ACL. Lorsque vous activez l'authentification Windows dans une application ASP.NET, ASP.NET vérifie automatiquement l'ACL pour chaque page .aspx demandée et refuse les demandes des appelants qui ne sont pas autorisés à lire le fichier. Il se comporte toujours ainsi même si la simulation est désactivée.
Il est parfois nécessaire de justifier la simulation. Mais vous pouvez généralement l’éviter avec un bon design. Par exemple, supposons que Salaries.aspx interroge une base de données pour obtenir des informations sur les salaires que seuls les responsables connaissent. Avec l'usurpation d'identité, vous pouvez utiliser les autorisations de base de données pour refuser au personnel non-cadre la possibilité d'interroger les données de paie. Vous pouvez également ignorer l'usurpation d'identité et limiter l'accès aux données de paie en définissant une liste de contrôle d'accès pour Salaries.aspx afin que les non-administrateurs n'aient pas d'accès en lecture. Cette dernière approche offre de meilleures performances car elle évite complètement les moqueries. Cela élimine également les accès inutiles aux bases de données. Pourquoi l'interrogation de la base de données est-elle refusée uniquement pour des raisons de sécurité ?
À propos, j'ai déjà aidé à dépanner une ancienne application ASP qui redémarrait périodiquement en raison d'une empreinte mémoire illimitée. Un développeur inexpérimenté a converti l'instruction SELECT cible en SELECT * sans considérer que la table interrogée contenait des images volumineuses et nombreuses. Le problème est exacerbé par une fuite de mémoire non détectée. (Mon domaine de code géré !) Une application qui fonctionnait bien depuis des années a soudainement cessé de fonctionner car les instructions SELECT qui renvoyaient auparavant un kilo-octet ou deux de données renvoyaient désormais plusieurs mégaoctets. Ajoutez à cela le problème d'un contrôle de version inadéquat, et la vie d'une équipe de développement devra être « hyperactive » – et par « hyperactive », c'est comme devoir regarder vos enfants jouer à un jeu ennuyeux pendant que vous allez lit la nuit. Match de football ennuyeux.
En théorie, les fuites de mémoire traditionnelles ne peuvent pas se produire dans les applications ASP.NET composées entièrement de code managé. Mais une utilisation insuffisante de la mémoire peut avoir un impact sur les performances en forçant le garbage collection à se produire plus fréquemment. Même dans les applications ASP.NET, méfiez-vous de SELECT * !
Haut de page Ne vous y fiez pas entièrement : configurez le fichier de configuration de la base de données !
En tant que consultant, on me demande souvent pourquoi les applications ne fonctionnent pas comme prévu. Récemment, quelqu'un a demandé à mon équipe pourquoi une application ASP.NET ne réalisait qu'environ 1/100 du débit (requêtes par seconde) requis pour demander un document. Les problèmes que nous avons découverts auparavant sont spécifiques aux problèmes rencontrés dans les applications Web qui ne fonctionnaient pas correctement et constituent des leçons que nous devrions tous prendre au sérieux.
Nous exécutons SQL Server Profiler et surveillons l'interaction entre cette application et la base de données principale. Dans un cas plus extrême, un simple clic sur un bouton provoquait plus de 1 500 erreurs dans la base de données. Vous ne pouvez pas créer des applications hautes performances de cette façon. Une bonne architecture commence toujours par une bonne conception de base de données. Peu importe l'efficacité de votre code, cela ne fonctionnera pas s'il est alourdi par une base de données mal écrite.
Une mauvaise architecture d'accès aux données résulte généralement d'un ou plusieurs des éléments suivants:
• Mauvaise conception de la base de données (généralement conçue par les développeurs, pas les administrateurs de la base de données).
• Utilisation d'ensembles de données et de données de données - en particulier DataAdapter.update, qui fonctionne bien pour les applications de formulaires Windows et autres clients riches, mais n'est généralement pas idéal pour les applications Web.
• Une couche d'accès aux données mal conçue (DAL) qui a des calculs mal programmés et consomme de nombreux cycles CPU pour effectuer des opérations relativement simples.
Le problème doit être identifié avant de pouvoir être traité. La façon d'identifier les problèmes d'accès aux données consiste à exécuter SQL Server Profiler ou à un outil équivalent pour voir ce qui se passe dans les coulisses. Le réglage des performances est terminé après la vérification de la communication entre l'application et la base de données. Essayez-le - vous pourriez être surpris par ce que vous trouvez.
Retour à la conclusion supérieure maintenant, vous connaissez certains des problèmes et leurs solutions que vous pourriez rencontrer lors de la création d'une application de production ASP.NET. La prochaine étape consiste à examiner de plus près votre propre code et à essayer d'éviter certains des problèmes que j'ai décrits ici. ASP.NET a peut-être réduit la barrière à l'entrée pour les développeurs Web, mais vos applications ont toutes les raisons d'être flexible, stable et efficace. Veuillez considérer cela avec soin pour éviter les erreurs des débutants.
La figure 8 fournit une courte liste de contrôle que vous pouvez utiliser pour éviter les pièges décrits dans cet article. Vous pouvez créer une liste de contrôle de défaut de sécurité similaire. Par exemple:
• Avez-vous crypté des sections de configuration contenant des données sensibles?
• Vérifiez-vous et validez-vous l'entrée utilisée dans les opérations de base de données et utilisez-vous une entrée codée HTML comme sortie?
• Votre répertoire virtuel contient-il des fichiers avec des extensions non protégées?
Ces questions sont importantes si vous appréciez l'intégrité de votre site Web, les serveurs qui l'hébergent et les ressources backend sur lesquelles ils comptent.
Jeff Prosise est un éditeur contributif du magazine MSDN et l'auteur de plusieurs livres, dont la programmation Microsoft .NET (Microsoft Press, 2002). Il est également co-fondateur de Winellect, une société de conseil et d'éducation logicielle.
À partir du numéro de juillet 2006 du magazine MSDN.