décembre
2008
Avec le framework dotnet, il n’existe aucun moyen pour générer une enveloppe SOAP générique autre que d’écrire tout le XML à la main. Or nous le savons le XML d’une enveloppe SOAP peut rapidement être complexe et ainsi être sources de nombreuses erreurs.
Cette solution n’est pas pas satisfaisante surtout dans le cas ou de nombreux arguments sont nécessaires, ou lorsque l’on veux faire un appel asynchrone au webservice.
Nous sommes donc devant un dilemme: Soit nous générons l’enveloppe SOAP à la main avec tout les risques que cela comporte, soit nous nous contentons de ne faire appel qu’à des webservices que nous connaissons et dont nous avons généré les classes proxy.
Une partie de la solution tiens dans ce constat : Si nous avions les classes proxy cela serait nickel. Or ces classes proxy sont générés par Visual Studio. Toutefois rien n’empêche d’aller faire un petit tours dans le code généré pour voir comme tout cela est fait.
La première chose qui nous intéresse c’est la classe dont hérite celle qui est généré : SoapHttpClientProtocol
Cette classe est la classe de base pour tout les clients SOAP du framework .NET.
Elle expose des méthodes permettant à ses enfants d’appeler les webservices de manière assez simple.
La méthode la plus intéressante dans notre cas est sans hésitation la méthode Invoke.
Cette méthode fait exactement ce que nous voulons ! En effet elle prend en paramètre le nom de la méthode web à appeler et un tableau d’objet correspondant aux paramètres à passer au webservice.
Toutefois elle est protected. Le premier réflexe est donc de créer une classe dérivé permettant d’accéder à cette méthode. Ne partez pas ! cela ne fonctionne pas.
En effet pour les fonctions de la classe SoapHttpClientProtocol puissent être utilisé il faut que les attributs de description soient positionnés. Or nous ne pouvons pas faire cela au moment du runtime.
Retour donc à la case départ.
Mais il reste que l’idée de pouvoir utiliser du code généré est intéressante. Finalement ce que nous aimerions faire c’est imiter le comportement de VS en générant la classe proxy au webservice à appeler et en l’utilisant par la suite. Cela nous amène donc à nous pencher sur la façon dont VS récupère les informations sur le webservice et comment il génère la classe proxy. Afin de générer la classe proxy, VS utilise un outil fourni avec le framework .NET : disco.exe.
Au vu de la page MSDN c’est lui qui fait tout le travail qui nous intéresse. Mais mieux encore, les classes permettant de récupérer la définition d’un webservice et de générer la classe proxy font partie intégrante du framework.
Dès lors nous pouvons coder notre propre classe de génération de classe proxy d’accès à un webservice.
La première chose à faire est de découvrir la structure du webservice et de ses méthodes.
Cela se fait grâce à la classe DiscoveryClientProtocol
Cette classe expose deux méthodes et une propriété qui vont nous intéresser :
Url : string, DiscoverAny(string url) et ResolveAll();
Ce qui nous donne ça lorsque nous l’utilisons :
{
DiscoveryClientProtocol discoProtocol = new DiscoveryClientProtocol();
discoProtocol.Url = url;
discoProtocol.DiscoverAny(url);
discoProtocol.ResolveAll();
if (discoProtocol.Errors.Count != 0)
{
throw new Exception();
}
return discoProtocol.Documents;
}
A partir de la vous avez en mémoire la définition du service web.
Il ne reste plus qu’à générer le code de la classe proxy pour ce webservice.
Cela ce fait grâce à la définition que nous venons de charger et au CodeDOM.
{
CodeNamespace codeNamespace = new CodeNamespace();
codeNamespace.Name = nameSpace;
CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
codeCompileUnit.Namespaces.Add(codeNamespace);
WebReferenceCollection webReferences = new WebReferenceCollection();
WebReference webReference = new WebReference(documents, codeNamespace);
webReferences.Add(webReference);
BasicProfileViolationCollection violations = new BasicProfileViolationCollection();
foreach (WebReference reference2 in webReferences)
{
WebServicesInteroperability.CheckConformance(WsiProfiles.BasicProfile1_1, reference2, violations);
}
if (violations.Count > 0)
{
throw new Exception();
}
ServiceDescriptionImporter.GenerateWebReferences(webReferences, codeProvider, codeCompileUnit, new WebReferenceOptions() { Verbose = false });
CompilerParameters compilerParameters = new CompilerParameters();
//Génération d'une dll en code optimisé
compilerParameters.GenerateExecutable = false;
compilerParameters.GenerateInMemory = false;
compilerParameters.IncludeDebugInformation = false;
compilerParameters.OutputAssembly = assemblyName;
CompilerResults results = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
if (results.Errors.Count != 0)
{
throw new Exception();
}
return results.CompiledAssembly;
}
La première étape consiste à valider le webservice à partir de la définition que nous avons récupéré plus haut. C’est le rôle de WebServicesInteroperability.CheckConformance
Ensuite nous créons en mémoire la classe proxy grâce à la méthode ServiceDescriptionImporter.GenerateWebReferences. c’est elle qui s’occupe de remplir le graph CodeDom avec les bons membres.
La dernière étape consiste à compiler le graph CodeDOM pour en produire une dll.
Dans cet exemple j’ai choisi de générer une dll plutot que de faire la génération en mémoire. Cela oblige à posséder un dépôt ou mettre les assemblies compilés mais cela permet également de grandement gagner en vitesse d’exécution lors du deuxième appel au même webservice (pour peu que vous ayez adopté un système vous permettant de retrouver l’assembly correspondant à une url ).
Justement puisque nous sommes au chapitre des performances :
Cette méthode peux en aucun cas vous procurer la même rapidité d’exécution que la version compilé et incluse dans votre projet. Rien que l’utilisation de la réflexion pour accéder aux classes et méthodes rend cela impossible.
De plus sachez que la récupération de la définition du webservice et une opération lente. La génération du code et de l’assembly n’ont pas un impact énorme mais c’est toujours du temps supplémentaire.
Par contre il existe quelque « astuces » permettant de minimiser ce problème de performance :
– Mise en cache des assemblies compilé. Il serait dommage de régénérer à chaque appel la dll contenant les méthodes d’accès au webservice.
– Mise en cache des informations de réflexion: Si la méthode du webservice doit être appelée plusieurs fois pourquoi être obligé de re-parcourir la totalité des méthodes pour atteindre la bonne ?
Archives
- juillet 2012
- mars 2012
- février 2012
- novembre 2011
- octobre 2011
- mars 2011
- novembre 2010
- octobre 2010
- septembre 2010
- août 2010
- avril 2010
- février 2010
- janvier 2010
- novembre 2009
- octobre 2009
- septembre 2009
- juin 2009
- mai 2009
- avril 2009
- mars 2009
- février 2009
- janvier 2009
- décembre 2008
- novembre 2008
- octobre 2008
- septembre 2008
- août 2008
- juin 2008
- mai 2008
- avril 2008
- février 2008
- mai 2007
- avril 2007
- mars 2007
- février 2007
- janvier 2007