Introduction à Windows Communication Foundation

Cet article vous présente le principe de la communication entre applications et en particulier Windows Communication Foundation pour l'implémentation.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Pourquoi faire des applications qui communiquent ?

C'est la base de tout : pourquoi faire une application qui permet à une autre de communiquer avec elle ?
Assurément toutes les applications n'ont pas besoin de communiquer avec les autres. Alors, quand mettre en œuvre une communication entre applications ?

  • Si vous avez par exemple une application qui doit accéder à des ressources spécifiques à un poste.
  • À des fins de centralisation de données.
  • À des fins de répartition des charges sur plusieurs postes.
  • Afin de ne pas réécrire un traitement qui existe dans une autre application.
  • Pour pouvoir contrôler une application.

Ce ne sont que des exemples, mais ils résument bien les différents cas qui sont le plus souvent rencontrés

II. Windows Communication Foundation ? C'est quoi ?

Windows Communication Foundation (WCF) est la nouvelle couche de communication du framework 3.0. Cette couche a été créée afin d'unifier les différents modèles d'écritures d'applications "communicantes".

Image non disponible
Unification des modèles de développement de communications

WCF fait partie du framework 3.0 qui est livré d'office avec Vista et est disponible sous Windows XP et 2003 serveur.
Pour pouvoir utiliser WCF dans vos applications, vous devez ajouter la référence à System.ServiceModel à vos projets. Ensuite vous devez faire un using System.ServiceModel;

III. Comment fonctionne la communication entre applications ?

Afin que les applications puissent communiquer, il faut qu'un certain nombre de contraintes soient respectées :

  • définir les méthodes exposées par le serveur ;
  • définir les types de données transmissibles entre applications ;
  • définir l'ABC de la communication.

III-A. Définir les méthodes exposées par le serveur

Pour que le client puisse appeler des méthodes sur le serveur encore faut-il que ce premier connaisse les méthodes disponibles sur le serveur. WCF est très strict sur la façon d'implémenter la communication entre applications. En effet les méthodes exposées par WCF doivent toutes être définies dans une interface "décorée" par l'attribut ServiceContract. De plus les fonctions que l'on veut exposer par WCF doivent être "décorées" par l'attribut OperationContract.

 
Sélectionnez
using System.ServiceModel;

[ServiceContract]
public interface IMonPremierServiceWCF
{
    [OperationContract]
    void EcrireUnePhrase(string phrase);
}

Cette interface définit donc une méthode qu'un client peut appeler à distance. Au passage vous remarquerez que nous passons un paramètre sans aucun problème. Cette interface définit donc le contrat de notre service. En effet les types primaires tels que string, int, float, etc. peuvent être utilisés sans aucun problème ni modification. Mais comment permettre à des types personnalisés d'être transmis par WCF d'un serveur à un client ?

III-B. Définir les types de données transmissibles entre applications

Afin de marquer une classe comme transmissible par WCF il suffit d'utiliser l'attribut [DataContract]. Cet attribut est défini dans l'assembly System.Runtime.Serialization qu'il faut donc ajouter à son projet. Ce choix d'implémentation est très intéressant, car il permet de ne pas bousculer toute la modélisation d'un projet en obligeant l'héritage par une classe comme MarshalByRefObject en Remoting. Cela vous permet donc de migrer facilement et avec peu de modifications un projet qui n'a pas été initialement prévu pour fonctionner en WCF.
Une fois que votre classe est "décorée" avec l'attribut [DataContract] il faut ensuite identifier les méthodes que nous allons exposer aux futurs clients de notre application.

 
Sélectionnez
[DataContract]
public class MyDatas
{
    private string data1
    
    [DataMember]
    public string Data1
    {
        get
        {
            return this.data1;
        }
        
        set
        {
            this.data1 = value;
        }    
    }
}

III-C. Définir l'abc de la communication

Qu'est-ce que l'abc de la communication ? C'est sous cet acronyme que Microsoft définit les trois étapes essentielles du déploiement d'un service WCF.

  • A pour Address : définit l'adresse du serveur qui expose le service.
  • B pour Binding : définit la façon dont le service sera exposé.
  • C pour Contract : définit le contrat que le service remplit.

La grande force de WCF est sans aucun doute la façon dont il a été pensé. En effet tout ce qui touche au déploiement et à la consommation d'un service WCF est défini dans le fichier de configuration de l'application. Cela permet de s'affranchir entièrement des contraintes de déploiement au moment du développement. En unifiant ainsi les différentes façons de programmer une application distribuée, WCF permet une souplesse et une facilité d'utilisation extrêmement agréable.

III-C-1. Définir l'adresse du service

Afin de pouvoir exposer un service, vous devez définir l'adresse à partir de laquelle il sera accessible. Pour cela il faut définir les sections correspondantes dans le fichier de configuration de l'application. Ces sections doivent toutes se trouver dans <system.serviceModel> de votre fichier de configuration.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="DvpDemoWCF.IMonPremierServiceWCF">
                <endpoint address="net.tcp://localhost:5000/DvpDemoWCF" binding="netTcpBinding" contract="DvpDemoWCF.IMonPremierServiceWCF"/>
            </service>
        </services>
    </system.serviceModel>
</configuration>

l'attribut address vous permet de définir un point de terminaison c'est-à-dire une URI où le service sera accessible. De plus cela vous permet de configurer la totalité de l'abc de la communication.
Ici l'adresse expose le service sur le poste local sur le port 5000 et à l'adresse DvpDemoWCF.

III-C-2. Définir le binding du service

Le binding est la façon dont le service sera exposé. C'est-à-dire qu'il vous permet de définir le protocole utilisé pour transporter vos objets sur le réseau ou en local. Chaque binding a ses avantages et ses inconvénients, mais au niveau du développeur ce choix ne doit pas être bloquant. En effet lors de votre développement vous ne savez pas toujours dans quel environnement votre application va être déployée. L'administrateur réseau ne veut peut-être pas ouvrir un port sur un serveur pour des raisons de sécurité, ou alors il voudrait pouvoir définir quel port. De même il préfère peut-être, vu la topologie de son réseau, que les communications se fassent en tcp afin de réduire la charge réseau. Toutes ces questions ne sont donc pas à la charge du développeur, mais bien de l'administrateur réseau qui va s'occuper de l'installation de votre application. C'est là que l'on comprend une autre des choses merveilleuses de WCF. En .NET Remoting ce choix était fait par l'équipe de développement et l'administrateur réseau n'avait aucun moyen d'influer dessus. WCF de par l'uniformisation des techniques d'utilisation des protocoles permet de rendre ce choix à la personne concernée : l'administrateur réseau. Par défaut le framework vous permet d'utiliser quatre méthodes d'exposition de service :

BindingProtocolEncodageDescription
basicHttpBindingHTTPXML 1.0Interopérable
wsHttpBindingHTTPXML 1.0Sécurité,
wsDualHttpBindingHTTPXML 1.0Sécurité, communication double sens
netTcpBindingTCPBinaireSécurité, communication double sens
netNamedPipeBindingtubes nommésBinaireLocal seulement, communication double sens
netMsmqBindingMessage windowsBinaireWindows seulement

Le binding est donc défini par l'attribut "binding" de notre fichier de configuration.

III-C-3. Définir le contract du service

Le "contract" (contrat en français) est tout simplement la liste des méthodes appelables par les clients. Rappelez-vous, nous avons défini une interface que nous avons décorée avec les attributs [ServiceContract] et [OperationContract]. C'est cette interface que nous allons définir en tant que contrat pour notre client.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="DvpDemoWCF.IMonPremierServiceWCF">
                <endpoint address="net.tcp://localhost:5000/DvpDemoWCF" binding="netTcpBinding" contract="DvpDemoWCF.IMonPremierServiceWCF"/>
            </service>
        </services>
    </system.serviceModel>
</configuration>

À noter qu'un contrat est obligatoirement une interface. Vous ne pouvez pas définir une classe en tant que contrat, car le framework vous impose une séparation forte entre ce que les clients peuvent voir et ce que le serveur implémente. Ceci est une bonne chose qui vous permettra à la longue de ne pas vous mélanger entre des classes qui sont appelables par le client, des classes qui ne le sont pas et des interfaces de définition de services.

III-D. Conclusion

À travers cette présentation générale de WCF, vous avez pu constater que le modèle de programmation est bien plus simple que celui de .NET Remoting.
Vous avez également découvert en quoi WCF peut vous être utile dans vos développements personnels.
Nous allons maintenant étudier une démonstration en profondeur afin d'appliquer les fondamentaux que nous venons de voir.

IV. Étude de cas : Service de déploiement à distance

Voyons les spécifications de notre service d'installation de logiciel à distance.
Ce logiciel doit tourner en tant que service Windows, il doit être transparent pour l'utilisateur. Il doit pouvoir interagir avec l'utilisateur en cas de besoin. Il doit envoyer le rapport de l'installation au serveur afin de maintenir la liste à jour.
Cependant il ne s'agit ici que d'un cas d'étude, nous n'allons pas mettre en œuvre le service Windows, mais écrire des applications consoles.

IV-A. Structure du projet

Comme nous l'avons vu, WCF nous impose une certaine structure afin de fonctionner.
Notre projet se divise donc en trois projets au sens Visual Studio :

  • SoftwareInstallerServerService : programme console destiné à être installé sur un serveur afin de fournir aux clients les informations nécessaires à l'installation des logiciels ;
  • SoftwareInstallerClientService : programme winform destiné aux clients. Il doit se connecter à intervalles réguliers au serveur afin de vérifier si des logiciels doivent être installés ;
  • SoftwareInstallerLibrary : bibliothèque contenant les classes communes et l'interface de définition du serveur.

Ces trois projets sont une base de travail, mais l'on pourrait tout à fait imaginer que le service serveur soit implémenté en ASP.NET par exemple.

IV-B. SoftwareInstallerLibrary

Nous allons commencer par implémenter la bibliothèque de définition du serveur. Comme nous l'avons vu cette définition intervient sous forme d'une interface décorée par [ServiceContract].

 
Sélectionnez
/// <summary>
/// Interface de définition du service distant
/// </summary>
[ServiceContract]
public interface RemoteService
{

    /// <summary>
    /// Identifie le client sur le serveur.
    /// </summary>
    /// <param name="clientInformation">Les informations d'identification du client</param>
    [OperationContract]
    void IdentifyClient(ClientInformation clientInformation);

       /// <summary>
       /// Obtient la liste des logiciels à installer sur le client
       /// </summary>
       /// <returns></returns>
       [OperationContract]
    List<SoftwareData> GetSoftwareList();

       /// <summary>
       /// Renvoie le rapport d'installation au serveur afin de centraliser l'information
       /// </summary>
       /// <param name="clientReport">le rapport du client sur l'installation des logiciels</param>
       [OperationContract]
    void UploadReport(ClientReport clientReport);

}

Nous avons donc défini notre interface. Nous avons aussi fait appel à deux classes personnelles que nous allons détailler.

La première est SoftwareData :

 
Sélectionnez
/// <summary>
/// Classe représentant un logiciel devant être installé sur le poste client
/// </summary>
[DataContract]
public class SoftwareData
{

    private string name;
    private string installerAddress;
    private Version version;

    /// <summary>
    /// Constructeur par défaut
    /// </summary>
    public SoftwareData()
    {

    }

    #region Proprietes

    /// <summary>
    /// Obtient ou définit le nom du logiciel à installer
    /// </summary>
    [DataMember]
    public string Name
    {
        get
        {
            return this.name;
        }

        set
        {
            this.name = value;
        }
    }

    /// <summary>
    /// Obtient ou définit l'adresse de l'installeur
    /// </summary>
    [DataMember]
    public string InstallerAddress
    {
        get
        {
            return this.installerAddress;
        }

        set
        {
            this.installerAddress = value;
        }
    }

    /// <summary>
    /// Obtient ou définit la version du logiciel à installer
    /// </summary>
    [DataMember]
    public Version Version
    {
        get
        {
            return this.version;
        }

        set
        {
            this.version = value;
        }
    }

    #endregion

}

et la classe ClientReport :

 
Sélectionnez
/// <summary>
/// Classe définissant un rapport suite à l'installation de logiciel sur le client
/// </summary>
[DataContract]
public class ClientReport
{

    private DateTime date;

    [DataMember]
    public DateTime Date
    {
        get { return date; }
        set { date = value; }
    }

    private string clientName;

    public string ClientName
    {
        get { return clientName; }
        set { clientName = value; }
    }

    private List<SoftwareInstallReport> installReports;

    public List<SoftwareInstallReport> InstallReports
    {
        get { return installReports; }
        set { installReports = value; }
    }


}

Vous remarquerez qu'il n'y a aucun code spécifique à WCF à par les attributs sur les classes et les membres que l'on souhaite exposer au client. Notre librairie partagée entre le client et le serveur est maintenant finie.
Nous passons donc à la création du serveur.

IV-C. SoftwareInstallerServerService

Ce serveur est un peu particulier. En effet il tourne en tant que programme console sur le PC serveur où il est installé. Il va sans dire qu'un service Windows serait plus adapté, mais ce n'est pas le sujet dans cet article. Une bonne partie de l'initialisation du serveur WCF va donc se faire directement dans la méthode Main du programme, mais cela n'est pas une bonne architecture ;-). De plus notre programme n'est pas "security friendly". En effet il demande les droits administrateur pour fonctionner.

IV-C-1. Création de la base de WCF

Nous allons écrire le code nécessaire à l'initialisation et la configuration du serveur WCF.
Pour commencer, voyons le code d'initialisation du service serveur.

 
Sélectionnez
public void Main(string[] args)
{
    remoteService = new RemoteService();

    //Publication de la classe en singleton
    host = new ServiceHost(remoteService);

    //Ouverture du canal de communication
    host.Open();
}

Et le code d'arrêt du serveur :

 
Sélectionnez
//Fermeture du canal de communication
host.Close();

Ces quelques lignes de code suffisent à créer un canal de communication et à se mettre en attente d'une connexion d'un client. Il faut maintenant écrire le code du service proprement dit :

IV-C-2. L'implémentation du service WCF

 
Sélectionnez
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public class RemoteService : IRemoteService
{

    private Dictionary<string, ClientInformation> clientInformations;
    private List<string> clientAuthentified;

    /// <summary>
    /// Constructeur par défaut
    /// </summary>
    public RemoteService()
    {
        this.clientInformations = new Dictionary<string, ClientInformation>();
        this.clientAuthentified = new List<string>();

        //DEBUG
        ClientInformation ci = new ClientInformation();
        ci.ClientVersion = new Version("1.0.0.0");
        ci.Name = "dev01";

        this.clientInformations.Add(ci.Name, ci);
    }

    #region IRemoteService Members

    public void IdentifyClient(ClientInformation clientInformation)
    {
        if ( !clientInformations.ContainsKey(clientInformation.Name))
        {
            throw new Exception("Client inconnu !");
        }

        if (clientAuthentified.Contains(clientInformation.Name))
            throw new Exception("Client déjà authentifié");

        this.clientAuthentified.Add(clientInformation.Name);
    }

    public List<SoftwareData> GetSoftwareList(ClientInformation clientInformation)
    {
        if (this.clientAuthentified.Contains(clientInformation.Name))
        {
            //DEBUG
            SoftwareData soft = new SoftwareData();
            soft.InstallerAddress = "\\myserver\partage\setup.exe";
            soft.Name = "Test";
            soft.Version = new Version("0.0.0.1");

            List<SoftwareData> lists = new List<SoftwareData>();
            lists.Add(soft);

            return lists;
        }
        else
        {
            throw new Exception("Client non authentifié");
        }
    }

    public void UploadReport(ClientReport clientReport)
    {
            
    }

    #endregion
}

La seule chose importante dans cette classe c'est l'attribut [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]. Cet attribut permet de définir que notre classe est prévue pour être publiée en tant que singleton et sur un seul thread. Le paramètre ConcurrencyMode permet de définir si un thread doit être créé à chaque fois qu'un client fait un appel ou si les appels doivent attendre que le précédent soit fini. Il existe une troisième option, mais tout cela va plus loin que cette introduction.

IV-D. SoftwareInstallerClientService

Il ne reste plus qu'à écrire le programme client de notre projet.

 
Sélectionnez
/// <summary>
/// Service client récupérant la liste des softs à installer sur le client
/// </summary>
public partial class SoftwareInstallerClientService 
{

    private ChannelFactory<IRemoteService> channelFactory;
    private IRemoteService remoteSerice;
    private System.Timers.Timer timer;
    private ClientInformation clientInformation;

    public event UpdateNeededProgramEventHandler UpdateNeededProgram;

    public SoftwareInstallerClientService()
    {
            
    }

    public void Start()
    {
        channelFactory = new ChannelFactory<IRemoteService>("SI");
        channelFactory.Open();

        remoteSerice = channelFactory.CreateChannel();

        clientInformation = new ClientInformation();
        clientInformation.ClientVersion = new Version("1.0.0.0");
        clientInformation.Name = "dev01";
        clientInformation.UserName = Environment.UserName;

        //Envoi de l'authentification au serveur
        remoteSerice.IdentifyClient(clientInformation);

        //Timer toutes les 5 minutes
        timer = new System.Timers.Timer(6000);
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        timer.Enabled = true;
            
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        List<SoftwareData> softs = remoteSerice.GetSoftwareList(clientInformation);

        if (this.UpdateNeededProgram != null)
            this.UpdateNeededProgram(softs);
    }

    public void Stop()
    {
        timer.Enabled = false;

        channelFactory.Close();    
    }
}

Le point important de notre client est la façon dont on va récupérer l'instance du service. En effet il existe plusieurs manières, la plus courante étant de passer par un générateur d'interface, outil également utilisé pour la consommation de web service. Mais je trouve cette façon de faire extrêmement non pratique. Je préfère largement déployer l'interface du service sur le client que régénérer des classes avec un outil (ici le SoapSuds fourni par le SDK du framework). Nous utilisons une classe du framework 3.0 qui s'appelle ChannelFactory qui vous permet de récupérer une instance correspondant à l'interface du contrat. Cette classe peut prendre un certain nombre de paramètres pour son constructeur, mais le plus utile est de passer le nom du endpoint à utiliser pour créer cette instance. Ensuite un simple appel à CreateChannel vous récupérera tout ce qu'il faut pour utiliser de manière transparente votre service WCF.

V. Conclusion

Pour finir cet article d'introduction à Windows Communication Foundation, je dirai que l'arrivée de cette nouvelle technologie est une très très bonne chose. Elle rationalise et permet de fédérer les différentes méthodes de communications interapplications qu'elles soient locales ou distantes. De plus la simplicité est au rendez-vous alors pourquoi s'en priver ?
Par contre, il reste encore beaucoup de choses à connaître de ce fabuleux outil ;-).

VI. Téléchargements

VII. Remerciements

Je tiens remercier Cardi pour la relecture orthographique et toute la rubrique dotnet pour son travail.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Lainé Vincent. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.