Plusieurs techniques sont à votre disposition pour interroger un service Web avec le Framework .NET. Dans cet article nous allons décrire ces deux principales techniques et voir comment appeler de manière asynchrone un service Web.
La première technique décrite est l’envoi de requêtes HTTP à partir de la classe WebRequest. La seconde consiste à développer une classe proxy héritant de SoapHttpClientProtocol permettant de construire et générer les requêtes HTTP de manière transparente.
Nous nous appuierons sur le service Web mis à votre disposition pour lister les formations à notre catalogue : http://www.bewise.fr/training/web-service.asmx. Nous utiliserons le service permettant de consulter l’ensemble des formations proposées correspondant à un mot-clé.
1 Interrogation par construction de requêtes HTTP
Il s’agit de construire une requête HTTP contenant les informations utiles à l’interrogation du service. Deux questions se posent :
- Comment émettre des requêtes HTTP avec le Framework ?
- Comment doit être construite cette requête ?
Pour répondre à la première question, il faut se pencher sur la classe WebRequest de l’espace de noms System.Net de l’assemblage System.dll pour avoir la réponse.
Le fonctionnement est le suivant :
- Tout d’abord vous devez utiliser la méthode statique Create qui permet, en fonction d’une URI (dans notre cas d’une URL), d’instancier un objet de type HttpWebRequest ou FileWebRequest (tous deux héritant de la classe WebRequest) en fonction du protocole indiqué (http://, https:// ou file://).
- L’objet de type HttpWebRequest obtenu va vous permettre de construire aisément votre requête HTTP en proposant notamment les propriétés et méthodes suivantes :
- Method : indiquez le verbe HTTP à utiliser (POST, GET, HEAD, PUT…). La valeur par défaut est GET.
- ContentType et ContentLength : si vous désirez envoyer des données de requête (dans le cas d’un POST par exemple) indiquez le type mime de contenu que vous désirez envoyer, par exemple text/xml pour des données XML et la longueur de ces données.
- GetRequestStream : permet de récupérer un flux pour renseigner les données de requête à envoyer
- Headers : tout header supplémentaire que vous désirez ajouter peut être indiqué grâce à cette collection.
- Il ne reste plus qu’à envoyer la requête et récupérer le résultat. C’est la méthode GetResponse qui effectue cette opération. La fonction retourne un objet de type HttpWebResponse qui vous permettra d’analyser le contenu de la réponse en interrogeant notamment GetResponseStream pour avoir les données associées à celle-ci.
Le premier problème étant résolu, passons au deuxième qui est de savoir comment formater notre requête HTTP pour qu’elle soit comprise par le service Web.
Pour cela, soit vous consultez la spécification SOAP, soit vous utilisez le handler du fichier asmx d’ASP .NET qui vous propose une fonction intéressante. Il permet, en effet, de consulter avec votre navigateur :
- une page descriptive du service,
- le fichier WSDL (Web Service Description Language) associé identifiant les types de données, messages, appels HTTP et méthodes exposées,
- une page descriptive pour chacune des méthodes du service Web. Ces pages permettent également d’invoquer les méthodes où de consulter la syntaxe pour réaliser l’appel.
Voici un exemple de la page générée pour la méthode RechercherFormationsParMotCle du Web service.

Maintenant que vous connaissez exactement la structure du message SOAP à générer, il ne reste plus qu’à le réaliser. La solution la plus simple consiste à utiliser la classe XmlTextWriter pour écrire le message SOAP.
Voici un exemple de code en Visual Basic .NET :
' Déclarations
Dim req As HttpWebRequest
Dim rep As HttpWebResponse
Dim sr As StreamReader
Dim sw As StreamWriter
Dim w As New StringWriter()
Dim t As New XmlTextWriter(w)
' Construction du message SOAP
t.Formatting = Formatting.Indented
t.WriteStartDocument()
t.WriteStartElement("soap", "Envelope",
"http://schemas.xmlsoap.org/soap/envelope/")
t.WriteAttributeString("xmlns:xsi",
"http://www.w3.org/2001/XMLSchema-instance")
t.WriteAttributeString("xmlns:xsd",
"http://www.w3.org/2001/XMLSchema")
t.WriteStartElement("Body",
"http://schemas.xmlsoap.org/soap/envelope/")
t.WriteStartElement("RechercherFormationsParMotCle",
"http://www.bewise.fr/training")
t.WriteElementString("keyword", txtMotCle.Text)
t.WriteEndElement()
t.WriteEndElement()
t.WriteEndElement()
t.Close()
txtRequete.Text = w.ToString()
' Construction requête
req = CType(Net.WebRequest.Create(
"http://www.bewise.fr/training/web-service.asmx"), HttpWebRequest)
req.Method = "POST"
req.ContentType = "text/xml; charset=utf-8"
req.ContentLength = w.ToString().Length
req.Headers.Add("SOAPAction",
"http://www.bewise.fr/training/RechercherFormationsParMotCle")
sw = New StreamWriter(req.GetRequestStream())
sw.Write(w.ToString())
sw.Close()
' Execution de la requête
rep = CType(req.GetResponse(), HttpWebResponse)
' Affichage de la réponse
sr = New StreamReader(rep.GetResponseStream)
txtReponse.Text = sr.ReadToEnd()
sr.Close()
Remarque : il aussi possible d’utiliser une requête HTTP ne comportant pas de message SOAP pour appeler votre Web Service. Vous avez la possibilité d’utiliser le verbe GET en passant les paramètres dans l’URL ou le verbe POST en passant les paramètres dans le corps de la requête à la manière d’un formulaire HTML. Bien que cette technique soit plus simple à réaliser, elle n’offre pas la possibilité de faire passer des données complexes (objets, structure, arborescence XML…).
2 Interrogation par utilisation d’un proxy
Vous conviendrez que l’écriture du message SOAP est fastidieuse. L’interprétation du résultat (que nous n’avons pas traité ici) est aussi un travail important. C’est pour cela qu’il existe, dans l’espace de noms System.Web.Services.Protocols, une classe nommée SoapHttpClientProtocol qui expose des méthodes permettant de construire aisément des appels SOAP. Ceci est réalisé principalement grâce à la méthode Invoke déclarée Protected. Il va donc être nécessaire de développer une classe héritant de SoapHttpClientProtocol pour faire appel à la méthode Invoke de la classe mère. Cette classe est l’occasion de créer un leurre reproduisant l’interface publique du service Web ciblé. Ainsi, le proxy développé permettra au développeur de réaliser les appels au service Web comme des appels à tout composant .NET. C’est le Framework qui s’occupe de sérialiser l’information en XML, de l’envoyer sur HTTP, d’attendre la réponse et de la reconstituer au client.
Cependant, il faut développer ce proxy. Ici encore nous pouvons être aidé :
- Soit par l’utilisation de l’utilitaire wsdl.exe qui permet de générer la classe proxy dans le langage de votre choix en analysant les données WSDL. Pour générer le proxy en Visual Basic .NET de notre exemple, il suffit d’exécuter la commande suivante :
wsdl /l:vb http://www.bewise.fr/training/web-service.asmx?wsdl
Notez qu’il existe un certain nombre d’options intéressantes notamment l’option /protocol:<protocol> permettant de spécifier le protocole à utiliser. Par défaut, le protocole SOAP est utilisé mais vous pouvez changer cette configuration pour une requête HTTP simple de type GET en indiquant HttpGet ou de type POST en indiquant HttpPost.
- Soit par l’utilisation des « Web References » de Visual Studio .NET. En effet, vous avez la possibilité de générer un proxy en utilisant le menu [Project] | [Add Web Reference…] où vous indiquerez le lien vers votre service Web. En cliquant sur Add Reference vous générez la classe proxy. Pour la visualiser, vous devez voir tous les fichiers du projet (menu [Project] | [Show All Files]) et consulter le fichier présent dans votre référence Web sous le fichier Reference.map.

Regardons de plus près le code de ce fameux proxy. Tout d’abord, il hérite comme nous l’avions évoqué de la classe SoapHttpClientProtocol.
Public Class Formations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
Ensuite, dans le constructeur, il référence le point d’entrée du service Web. Vous pouvez à tout moment modifier cette propriété URL pour vous connecter à une autre implémentation.
Public Sub New()
MyBase.New
Me.Url = "http://totoro/internet/training/web-service.asmx"
End Sub
Enfin, pour chacune des méthodes exposées par le service Web, il crée la méthode équivalente dans le proxy en faisant appel à la méthode Invoke dont nous avons parlé précédemment. Voici l’exemple associé à la méthode RechercherFormationsParMotCle (ce nom n’est en fait qu’un alias de la méthode réelle SearchTraining du service Web). Nous avons volontairement omis ici les attributs par souci de concision.
Public Function SearchTraining(ByVal keyword As String) As
System.Data.DataSet
Dim results() As Object = Me.Invoke("SearchTraining",
New Object() {keyword})
Return CType(results(0),System.Data.DataSet)
End Function
L’appel à notre service (récupération des formations ayant comme mot-clé « Visual C# » et affichage dans une DataGrid) peut se faire donc tout simplement avec le code suivant :
Dim MonProxy As New Bewise.Formations()
MaGrid.DataSource =
MonProxy.SearchTraining("Visual C#").Table(0)
Remarque : il est important de noter que tout changement de l’interface du service Web nécessitera une mise à jour de la classe proxy. Ceci peut être réalisé par un clic droit | [Update Web Reference] sur la référence du service Web.
3 Interrogation asynchrone
Si nous regardons bien le code du proxy, nous nous apercevons qu’à chaque méthode du service Web correspond non pas une méthode mais trois. Les deux supplémentaires, nommées BeginMethode et EndMethode, vont nous servir à implémenter un appel asynchrone.
Il est, en effet, souvent utile de contacter le service Web en arrière plan. Les raisons principales sont les suivantes :
- L’appel à service sera toujours plus long qu’un appel classique de composant même distant,
- L’utilisateur peut souvent effectuer d’autres tâches dans l’application (dans le cas d’un client lourd) avant d’être averti de la mise à disposition des données récupérées,
- L’application peut lancer des traitements en parallèle (tels que plusieurs exécution de services Web) afin de profiter pleinement de ce temps de latence.
Voici les étapes pour réaliser un appel asynchrone :
- Création d’une variable référençant la méthode qui sera exécutée lors de la réception du retour du service Web. Cette méthode doit avoir une signature particulière correspondant au délégué AsyncCallback.
Dim fin As New AsyncCallback(AddressOf RecupFormationsAsyncFin)
- Appel de la méthode commençant par Begin en prenant soin de lui passer la référence vers la méthode créée précédemment et l’objet qui a émis cet appel.
MonProxy.BeginSearchTraining("Visual C#", fin, MonProxy)
- Création de la méthode RecupFormationsAsyncFin suivant la signature du délégué AsyncCallback. Le paramètre IAsyncResult nous permettra grâce à la propriété AsyncState de récupérer la référence vers le proxy.
Private Sub RecupFormationsAsyncFin(ByVal resultat As IAsyncResult)
Dim MonProxy As bewise.Formations
MonProxy = resultat.AsyncState
...
- Enfin, il faut récupérer le résultat. Pour cela, un simple appel à la méthode commençant par End suffit. Notez cependant qu’il faut lui passer la variable de type IAsyncResult en paramètre pour que le proxy sache quel retour de quel appel il doit vous fournir. La variable donnees a été déclarée en tant que membre de la classe et de type DataSet.
...
donnees = local.EndSearchTraining(resultat)
End Sub
Tout pourrait s’arrêter ici mais, vous avez sûrement envie d’afficher les données dans un contrôle, par exemple une DataGrid. Or, il faut savoir que la plupart des membres des contrôles Windows Forms (comme la propriété DataSource de la DataGrid) doivent être exécutés avec le thread qui a créé le contrôle. Le problème peut paraître compliqué mais la résolution est simple. En effet, il existe une méthode Invoke sur les contrôles qui permet d’exécuter une procédure (dont la référence est passée en paramètre) avec le thread créateur du contrôle. La procédure RecupFormationsAsyncFin doit donc être complétée comme suit.
..
MaGrid.Invoke(New MethodInvoker(AddressOf AfficherFormations))
End Sub
Private Sub AfficherFormations()
MaGrid.DataSource = donnees.Tables(0)
End Sub