WCF Communication bi-directionnelle28 décembre 2010

Network-Web-icon

J’écris cette petite note afin de regrouper au sein d’un même article tout ce qu’il faut savoir pour créer une application client/server WCF au sein de laquelle le client parle au service et le service parle au client. Cela s’appelle une communication bi-directionnelle que la technologie WCF a introduit par rapport au protocole SOAP des webservices d’antan.

Les exemples suivants ont été réalisés au sein d’une application console faite en .NET 3.5.

Téléchargement : Projet WCF Communication bi-directionnelle 1.0 (26)

Prérequis

Ajouter une référence à System.ServiceModel v3.0.0.0 à votre projet Service et Client.

Configuration App.config

Que ce soit pour la configuration du service ou du client, vous devrez ajouter une balise <system.serviceModel> juste sous le noeud <configuration> afin d’y placer les éléments de configuration service ou client.

Configuration du service

<services>
   <service name="ConsoleApplication1.SampleDevilContract">
      <endpoint address="net.tcp://localhost:666/devil"
                contract="ConsoleApplication1.IDevilContract"
                binding="netTcpBinding" />
   </service>
</services>

Configuration du client

<client>
   <endpoint address="net.tcp://localhost:666/devil"
             binding="netTcpBinding"
             contract="ConsoleApplication1.IDevilContract"
             name="devilEP" />
</client>

Exemple de fichier de config complet

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>

    <!-- client side config -->
    <client>
      <endpoint address="net.tcp://localhost:666/devil"
                binding="netTcpBinding"
                contract="ConsoleApplication1.IDevilContract"
                name="devilEP" />
    </client>

    <!-- server side config -->
    <services>
      <service name="ConsoleApplication1.SampleDevilContract">
        <endpoint address="net.tcp://localhost:666/devil"
                  contract="ConsoleApplication1.IDevilContract"
                  binding="netTcpBinding" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Interfaces du service

Le service

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ConsoleApplication1
{
    /// <summary>
    /// Interface à implémenter coté service et à utiliser côté client pour communiquer avec le service.
    /// </summary>
    [ServiceContract(CallbackContract = typeof(IDevilCallbackContract))]
    public interface IDevilContract
    {
        /// <summary>
        /// IsOneWay définit à true permet au client d'envoyer le message (ici SubscribeDevil) sans attendre un retour.
        /// </summary>
        [OperationContract(IsOneWay = true)]
        void SubscribeDevil();
        [OperationContract(IsOneWay = true)]
        void UnsubscribeDevil();
    }
}

Le callback

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ConsoleApplication1
{
    /// <summary>
    /// Interface de callback à implémenter côté client.
    /// </summary>
    public interface IDevilCallbackContract
    {
        /// <summary>
        /// Les méthodes de callback doivent être marquées avec OperationContract.
        /// </summary>
        /// <param name="message"></param>
        [OperationContract]
        void OnCallback(string message);
    }
}

Implémentation du service

Cette implémentation du service est à définir dans le projet serveur.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ServiceModel;

namespace ConsoleApplication1
{
    /// <summary>
    /// Implémentation du service exposé aux clients.
    /// InstanceContextMode.Single pour indiquer que l'instance de notre service est unique pour tous les clients.
    /// ConcurrencyMode.Reentrant pour autoriser l'appel retour au client au sein d'une méthode appelée par ce client.
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    class SampleDevilContract : IDevilContract
    {
        private bool runningContract;

        #region IDevilContract Members

        public void SubscribeDevil()
        {
            Console.WriteLine("Service : SubscribeDevil");

            runningContract = true;
            while (runningContract)
            {
                // OperationContext.Current renvoie le context de communication retour afin que le service puisse
                // communiquer avec le client.
                if (OperationContext.Current == null)
                    return;
                var callBack = OperationContext.Current.GetCallbackChannel<IDevilCallbackContract>();
                callBack.OnCallback("You are mine.");

                Thread.Sleep(2000);
            }
        }

        public void UnsubscribeDevil()
        {
            Console.WriteLine("Service : UnsubscribeDevil");
            runningContract = false;
        }

        #endregion
    }
}

Implémentation du Callback

Cette implémentation est à définir dans votre projet client. La méthode OnCallback sera appelé par le service afin de notifier le client d’un évènement quelconque.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace ConsoleApplication1
{
    /// <summary>
    /// Implémentation du callback à définir sur l'application cliente.
    /// </summary>
    class SampleClientCallbackImpl : IDevilCallbackContract
    {
        #region IDevilCallbackContract Members

        public void OnCallback(string message)
        {
            Console.WriteLine("Client : {0}", message);
        }

        #endregion
    }
}

Etablissement de la connexion

Lancement du service

var singleton = new SampleDevilContract();
var host = new ServiceHost(singleton);
host.Open();

Le service se lance avec le paramétrage définit dans le fichier de configuration, via l’attribut name du noeud <service>.

Lancement du client

var clientCallbackImpl = new SampleClientCallbackImpl();
// devilEP est le nom du EndPoint définit dans le fichier app.config.
var duplexChannel = new DuplexChannelFactory<IDevilContract>(clientCallbackImpl, "devilEP");
IDevilContract devilContract = duplexChannel.CreateChannel();
devilContract.SubscribeDevil();
Console.WriteLine("Press any key to end.");
Console.ReadKey();
devilContract.UnsubscribeDevil();

La connexion au service est effectuée lors de l’appel à une méthode, pas avant. Si le service n’est pas lancé au moment de la connexion du client, une exception est lancée : EndpointNotFoundException.