Publication des meta-data des services WCF


Auteur : Pascal Belaud, Relation technique Développeurs

Téléchargement du code source associé : Publication_des_meta-data_des_services_WCF

 

Lors du développement de services WCF (Windows Communication Services), nous sommes généralement amenés à produire deux types choses :

  1. Des classes susceptibles d’être transmises de part et d’autre, ce sont les DataContracts
  2. Les services proprement dits, ce sont les ServiceContract

 

Ainsi, une architecture courante WCF se compose de 5 composants :

  1. Une assembly contenant la définition des DataContracts
  2. Une assembly contenant la définition des interfaces décrivant nos services
  3. Une assembly contenant l’implémentation proprement dite de nos services
  4. Un hôte (Host) pour nos services pour l’aspect serveur
  5. Une application cliente venant consommer ses services

 

Pour permettre au développeur d’une application cliente de pouvoir appeler nos services, une possibilité est de lui fournir l’assembly contenant les DataContracts (1) et l’assembly contenant les interfaces (2). Dans ce cas, le développeur n’a qu’à référencer ces assemblies depuis Visual Studio 2005 puis utiliser la classe ChannelFactory (se trouvant dans le namespace System.ServiceModel) pour créer les canaux de communication (Channels) adéquats afin d’appeler les services.

Maintenant imaginons que le développeur de l’application cliente ne dispose pas de ces assemblies ou encore que le développeur en question n’utilise pas du tout ni Visual Studio 2005, ni le framework .NET 3.0. A-t-il dans ce cas une solution pour pouvoir appeler nos services WCF ?

La réponse est oui car WCF repose de manière native sur le protocole standard (soumis au W3C) appelé SOAP. Tous les messages échangés entre les deux bords d’une application distribué WCF sont encodés en SOAP. Ensuite, en fonction des bindings de communication spécifiques qui vont être utilisés, une application cliente pourra avoir accès à la version en clair et en XML de ces messages. Ainsi, tout client SOAP sera donc capable d’appeler nos services. Encore faut-il qu’il soit au courant de la liste des services disponibles. Ceci va être réalisé grâce à la publication des meta-datas de nos services WCF sous forme de WSDL SOAP. L’objectif de cet article est de vous montrer comment exposer la définition de vos services sous forme WSDL.

 

Mise en place de l’exemple

Avant cela, nous allons mettre en place l’exemple avec lequel nous allons travailler. Voici le contract de services que nous souhaitons exposer :

Iservices

Notez que la méthode BestSeller() renvoie une classe de type Employee, définie comme étant un DataContract :

Iservices

Pour exposer les services, nous utilisons un Host qui charge la configuration qu’il doit utiliser depuis le fichier de configuration XML :

<?xml version="1.0"encoding="utf-8" ?>

<configuration>

                <system.serviceModel>

                                <services>

                                                <service name="LibrairieMetier.Services">

                                                                <endpoint contract="ILibrairieMetier.IServices"

                                                                                                  binding="netTcpBinding"

                                                                                                  address="net.tcp://localhost:2605/Services"/>

                                                              <endpoint contract="ILibrairieMetier.IServices"

                                                                                                  binding="basicHttpBinding"

                                                                                                  address="https://localhost:1993/Services"/>

                                                </service>

                                </services>

                </system.serviceModel>

</configuration>

Ici, nous exposons la classe métier LibrairieMetier.Services qui a implémenté le contrat ILibrairieMetier.IServices

Cette classe est exposée avec deux bindings différents basés sur TCP/IP et http

 

Exposition des meta-data en SOAP

Pour exposer les meta-datas liées à ces services sous forme SOAP, il suffit d’ajouter un nouveau ServiceBehavior et de spécifier le service Meta-Data doit être activé :

 

<?xml version="1.0"encoding="utf-8" ?>

<configuration>

                <system.serviceModel>

                                   ...

                                <behaviors>

                                                <serviceBehaviors>

                                                                <behavior name="maConfiguration">

                                                                                <serviceMetadata httpGetEnabled="true"

                                                                                                                httpGetUrl="https://localhost/Services"/>

                                                                </behavior>

                                                </serviceBehaviors>

                                </behaviors>

                </system.serviceModel>

</configuration>

Parmi lesparamètres que l’on peut affecter sur le ServiceBehavior, nous allons indiquerque nous souhaitons exposer nos meta-datas via HTTP/GET grâce à l’attribut httpGetEnabled="true". Ensuite, nous allons indiquer viaquelle URL, le WSDL final sera disponible en utilisant l’attribut httpGetUrl="https://localhost/Services".Il ne reste plus qu’à indiquer que notre service doit utiliser ce ServiceBehavior:

<?xmlversion="1.0"encoding="utf-8" ?>

<configuration>

                <system.serviceModel>

                                <services>

                                                <service behaviorConfiguration="maConfiguration"

                                                                                 name="LibrairieMetier.Services">

Dès lors, en lançant le Host, nous pouvons maintenant requêter l’URL suivante : https://localhost/Services

Services 

 

Appel des services en tant que Services Web XML SOAP

Maintenant que ce contrat WSDL est disponible, il est possible pour toute plateforme, compatible SOAP, de consommer les services exposés. WCF s’appuyant nativement sur SOAP, il est donc possible de les appeler dans des Web Services XML pour peu que le binding basicHttpBinding soit utilisé côté serveur au minimum.

Dès lors, nous pouvons ajouter une référence web depuis notre projet client sous Visual Studio 2005 :

Web XML SOAP

Nous pouvons désormais consommer nos services WCF de la manière suivante :

 

ServicesSoap.Services mesServices = new ServicesSoap.Services();

 

using (mesServices)

{

    try

    {

        // Appel duServiceContract

        string majuscule = mesServices.Majuscule(textBoxChaîne.Text);

 

        //Récupération du DataContract

        ServicesSoap.Employé jeanPierre = mesServices.MeilleurVendeur();

    }

    catch

    {

        mesServices.Abort();

        throw;

    }

}

Appel des services en tant que Services WCF

Nous pouvons également consommer l’ensemble de ces services WCF nativement en WCF. Pour cela, maintenant que le contrat WSDL est disponible, nous pouvons demander à Visual Studio 2005 de générer pour nous le code client nécessaire :

Services

L’intégration de cette fonctionnalité dans ce menu a été rendue possible grâce à l’installation des extensions WCF pour Visual Studio 2005 disponible à l’adresse suivante :

 

Visual Studio 2005 extensions for .NET Framework 3.0 (WCF & WPF), November 2006 CTP

https://www.microsoft.com/downloads/details.aspx?familyid=F54F5537-CC86-4BF5-AE44-F5A1E805680D\&displaylang=en

Services WCF

Dès lors, nous pouvons appeler nos services comme suit :

ServicesWCF.ServicesClient mesServices = new

ServicesWCF.ServicesClient("NetTcpBinding_IServices");

 

using (mesServices)

{

    try

    {

        // Appel du ServiceContract

        string majuscule = mesServices.Majuscule(textBoxChaîne.Text);

 

        //Récupération du DataContract

        ServicesWCF.Employé jeanPierre = mesServices.MeilleurVendeur();

    }

    catch

    {

        mesServices.Abort();

        throw;

    }

}

Pour référence, voici le code WCF qui a été généré par Visual Studio :

//------------------------------------------------------------------------------

// <auto-generated>

//     This code was generated by a tool.

//     RuntimeVersion:2.0.50727.312

//

//     Changes to thisfile may cause incorrect behavior and will be lost if

//     the code is regenerated.

// </auto-generated>

//------------------------------------------------------------------------------

 

namespace Client.ServicesWCF

{

    using System.Runtime.Serialization;

    using System;

   

   

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization","3.0.0.0")]

    [System.Runtime.Serialization.DataContractAttribute(Namespace="http://schemas.datacontract.org/2004/07/LibrairieData")]

    [System.SerializableAttribute()]

    public partial class Employé : object,System.Runtime.Serialization.IExtensibleDataObject

    {

       

        [System.NonSerializedAttribute()]

        private System.Runtime.Serialization.ExtensionDataObjectextensionDataField;

       

        [System.Runtime.Serialization.OptionalFieldAttribute()]

        private string NomField;

       

        [System.Runtime.Serialization.OptionalFieldAttribute()]

        private string PrénomField;

       

         public System.Runtime.Serialization.ExtensionDataObject ExtensionData

        {

            get

            {

                returnthis.extensionDataField;

            }

            set

            {

                this.extensionDataField= value;

            }

        }

       

        [System.Runtime.Serialization.DataMemberAttribute()]

        public string Nom

        {

            get

            {

                return this.NomField;

            }

            set

            {

                this.NomField= value;

            }

        }

       

        [System.Runtime.Serialization.DataMemberAttribute()]

        public string Prénom

        {

            get

            {

                return this.PrénomField;

            }

            set

            {

                this.PrénomField= value;

            }

        }

    }

   

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0")]

    [System.ServiceModel.ServiceContractAttribute(ConfigurationName="Client.Services.IServices")]

    public interface IServices

    {

       

        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IServices/Majuscule",ReplyAction="http://tempuri.org/IServices/MajusculeResponse")]

        string Majuscule(string valeur);

       

        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IServices/Minuscule",ReplyAction="http://tempuri.org/IServices/MinusculeResponse")]

        string Minuscule(string valeur);

       

        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IServices/MeilleurVendeur",ReplyAction="http://tempuri.org/IServices/MeilleurVendeurResponse")]

        Client.ServicesWCF.Employé MeilleurVendeur();

    }

   

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0")]

    public interface IServicesChannel: Client.ServicesWCF.IServices,System.ServiceModel.IClientChannel

    {

    }

   

    [System.Diagnostics.DebuggerStepThroughAttribute()]

    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0")]

    public partial class ServicesClient : System.ServiceModel.ClientBase<Client.ServicesWCF.IServices>, Client.ServicesWCF.IServices

    {

       

        public ServicesClient()

        {

        }

       

        public ServicesClient(string endpointConfigurationName) :

                base(endpointConfigurationName)

        {

        }

       

        public ServicesClient(string endpointConfigurationName, string remoteAddress):

                base(endpointConfigurationName,remoteAddress)

        {

        }

       

        public ServicesClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :

                base(endpointConfigurationName,remoteAddress)

        {

        }

       

        public ServicesClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :

                base(binding,remoteAddress)

        {

        }

       

        public string Majuscule(string valeur)

        {

            returnbase.Channel.Majuscule(valeur);

        }

       

        public string Minuscule(string valeur)

        {

            return base.Channel.Minuscule(valeur);

        }

       

         public Client.ServicesWCF.Employé MeilleurVendeur()

        {

            return base.Channel.MeilleurVendeur();

        }

    }

}

 

<?xml version="1.0"encoding="utf-8" ?>

<configuration>

                <configSections>

                                <sectionGroup name="applicationSettings"

                                                                                  type="System.Configuration.ApplicationSettingsGroup,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >

                                                <section name="Client.Properties.Settings"

                                                                                 type="System.Configuration.ClientSettingsSection,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

                                                                                 requirePermission="false" />

                                </sectionGroup>

                </configSections>

                <system.serviceModel>

                                <bindings>

                                                <basicHttpBinding>

                                                                <binding name="BasicHttpBinding_IServices"

                                                                                                 closeTimeout="00:01:00"

                                                                                                 openTimeout="00:01:00"

                                                                                                 receiveTimeout="00:10:00"

                                                                                                 sendTimeout="00:01:00"

                                                                                                 allowCookies="false"

                                                                                                 bypassProxyOnLocal="false"

                                                                                                 hostNameComparisonMode="StrongWildcard"

                                                                                                 maxBufferSize="65536"

                                                                                                 maxBufferPoolSize="524288"

                                                                                                 maxReceivedMessageSize="65536"

                                                                                                 messageEncoding="Text"

                                                                                                 textEncoding="utf-8"

                                                                                                 transferMode="Buffered"

                                                                                                 useDefaultWebProxy="true">

                                                                                <readerQuotas maxDepth="32"

                                                                                                                                  maxStringContentLength="8192"

                                                                                                                                  maxArrayLength="16384"

                                                                                                                                  maxBytesPerRead="4096"

                                                                                                                                  maxNameTableCharCount="16384" />

                                                                                <security mode="None">

                                                                                                <transport clientCredentialType="None"

                                                                                                                                   proxyCredentialType="None"

                                                                                                                                   realm="" />

                                                                                                <message clientCredentialType="UserName"

                                                                                                                                 algorithmSuite="Default" />

                                                                                </security>

                                                                </binding>

                                                </basicHttpBinding>

                                                <netTcpBinding>

                                                                <binding name="NetTcpBinding_IServices"

                                                                                                 closeTimeout="00:01:00"

                                                                                                 openTimeout="00:01:00"

                                                                                                 receiveTimeout="00:10:00"

                                                                                                 sendTimeout="00:01:00"

                                                                                                 transactionFlow="false"

                                                                                                 transferMode="Buffered"

                                                                                                 transactionProtocol="OleTransactions"

                                                                                                 hostNameComparisonMode="StrongWildcard"

                                                                                                 listenBacklog="10"

                                                                                                 maxBufferPoolSize="524288"

                                                                                                 maxBufferSize="65536"

                                                                                                 maxConnections="10"

                                                                                                 maxReceivedMessageSize="65536">

                                                                                <readerQuotas maxDepth="32"

                                                                                                                                  maxStringContentLength="8192"

                                                                                                                                  maxArrayLength="16384"

                                                                                                                                  maxBytesPerRead="4096"

                                                                                                                                  maxNameTableCharCount="16384" />

                                                                                <reliableSession ordered="true"

                                                                                                                                                 inactivityTimeout="00:10:00"

                                                                                                                                                 enabled="false" />

                                                                                <security mode="Transport">

                                                                                                <transport clientCredentialType="Windows"

                                                                                                                                   protectionLevel="EncryptAndSign" />

                                                                                                <message clientCredentialType="Windows" />

                                                                                </security>

                                                                </binding>

                                                </netTcpBinding>

                                </bindings>

                                <client>

                                                <endpoint address="net.tcp://localhost:2605/Services"

                                                                                  binding="netTcpBinding"

                                                                                  bindingConfiguration="NetTcpBinding_IServices"

                                                                                  contract="Client.Services.IServices"

                                                                                  name="NetTcpBinding_IServices">

                                                </endpoint>

                                                <endpoint address="https://localhost:1993/Services"

                                                                                  binding="basicHttpBinding"

                                                                                  bindingConfiguration="BasicHttpBinding_IServices"

                                                                                  contract="Client.Services.IServices"

                                                                                  name="BasicHttpBinding_IServices" />

                                </client>

                </system.serviceModel>

                <applicationSettings>

                                <Client.Properties.Settings>

                                                <setting name="Client_ServicesSoap_Services"

                                                                                 serializeAs="String">

                                                                <value>net.tcp://localhost:2605/Services</value>

                                                </setting>

                                </Client.Properties.Settings>

                </applicationSettings>

</configuration>

 

Utilisation de SVCUTIL pour générer du code WCF

La génération de code précédente a été faite en appelant en ligne de commande l’utilitaire svcutil, situé dans le répertoire du SDK : C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\SvcUtil.exe

Voici un exemple d’utilisation de cet utilitaire en ligne de commande :

"C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\SvcUtil.exe" https://localhost/Services /out:CodeClient.cs /config:app.config /language:C#

code WCF

Conclusion

 

On a vu dans cet article technique comment on pouvait exposer les meta-data relatives à nos services WCF sous forme de contrats WSDL, l’intérêt étant que du coup, tout développeur, sachant avec sa plateforme de développement appeler des services SOAP, sera capable de découvrir ceux-ci très facilement.