Introduction à Windows Communication Foundation – Partie 4
Voici le 4ème billet de ma série d'articles consacrés à Windows Communication Foundation. Pour rappels, le premier article de cette série traitait d'une introduction générale à WCF ainsi qu'aux grands principes de la SOA (Service Oriented Architecture). Le second traitait de la mise en place des entités fondamentales d'un service WCF, à savoir la définition du contrat de service, le contrat de données et l'implémentation du service. Enfin un troisième billet était consacré aux différents modes d'hébergement de services WCF : Self-Hosting, IIS, WAS. Il traitait également de l'exposition des métadonnées des services WCF.
Dans ce post, et comme je l'avais prévu au niveau de mon plan d'articles, nous allons nous intéresser à la consommation de notre service Windows Communication Foundation. Nous allons voir dans un premier temps comment écrire nous même un client WCF à l'aide des ChannelFactory. Une deuxième partie sera consacrée à l'outil svcutil.exe permettant la génération du proxy WCF.
Ecrire son propre client WCF
L'écriture de son propre client Windows Communication en .NET n'est pas une opération très compliquée. Elle repose principalement sur l'utilisation d'une classe – ChannelFactory<T> - et de la bonne configuration d'endpoint WCF. En effet, nous avions vu dans le tout premier article de cette série, que tout comme le service WCF qui pouvait définir lui plusieurs endpoints, un client WCF allait lui aussi avoir besoin de ces informations – au moins un endpoint – afin de pouvoir contacter le service.
Nous verrons dans quelques lignes que le fichier de configuration d'un client Windows Communication Foundation s'écrit aussi facilement que celui d'un service. Avant cela, intéressons nous plus en détail à la dite classe ChannelFactory<T>.
Comme sa signature l'indique, celle-ci est une classe générique, où le type T ne représente rien d'autre que le type du contrat de service mis en jeu – dans notre cas INorthwindService. Définie dans l'espace de nom System.ServiceModel, ChannelFactory<T> dérive de la classe ChannelFactory elle-même dérivant de la classe System.ServiceModel.Channels.CommunicationObject. C'est de cette dernière classe de base que la classe ChannelFactory<T> que nous allons utiliser va hériter la plupart de ces méthodes et propriétés liées à la communication vers un point de terminaison de service.
On retrouve notamment les 3 méthodes que nous manipulerons le plus :
- Abort : cette méthode entraîne un changement d'état immédiat de l'objet de communication, vers un état « Closed ».
- Close : cette méthode entraîne une fermeture du canal de communication et donc une transition en douceur de l'objet de communication vers un état « Closed ».
- Open : cette méthode entraîne l'ouverture du canal de communication de l'objet communiquant en faisant passer ce dernier d'un état « Created » à « Opened ».
Note : des méthodes asynchrones (Begin… et End…) sont également disponibles pour Open et Close.
Vous pouvez remarquer que nous parlons d'états de l'objet communiquant dans les lignes ci-dessous. En effet, à n'importe quel moment une instance de CommunicationObject possède un état clairement défini. Cet état se matérialise par la propriété « State » prenant une valeur de l'énumération System.ServiceModel.CommunicationState :
- Created
- Opening
- Opened
- Closing
- Closed
- Faulted
Il est également important de souligner que la classe CommunicationObject définie des évènements se produisant à chaque fois que la transition d'un état vers un autre se produit (mise à part à la création lorsque l'objet de communication prend l'état Created). Ces évènements se nomment comme les valeurs de l'énumération ci-dessous et permette d'effectuer certaines actions à l'ouverture, à la fermeture du canal ou encore en cas d'erreur sur l'objet de communication.
Comme pour la classe ServiceHost vue dans l'article dédié à l'hébergement de services WCF, il est possible de configurer le point de terminaison sur lequel le service sera contacté par le client de plusieurs manières, par le code directement où via le fichier de configuration afin de conserver une certaine maintenabilité de l'application. Tout comme le service, la configuration va se faire dans la section « system.serviceModel » mais contrairement à ce que nous avions vu, nous allons cette fois-ci définir notre (nos) endpoint (s) dans une balise <client></client> (plutôt logique J) :
<system.serviceModel>
<client>
<endpoint
address="net.tcp://localhost:1664/NorthwindService"
binding="netTcpBinding"
contract="Northwind.ServiceContract.INorthwindService"
name="NorthwindServiceEndpoint" />
</client>
</system.serviceModel>
Note : n'oublier pas de nommer l'endpoint que vous souhaiter utiliser pour pouvoir le référencer depuis le code, dans le constructeur de la classe ChannelFactory <T>.
Vous pourrez remarquer que la définition de l'endpoint ci-dessous ne change en rien de celle vue pour l'hébergement du service !
Nous pouvons à présent instancier notre ChannelFactory<T>, où le type T est, je vous le rappel, le type du contrat de service WCF.
J'ai choisi de réaliser mon client en WPF (Windows Presentation Foundation) je ne détaillerai pas tout le code de ce dernier dans un souci de lisibilité. Pour effectuer un binding propre sur l'interface Xaml, je crée une classe intermédiaire – NorthwindServiceClient – que je passerai en ressource de mon interface et qui sera capable de communiquer avec le service.
Voici un premier exemple de code de cette classe :
public
class
NorthwindServiceClient
{
private
ChannelFactory<INorthwindService> channelFactory = null;
private
INorthwindService northwindService = null;
public NorthwindServiceClient()
{
this.channelFactory = new
ChannelFactory<INorthwindService>("NorthwindServiceEndpoint");
}
}
Vous pouvez tout d'abord remarquer la déclaration d'un ChannelFactory<INorthwindService> ainsi que d'un objet de type INorthwindService, via lequel nous ferons nos appels client. Dans le constructeur par défaut (pour les besoins du binding) de la classe NorthwindServiceClient nous instancions le ChannelFactory<INorthwindService> en passant au constructeur le nom du point de terminaison présent dans le fichier de configuration du client.
Nous devons à présent récupérer une référence vers notre service et ce par l'appel de la méthode CreateChannel sur notre instance de ChannelFactory :
public NorthwindServiceClient()
{
this.channelFactory = new
ChannelFactory<INorthwindService>("NorthwindServiceEndpoint");
this.northwindService = this.channelFactory.CreateChannel();
}
Au final, si vous mettez un point d'arrêt sur la dernière ligne ajoutée ici, vous verrez qu'à l'exécution l'objet « northwindService » n'est rien d'autre qu'un « Proxy Transparent » vers le service. Ce terme de « Proxy Transparent » - bien connu des développeurs .NET Remoting – n'est autre qu'un objet vous permettant de manipuler un objet serveur de manière totalement transparente :
A présent, nous allons exposer une propriété de type List<Customer> sur notre classe NorthwindServiceClient. Celle-ci sera chargée dans le constructeur par défaut après l'appel à CreateChannel et utilisée en ItemsSource de la grille de l'interface WPF :
public
List<Customer> Customers
{
get;
set;
}
public NorthwindServiceClient()
{
this.channelFactory = new
ChannelFactory<INorthwindService>("NorthwindServiceEndpoint");
this.northwindService = this.channelFactory.CreateChannel();
this.Customers = this.northwindService.GetCustomers();
}
Il ne reste plus qu'à déclarer notre ressource au niveau de la fenêtre Xaml :
<Window.Resources>
<local:NorthwindServiceClient x:Key="NorthwindService" />
</Window.Resources>
Et à lier celle-ci à notre grille :
<toolkit:DataGrid x:Name="dgCustomers"
DataContext="{StaticResource NorthwindService}"
ItemsSource="{Binding Path=Customers}">
</toolkit:DataGrid>
Ainsi en exécutant l'application WPF nous récupérons bien la liste des clients de Northwind, via le service NorthwindService :
Dans un souci de propreté et de sécurité, il nous reste à entourer le code du constructeur de NorthwindServiceClient d'un bloc try/catch afin d'attraper d'éventuelles CommunicationException :
try
{
this.channelFactory = new
ChannelFactory<INorthwindService>("NorthwindServiceEndpoint");
this.northwindService = this.channelFactory.CreateChannel();
this.Customers = this.northwindService.GetCustomers();
}
catch (CommunicationException cXcp)
{
//traitement ici
}
Rappels : la classe CommunicationException est la classe représentant une exception lors d'une communication réseau, côté service comme client.
Nous venons de voir une première partie nous permettant d'écrire nous même le client Windows Communication Foundation. Dans la partie suivante, nous allons voir – comme à la manière des services Web – comment générer ce proxy client à partir des métadonnées exposées par le service.
Génération d'un client WCF
La génération d'un client Windows Communication Foundation passe par l'utilisation d'un outil en ligne de commande – svcutil.exe – fournit avec le .NET Framework. Cet outil peut être utilisé en ligne de commande de la manière suivante :
C:\> svcutil.exe adresse_métadonnées_service
Ainsi, si votre service expose ces métadonnées en WSDL (ou en MEX) à l'adresse http://localhost:1665/NorthwindService?wsdl (http://localhost:1665/NorthwindService/mex, respectivement) il suffira de taper la commande suivante dans l'invite de commande Visual Studio 2008 :
C:\> svcutil.exe http://localhost:1665/NorthwindService?wsdl
Le résultat de cette commande sera alors la génération de deux fichiers : NorthwindService.cs et output.config. Ces deux fichiers contiennent le code et la configuration, respectivement, nécessaire pour appeler le service NorthwindService. Nous les détaillerons juste après.
Par souci de commodité, Visual Studio intègre un outil encapsulant l'outil svcutil.exe permettant de générer le code nécessaire pour contacter le service et ajoutant directement les directives au fichier de configuration de l'application. Ce qui évite d'avoir à intégrer les fichiers générés ci-dessus au projet courant (oui, le développeur est feignantJ).
Pour ce faire, faites un clic droit sur le projet dans lequel vous souhaiter générer le code (ici, la bibliothèque de classe dans laquelle NorthwindServiceClient est définie) et choisissez d'ajouter une référence vers un service :
Dans la fenêtre qui s'ouvre, entrez alors l'adresse auxquelles sont exposées les métadonnées du service, cliquez sur « Go » et choisissez le nom de l'espace de nom dans lequel Visual Studio doit générer le code :
Le bouton « Advanced… » va vous permettre de définir des paramètres avancés quant à la génération du contrat de service, du contrat de données et du proxy WCF, notamment le modificateur de portée de ces classes ou encore le type à utiliser pour les collections (par défaut System.Array, modifié ici pour utiliser les listes génériques) :
Une fois vos paramètres définis, cliquez sur le bouton « Ok » pour lancer la génération du code. Après cette dernière une nouvelle référence vers un service est ajoutée à votre projet. Vous pouvez visionner le code généré via l'explorateur d'objets de Visual Studio, en faisant un clic droit sur la référence puis « Voir dans l'explorateur d'objets » :
Vous pouvez alors constater que le contrat de service a été généré, ainsi qu'une classe « NorthwindServiceClient » imbriquant directement les fonctionnalités réunies par les ChannelFactory<INorthwindService> et INorthwindService dans la création manuelle du client WCF, c'est-à-dire l'établissement de la connexion avec le service et la manipulation de ce dernier via un proxy transparent :
Vous constatez également que les classes faisant partie du contrat de données (Category, Customer…) ont été générées.
Si vous regardez le fichier de configuration, vous verrez que ce dernier fait mention des endpoints auxquels le service est accessible. Ces endpoints sont tous nommés et nous les appellerons via le constructeur de la classe NorthwindServiceClient, comme nous le faisions pour la classe ChannelFactory.
Nous pouvons alors écrire une classe – NorthwindServiceClientGenerate – facilement liable à une interface WCF et utilisant le proxy généré pour récupérer la liste des produits :
public
class
NorthwindServiceClientGenerate
{
private NorthwindServiceReference.NorthwindServiceClient client;
public NorthwindServiceClientGenerate()
{
try
{
client = new
NorthwindServiceClient("BasicHttpBinding_INorthwindService");
this.Products = client.GetProducts();
}
catch(CommunicationException cXcp)
{
//traitement
}
}
public
List<Product> Products
{
get; set;
}
}
On obtient bien alors la liste des produits de la base Northwind, via le service WCF et surtout le client WCF généré par Visual Studio :
Conclusion
Nous avons vu ici qu'il existait deux moyens de consommer un service WCF depuis une application .NET. Le premier consiste à écrire son client Windows Communication Foundation soi-même à l'aide des ChannelFactory. Le second consiste à laisser Visual Studio travailler pour nous, via l'outil svcutil.exe, afin de générer un proxy WCF nous permettant de joindre le service.
J'espère que ce billet vous sera utile. Le prochain traitera de la gestion des exceptions et des erreurs lors de communication entre client et service Windows Communication Foundation.
A bientôt
Sources du projet pour la partie 4 : NorthwindWCF-Part4.zip (488.31 kb)