Mettre en place du “Call-Back” avec WCF

1459055735_3480b4050e_z

Récemment Julien m’a demandé comment il était possible de mettre un place un système de communication bi-directionnel en WCF. Comme j’avais un peu de temps, je lui ai fais une petite application de démonstration dont je partage avec vous les quelques subtilités.

L’idée est de réaliser une application très basique avec 2 fonctionnalités :

  • n clients se connectant en fournissant leur nom (Abonnement au serveur)
  • 1 serveur capable d’émettre des messages (via la console) à tous ses clients abonnés.

L’implémentation se fait en 3 grandes étapes correspondant chacune à un projet différent

1ère étape : Définir les interfaces de communication (Projet Interfaces)

Comme toujours en WCF, on commence par définir les interfaces de communication.

Il nous faut 1 interface qui corresponde aux échanges du client vers le serveur (Abonnement d’un client) :

[sourcecode language= »csharp » padlinenumbers= »true »]
[ServiceContract(CallbackContract = typeof(ICallBack))]
public interface IServeur
{
[OperationContract()]
void Connecter(string nom);
}
[/sourcecode]

Hormis les traditionnels attributs ServiceContract & OperationContrat, on notera la présence de la propriété CallbackContract permettant de définir l’interface que pourra utiliser le serveur pour rappeler ses clients :

[sourcecode language= »csharp »]
public interface ICallBack
{
[OperationContract()]
void RecevoirMessage(string message);
}
[/sourcecode]

Dans notre cas, le serveur appellera la méthode RecevoirMessage de chacun de ses clients en lui passant le message émis.

2ème étape : Créer le serveur (Projet Serveur)

Côté serveur, on dispose de 2 classes :

  • Program : Gestion de la console et hébergement du serveur via le classique ServiceHost
[sourcecode language= »csharp »]
static void Main()
{
Console.WriteLine("Démarrage du serveur");

Serveur svr = new Serveur();
using (ServiceHost host = new ServiceHost(svr))
{
host.Open();

Console.WriteLine("Entrez un message (‘fin’ pour terminer)");
string msg = null;
while ((msg = Console.ReadLine()) != "fin")
{
svr.EmettreMessage(msg);
// peut être écrit aussi : (host.SingletonInstance as Serveur).EmettreMessage(msg);
}
}
}

[/sourcecode]

A chaque fois qu’un message est tapé sur la console, celui-ci est ré-émis vers tous les clients (Méthode EmettreMessage)

Pour simplifier la configuration du serveur a été faite avec l’outil de configuration WCF accessible via un clic droit sur le fichier app.Config, option “Modifier la configuration WCF” :

image

  • Serveur :
[sourcecode language= »csharp »]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class Serveur : IServeur
{
Dictionary<string, ICallBack> _lstCallBacks = new Dictionary<string,ICallBack>();

public void Connecter(string nom)
{
Console.WriteLine("Connection de " + nom);
_lstCallBacks.Add(nom, OperationContext.Current.GetCallbackChannel<ICallBack>());
}

public void EmettreMessage(string msg)
{
foreach (ICallBack cb in _lstCallBacks.Values)
{
cb.RecevoirMessage(msg);
}
}
}
[/sourcecode]

Ce code présente les particularités suivantes :

  • Présence de l’attribut ServiceBehavior sur la classe avec la propriété InstanceContexteMode positionnée à Single: Le service est alors exposé en tant que singleton. C’est toujours la même classe qui est appelée et qui contient tous les callbacks des clients.
  • _lstCallBacks : la liste des tous les clients abonnés. Chaque instance correspond à un proxy vers le client et a été récupérée pendant la connexion du client via la méthode OperationContext.Current.GetCallbackChannel<ICallBack>().
  • La méthode EmettreMessage se contente ensuite de boucler sur la liste des CallBacks pour émettre les messages vers les clients. C’est la partie techniquement la plus compliquée mais heureusement pour nous, c’est WCF qui se charge de tout Sourire

3ème étape : Créer le client (Projet client)

Pour simplifier la démo (et le déboguage), une seule application a été créée pour simuler 4 clients :

image

2 points importants sur cette application :

  1. La classe CallBack implémentant l’interface ICallBack : c’est la classe qui sera appelée par le serveur. L’objet CallBack côté serveur correspond à un proxy vers une instance de CallBack côté client.
  2. La “plomberie” WCF. Toute l’initialisation est faite par code (et non par paramétrage : je vous le laisse en exercice Clignement d'œil) :
[sourcecode language= »csharp »]
var factory = new DuplexChannelFactory&lt;IServeur&gt;(_lstCallBacks[nom]);
factory.Endpoint.Address = new EndpointAddress(&quot;net.tcp://localhost:12345&quot;);
factory.Endpoint.Binding = new NetTcpBinding();
factory.Open();
var c = factory.CreateChannel(new EndpointAddress(&quot;net.tcp://localhost:12345&quot;));
c.Connecter(nom);
[/sourcecode]

Télécharger le projet

Pour plus de détails, n’hésitez pas à télécharger le projet ici.