Exporter (0) Imprimer
Développer tout

Utilisation de l'état de session ASP.NET dans un service Web

Matt Powell
Microsoft Corporation

Introduction

Un des défis les plus courants auquel se frottent les développeurs Web est le contrôle de l'état alors que celui-ci est absent de l'environnement HTTP. Toutes sortes de méthodes plus astucieuses l'une que l'autre ont été utilisées pour contourner le problème posé par l'absence d'état : données d'application repostées avec chaque demande, utilisation de l'authentification HTTP pour faire coïncider les demandes avec des utilisateurs spécifiques, voire utilisation de cookies HTTP pour préserver l'état d'une série de demandes. Une méthode particulièrement futée et qui ne laisse rien paraître de la difficulté existante consiste tout simplement à utiliser la classe System.Web.SessionState.HttpSessionState de Microsoft® ASP.NET. Vous pouvez utiliser la classe HttpSessionState d'ASP.NET à partir d'une méthode Web de la même façon de depuis les pages ASPX, mais la procédure comporte de légères différences.

Présentation rapide des sessions ASP.NET

L'état de session ASP.NET est géré à l'aide d'un des deux mécanismes sous-jacents suivants. Le premier consiste à utiliser les cookies HTTP. Le principe de base est que lorsque le client émet une demande, le serveur renvoie une réponse associée à un en-tête HTTP Set-Cookie contenant une paire nom/valeur. Pour toutes les demandes ultérieures au même serveur, le client envoie la même paire nom/valeur dans un en-tête de cookie HTTP. Le serveur peut alors utiliser la valeur pour associer les demandes suivantes à la demande initiale. ASP.NET utilise un cookie contenant une ID de session pour contrôler l'état de session, ID qui est ensuite utilisée pour trouver l'instance correspondante de la classe HttpSessionState de cet utilisateur particulier. La classe HttpSessionState met simplement à votre disposition une collection générique dans laquelle vous pouvez stocker toutes les données que vous voulez.

L'autre mécanisme utilisé par ASP.NET pour contrôler l'état de session n'utilise pas les cookies. Certains navigateurs ne prennent pas les cookies en charge ou leur configuration ne prévoit pas leur conservation ou leur envoi. ASP.NET offre un mécanisme permettant de court-circuiter ce problème par réacheminement d'une demande vers une URL dans laquelle est imbriquée l'ID de session ASP.NET. À la réception de la demande, l'ID de session imbriquée est tout simplement extraite de l'URL et elle est utilisée pour trouver l'instance appropriée de l'objet session. Cette méthode est impeccable pour les navigateurs qui émettent des demandes HTTP de type GET, mais elle est à l'origine de problèmes lorsque vous créez un code Microsoft® .NET qui utilise un service Web XML.

Notez qu'il est parfois judicieux de stocker les informations d'état dans les cookies proprement dits, plutôt que dans l'objet session ASP.NET. En évitant l'objet session, vous économisez les ressources du serveur et vous n'avez pas à vous soucier de problèmes tels que la recherche d'une instance spécifique de l'objet session sur un parc Web, les instances de l'objet session éliminées à cause d'un délai trop long entre les demandes ou les instances de session mise en attente sans raison jusqu'à expiration de leur délai d'attente. Toutefois, si vos données contiennent des informations d'implémentation que vous ne souhaitez pas partager avec les utilisateurs du service ou des informations privées que vous ne souhaitez pas diffuser sur un canal sans cryptage, ou encore si elles ne se prêtent pas à être sérialisées dans un en-tête HTTP, dans ce cas, il peut être bon de faire appel à la classe HttpSessionState d'ASP.NET. La classe HttpSessionState retourne une clé d'index qui permet d'apparier un utilisateur particulier à une instance de cette classe contenant les informations enregistrées pour cet utilisateur. Les utilisateurs qui créent des services Web ASP.NET ont accès au choix à la classe HttpSessionState d'ASP.NET et aux cookies HTTP.

Pourquoi utiliser un mécanisme HTTP pour contrôler l'état dans un service Web XML ?

Il existe un grand nombre de moyens pour gérer l'état entre les demandes SOAP. Une possibilité consiste à inclure dans l'en-tête SOAP du message SOAP une information telle que l'ID de session ASP. Le problème est que vous devez quand même : 1) écrire vous-même la partie serveur du code et 2) vous assurer que les clients traitent l'en-tête contenant l'ID de session comme un cookie HTTP et qu'ils vous le renvoient avec chaque demande. Il y a sans doute des cas où la méthode en-tête SOAP est la plus appropriée, mais il y a aussi des situations où l'approche HTTP est la meilleure.

L'état de session ASP.NET est déjà créé pour vous. La classe HttpSessionState est disponible et elle vous permet de stocker facilement les objets sessions. La plupart des clients HTTP sont configurés pour renvoyer les cookies définis par le serveur et il se trouve que HttpSessionState est compatible avec le protocole sous-jacent utilisé le plus souvent pour les communications SOAP, à savoir HTTP. Il en ressort que l'utilisation de la prise en charge de session ASP.NET peut souvent être une décision judicieuse pour répondre aux nécessités relatives à la gestion d'état.

Activation de la prise en charge de session sur le serveur

Par défaut, la prise en charge de session ASP.NET est désactivée pour chaque méthode Web. Vous devez l'activer de façon explicite pour chacune des méthodes Web appelée à utiliser l'état de session. Pour ce faire, il faut ajouter la propriété EnableSession à l'attribut WebMethod de la fonction. Vous trouverez ci-après le code d'une méthode Web accédant à l'objet HttpSessionState dont la propriété EnableSession est activée (définie sur True).

<WebMethod(EnableSession:=True)> _
 Public Function IncrementSessionCounterX() As Integer
 Dim counter As Integer
 If Context.Session("Counter") Is Nothing Then
  counter = 1
 Else
  counter = Context.Session("Counter") + 1
 End If
 Context.Session("Counter") = counter
 Return counter
 End Function

Il va de soi que l'activation de la prise en charge de session pour une méthode Web n'implique en rien qu'elle soit activée pour une autre. En fait, la propriété Context.Session est nulle si EnableSession n'est pas explicitement définie sur True pour une méthode Web particulière.

N'oubliez pas qu'il est possible de désactiver les sessions à l'aide d'un paramètre web.config de façon à ce que même si vous utilisez la propriété EnableSession dans l'attribut WebMethod, Context.Session soit toujours nulle. L'élément /configuration/system.web/sessionState possède un attribut de mode servant à configurer la façon dont est géré l'état de session pour votre application ASP.NET. Par défaut, le mode choisi est InProc, ce qui veut dire que les objets HttpSessionState sont tout simplement conservés dans la mémoire du processus ASP.NET. Si le mode choisi est Off, l'état de session n'est pas pris en charge dans l'application ASP.NET.

Dans la perspective du serveur HTTP, une session ASP.NET est associée à une application ASP.NET donnée, point final. Il en résulte que la même instance de la classe HttpSessionState est utilisée pour toutes les demandes ASP.NET avec prise en charge de session au sein d'un même répertoire virtuel pour un utilisateur particulier. Une demande portant sur un répertoire virtuel associé à un cookie contenant la même ID de session a pour conséquence l'impossibilité pour ASP.NET de trouver l'objet session correspondant (parce que l'ID de session a été définie pour une autre application ASP.NET. En ce qui concerne les sessions, ASP.NET ne fait aucune différence entre les demandes ASPX et ASMX, aussi est-il en théorie possible de partager un état de session entre un appel de méthode Web et un fichier ASPX normal. Toutefois, il y a une marge en théorie et pratique, et le côté client pose quelques problèmes que nous allons examiner.

Lorsque vous définissez un cookie HTTP, vous pouvez l'associer à une durée limite de validité. Cette durée correspond à la période pendant laquelle le client doit continuer à renvoyer le cookie au serveur. Si aucune limite de validité n'est fixée pour un cookie, il n'est envoyé que pendant la durée de vie du processus émetteur des demandes. Par exemple, Microsoft® Internet Explorer continue de retourner le cookie jusqu'à la fermeture de l'instance en cours du navigateur. Aucune limite de validité n'est fixée pour les cookies contenant un ID de session utilisés par ASP.NET. Par conséquent, si plusieurs processus d'une machine cliente envoient au serveur des demandes HTTP, ils ne partagent pas le même objet HttpSessionState, et ce même si ces deux processus sont simultanés.

Si vous faites en même temps plusieurs appels à un service Web pour le même processus, les demandes sont organisées sur le serveur de façon à être exécutées l'une après l'autre. Contrairement aux pages .ASPX qui autorisent l'accès en lecture seule à l'objet HttpSessionState, ce qui permet le traitement simultané de plusieurs demandes, les services Web ASP.NET n'offrent pas cette possibilité. Tous les appels à une méthode Web avec prise en charge des sessions possèdent un accès lecture/écriture et sont organisés au sein de chaque session.

Problèmes inhérents au client

L'utilisation dans de bonnes conditions des fonctionnalités de la classe HttpSessionState dans votre service Web repose sur certaines présuppositions concernant les utilisateurs de ce service. Condition première et essentielle, si vous utilisez la méthode par défaut de contrôle de l'état de session, les cookies HTTP, il faut impérativement que vos clients prennent ceux-ci en charge. Si vous utilisez la méthode sans cookies, vos clients doivent être en mesure de réacheminer leurs demandes vers les URL modifiées dans lesquelles sont imbriquées les ID de session, et surtout ils doivent accepter de le faire. Comme vous le constatez, même dans le cas d'une application cliente .NET, cela ne va pas de soi.

Tout fonctionne à partir du navigateur

Si vous développez un service Web ASP.NET dans Microsoft® Visual Studio® .NET, le comportement par défaut de débogage consiste à lancer Internet Explorer et à naviguer jusqu'au fichier .ASMX. Ceci vous permet généralement d'accéder à une interface HTML conviviale pour appeler les méthodes Web. Cette interface est très commode pour éliminer les erreurs du code du service Web et, si vous avez affecté la valeur True à la propriété EnableSession de votre méthode Web, cela marche en principe à merveille. Même si vous activez la prise en charge de session sans cookies, le client navigateur fonctionne parfaitement et la session se déroule conformément à vos attentes.

Toutefois, la majorité des demandes au service Web ne proviennent pas d'un navigateur. Que se passe-t-il lorsque vous créez une application cliente qui utilise la fonction « Ajouter une référence Web » de .NET Framework ? Examinons les résultats.

Problèmes posés par l'utilisation de la fonction d'ajout de référence Web

J'ai créé un service Web XML élémentaire à l'aide de l'extrait de code que nous avons vu plus haut. Si vous avez bien retenu ce que vous avez lu, la méthode Web porte le nom de IncrementSessionCounter et a tout simplement pour fonction d'enregistrer un nombre entier dans l'objet HttpSessionState, de l'incrémenter à chaque appel et de retourner la valeur actuelle. Depuis le client navigateur, nous constatons, comme nous nous y attendions, qu'à chaque appel ce nombre augmente de un.

J'ai ensuite créé une application Microsoft® Windows Form très simple et ajouté une référence Web pour mon service Web. Le code d'appel du service a l'allure suivante :

' Ne fonctionne pas avec les sessions ASP.NET 
Private Sub Button1_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles Button1.Click
 Dim proxy As New localhost.Service1()
 Dim ret As Integer
 ret = proxy.IncrementSessionCounter()
 Label1.Text = "Résultat : " & CStr(ret)
End Sub

La première fois que j'appelle le service Web, tout fonctionne comme prévu. La méthode Web retourne la valeur initiale de la variable de session, à savoir 1. À présent, si je clique sur Button1 pour appeler à nouveau la méthode Web, je m'attends à voir apparaître la valeur 2. Or, j'ai beau cliquer autant de fois que je veux sur Button1, c'est toujours 1 qui s'affiche.

À première vue, on peut penser que la cause du problème est que je crée une nouvelle instance de la classe proxy pour mon service Web, de sorte qu'à chaque fois que je clique sur le bouton, je perds mes cookies. Malheureusement, même si vous faites passer le code d'initialisation de la classe proxy dans le constructeur de la classe Form et que vous utilisez la même instance de la classe proxy pour chaque appel de méthode Web, vous n'obtiendrez jamais que la variable de session renvoie une valeur supérieure à 1.

Le problème provient des cookies. Le code du service Web ne trouve pas d'ID de session valide avec la demande et il en crée une nouvelle à chaque appel avec l'objet HttpSessionState, ce qui explique que la valeur retournée soit toujours 1. La raison en est que la classe proxy cliente héritière de la classe System.Web.Services.Protocols.SoapHttpClientProtocol n'est associée à aucune instance de la classe System.Net.CookieContainer, ce qui revient à dire qu'il n'y a nulle part où enregistrer les cookies retournés. Pour remédier à ce problème, j'ai modifié mon code comme suit. Le nouveau code est mis en évidence :

' Fonctionne pour les sessions ASP.NET avec cookies, mais PAS
' pour les sessions sans cookies.
Private Cookies As System.Net.CookieContainer

Private Sub Button1_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles Button1.Click
 Dim proxy As New localhost.Service1()
 Dim ret As Integer
 ' Définir le conteneur de cookies de la classe proxy
If Cookies Is Nothing Then
Cookies = New System.Net.CookieContainer()
End If
proxy.CookieContainer = Cookies
 ret = proxy.IncrementSessionCounter()
 Label1.Text = "Résultat : " & CStr(ret)
End Sub

Maintenant le code fonctionne comme prévu ! À chaque clic sur Button1, la valeur retournée augmente de 1. Notez que la variable Cookies n'est pas déclarée à l'intérieur de ma fonction, mais en tant que membre privé de la classe Form. Je dois utiliser la même instance de la classe CookieContainer avec chaque demande si je veux que le même cookie avec ID de session soit renvoyé au serveur. Ceci explique pourquoi aucun conteneur de cookies par défaut n'est automatiquement associé à une instance de la classe SoapHttpClientProtocol. Il y a fort à parier que vous préféreriez utiliser un conteneur de cookies géré individuellement susceptible d'être partagé par plusieurs instances de la classe SoapHttpClientProtocol, plutôt que d'en créer automatiquement un nouveau pour chaque instance.

Sessions sans cookies

En vous plaçant du point de vue du développeur du service Web, vous vous dites sans doute que bon nombre d'utilisateurs de votre service risquent d'oublier d'adjoindre un conteneur de cookies à leurs classes proxy clientes. Vous ajoutez sûrement, avec un brin de malice, que dans le fond les sessions sans cookies sont une solution toute trouvée à ce problème. Si vous affectez à l'attribut cookieless de l'élément sessionState de web.config la valeur « true », vous constatez que les sessions se déroulent à merveille lorsque vous appelez les méthodes Web via l'interface du navigateur. Malheureusement, il existe également des problèmes liés à l'utilisation de la fonction « Ajouter une référence Web » de Visual Studio .NET.

Pour étudier les sessions sans cookies, j'ai résolu de prendre le code client utilisé ci-avant pour voir s'il serait opérationnel avec un service Web configuré pour les sessions sans cookies. Je n'ai pas pris la peine de supprimer la partie du code concernant le conteneur de cookies car je souhaitais disposer d'un code fonctionnant aussi bien pour les sessions avec que sans cookies. Armé de l'optimisme qui me caractérise, je me suis contenté d'exécuter le code tel quel. Sans être outre mesure étonné, j'ai été déçu de constater l'exception suivante :

Une exception non gérée du type 'System.Net.WebException' s'est produite dans system.web.services.dll

Informations supplémentaires : la demande a échoué avec le message d'erreur :
--
<html><head><title>Objet déplacé</title></head><body>
<h2>Objet déplacé <a href='/HttpSessionState/(l2z3psnhh2cf1oahmai44p21)/service1.asmx'>ici</a>.</h2>
</body></html>

Ce qui s'est passé, c'est que la réponse à la demande HTTP n'a pas été du type « 200 OK ». Pour ceux d'entre vous qui sont familiarisés avec HTTP, vous avez probablement identifié dans la code HTML renvoyé que la réponse était de type « 302 Found ». Cela signifie que la demande a été réacheminée vers l'URL indiquée dans le lien hypertexte. Le code HTML qui s'affiche n'est rien d'autre en fait qu'une petite gâterie que vous offre le navigateur si pour une raison quelconque il n'autorise pas les réacheminements ou bien tant que la demande de réacheminement n'est pas parvenue à son terme. Si vous regardez le lien hypertexte de plus près, vous constatez que la portion href comporte une sous-chaîne particulièrement intéressante : « (l2z3psnhh2cf1oahmai44p21) ». Si vous avez prêté attention jusque là, vous avez vraisemblablement compris qu'il s'agit de l'ID de session ASP.NET qui a été imbriquée dans l'URL vers laquelle nous avons été redirigés. Ce qu'il faut maintenant, c'est que notre classe proxy cliente renvoie à nouveau la demande à cette nouvelle URL.

En tant que vieux renard de la programmation avec l'ancienne interface API Win32 WinInet, je me suis mis en quête d'une propriété de notre classe proxy permettant d'activer automatiquement les réacheminements. En termes profanes, cela veut tout bêtement dire que si nous avons reçu une réponse HTTP « 302 Found », il suffit d'envoyer à nouveau la demande à l'URL indiquée par l'en-tête HTTP Location de la réponse. J'ai eu mon petit moment de fierté lorsque le module Microsoft® IntelliSense® intégré à Visual Studio .NET m'a mis sous le nez la propriété AllowAutoRedirect de ma classe proxy. En un clin d'œil, j'ai ajouté au code la ligne suivante :

proxy.AllowAutoRedirect = True

J'ai donné une nouvelle chance à mon programme en me disant que c'était un peu moins compliqué que de créer une classe CookieContainer et de l'affecter à ma classe proxy. J'ai obtenu en retour l'exception suivante (que j'ai abrégée pour ne pas prendre trop de place) :

Une exception non gérée du type 'System.InvalidOperationException' s'est produite 
 dans system.web.services.dll

Informations supplémentaires : le client a trouvé 'text/html; charset=utf-8' comme 
 type de contenu de la réponse, alors qu'il attendait 'text/xml'.

La demande a échoué avec le message d'erreur : …

En examinant le contenu du message d'erreur, vous constatez qu'il s'agit de la page HTML que vous voyez lorsque vous naviguez jusqu'au fichier .ASMX. Là, je suis sûr que vous vous demandez pourquoi la réponse est en HTML alors que la demande au service Web est en XML (sous la forme d'une enveloppe SOAP). Il s'avère qu'en fait vous n'avez pas envoyé une demande HTTP POST avec une enveloppe SOAP, mais simplement une demande HTTP GET sans corps, ce qui, en toute logique, a conduit votre service Web à vous prendre pour un navigateur et à vous envoyer une réponse HTML appropriée dans ces cas-là. Comment cela a-t-il pu se produire ?

Si vous lisez la spécification HTTP leave-msdn france Site en anglais, vous découvrez qu'il est normal pour un client HTTP d'envoyer une demande HTTP GET à l'URL indiquée en réaction à réponse HTTP « 302 Found », même si la demande initiale était de type HTTP POST. Cette solution convient fort bien aux navigateurs car de toute façon leurs demandes sont presque toutes du type HTTP GET. Elle convient beaucoup moins bien lorsque vous voyez ce résultat alors que vous envoyez des données à une URL.

La justification de cette disposition est qu'il peut y avoir dans les données envoyées des informations à ne pas mettre sous tous les yeux et que, par conséquent, il faut l'accord de l'utilisateur pour les réacheminer. Si vous allez directement au nouvel emplacement à partir d'une instruction de redirection automatique, il est évident que vous ne disposez pas de l'accord formel de l'utilisateur. Par conséquent, au lieu des données elles-mêmes, c'est une simple demande HTTP GET qui est envoyée.

J'ai mis en place les modifications suivantes pour définir l'URI sur la classe proxy, capturer l'exception WebException « 302 Found », demander à l'utilisateur la permission de réacheminer sa demande et appeler à nouveau la fonction en indiquant la nouvelle destination (les changements sont mis en évidence) :

' Fonctionne à la fois pour les sessions ASP.NET avec et sans cookies.
Private Cookies As System.Net.CookieContainer
Private webServiceUrl as Uri

Private Sub Button1_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles Button1.Click
 Dim proxy As New localhost.Service1()
 Dim ret As Integer
 ' Définir le conteneur de cookies de la classe proxy
 If Cookies Is Nothing Then
 Cookies = New System.Net.CookieContainer()
 End If
 proxy.CookieContainer = Cookies
 ' Définir URL sur proxy
If webServiceUrl Is Nothing Then
webServiceUrl = New Uri(proxy.Url)
Else
proxy.Url = webServiceUrl.AbsoluteUri
End If
Try
 ret = proxy.IncrementSessionCounter()
 Catch we As WebException
' Pour vérifier l'état du code HTTP, nous avons
' besoin d'une réponse HttpWebResponse.
If TypeOf we.Response Is HttpWebResponse Then
 Dim HttpResponse As HttpWebResponse
 HttpResponse = we.Response
 If HttpResponse.StatusCode = HttpStatusCode.Found Then
 ' Ceci est une réponse de type "302 Found" . Demander à l'utilisateur
 ' s'il accepte la redirection.
 If MsgBox(String.Format(redirectPrompt, _
  HttpResponse.Headers("Location")), _
  MsgBoxStyle.YesNo) = _
  MsgBoxResult.Yes Then
  ' OK. Définir le nouvel emplacement
  ' et réessayer.
  webServiceUrl = New Uri(webServiceUrl, _
  HttpResponse.Headers("Location"))
  Button1_Click(sender, e)
  Return
 End If
 End If
End If
Throw we
End Try
 Label1.Text = "Résultat : " & CStr(ret)
End Sub

Et maintenant, le code de la session ASP.NET fonctionne enfin comme prévu ! Dans le cadre d'une application qui vous est propre, vous pouvez déterminer si oui ou non il faut demander à l'utilisateur l'autorisation de réacheminer leurs demandes HTTP POST. Par exemple, si vous appeliez ce code depuis un service, vous ne souhaiteriez pas créer une boîte de dialogue qui serait invisible.

Cela peut sembler être beaucoup de travail rien que pour faire fonctionner les sessions ASP.NET de façon satisfaisante, mais vous ne devez pas perdre de vue que le code illustré ici a d'autres utilités. Par exemple, n'importe quel service Web sur n'importe quelle plate-forme utilisant les cookies HTTP a besoin d'un code pour le conteneur de cookies. De la même manière, il peut y avoir une foule d'autres raisons entraînant l'affichage d'une réponse « 302 Found » en réaction à l'envoi d'une demande à un service Web. Dans une application d'envergure, vous voudrez sans doute pouvoir utiliser un certain nombre de scénarios spéciaux pour appeler un service Web. La gestion des cookies et des réacheminements en sont deux exemples et il est probable que vous avez besoin des les inclure dans votre code d'appel de service Web de façon régulière.

Conclusion

Les sessions ASP.NET peuvent être très utiles pour contrôler l'état entre les appels aux méthodes Web dans votre service Web. Vous devez avoir conscience de ce qu'il peut y avoir des problèmes qui devront être gérés par des applications clientes auxquelles vous n'avez pas forcément accès lorsque vous testez le service Web avec l'interface du navigateur. Heureusement, ces problèmes ne sont pas particulièrement difficiles à régler.

À votre service

Matt Powell est membre de l'équipe des exemples d'architecture MSDN, au sein de laquelle il a contribué au développement du très innovant SOAP Toolkit 1.0. Par ailleurs, Matt Powell Matt a participé à la rédaction de l'ouvrage Running Microsoft Internet Information Server (en anglais) édité chez Microsoft Press ainsi que de nombreux articles de presse, et il se consacre chaque jour à sa très belle famille.



Dernière mise à jour le lundi 7 octobre 2002



Pour en savoir plus
Afficher:
© 2014 Microsoft