() => {
WCF

WCF Discovery – mise en place

WCF Discovery est la faculté de rendre détectable un service ou un ensemble de services. Cette fonctionnalité est souvent oubliée ou, au mieux, à la mauvaise réputation d’être dure à implémenter, il n’en est rien.

Partons sur une configuration standard d’un service WCF :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="discoveryBehavior"
               name="WcfDiscoveryServiceLibrary.Service1">
        <endpoint address="http://petetin-pc/MyService"
                  binding="wsHttpBinding"
                  contract="WcfDiscoveryServiceLibrary.IService1" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Voiçi les  2 étapes pour rendre perfectible la détection du service  :

– ajouter 1 endpoint d’écoute des demandes de découverte

– ajouter le cœur même de la découverte via le behavior serviceDiscovery

Ci-dessous la configuration adéquate :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="discoveryBehavior"
               name="WcfDiscoveryServiceLibrary.Service1">
        <endpoint address="http://petetin-pc/MyService"
                  binding="wsHttpBinding"
                  contract="WcfDiscoveryServiceLibrary.IService1" />
        <endpoint kind="udpDiscoveryEndpoint" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="discoveryBehavior">
          <serviceDiscovery />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

 

Le behiavor serviceDiscovery doit être présent pour chaque endpoint d’écoute de découverte. Comme tout bebiavor WCF, il peut être utilisé pour 1 unique service comme l’exemple précédent via la propriété behaviorConfiguration s’il est nommé, ou être global à tous les services (behavior sans nom qui devient alors le behavior par défaut de tous les services).

Il faut 1 endpoint d’écoute de découverte par service où l’on souhaite activer cette fonctionnalité. Celui utilisé ci-dessus est le endpoint standard udpDiscoveryEndpoint que propose WCF. C’est par ce endpoint standard que l’on va pouvoir configurer le canal de découverte :

<standardEndpoints>
  <udpDiscoveryEndpoint>
    <standardEndpoint multicastAddress="soap.udp://239.255.255.250:3702">
      <transportSettings duplicateMessageHistoryLength="0"
                          maxPendingMessageCount="33"
                          maxMulticastRetransmitCount="3"
                          maxUnicastRetransmitCount="2"
                          maxReceivedMessageSize="65506"
                          maxBufferPoolSize="524289" />
    </standardEndpoint>
  </udpDiscoveryEndpoint>
</standardEndpoints>

 

Je vous conseille de lire la documentation msdn avant de modifier l’un de ces paramètres: http://msdn.microsoft.com/fr-fr/library/dd456782.aspx

A ce point, le service répondra présent à tous ceux souhaitant connaitre qui implémente le service IMyService ! Un exemple de code :

static void Main(string[] args)
{
    var helper = new DiscoveryClientHelper();
    helper.FindCompleted += (sender, e) =>
    {
        if (e.Error == null && e.Cancelled == false)
        {
            if (e.Result.Endpoints.Count > 0)
            {
                Console.WriteLine("Service found: " + e.Result.Endpoints[0].Address.Uri);
            }
            else
            {
                Console.WriteLine("Service not found.");
            }
        }
    };

    helper.Find(typeof(IService1));
    Console.ReadLine();
}

Avec la classe helper suivante :

public class DiscoveryClientHelper
{
    private readonly object _token = new object();
    private readonly DiscoveryClient _discoveryClient;

    public DiscoveryClientHelper(DiscoveryEndpoint discoveryEndpoint = null)
    {
        var endpoint = discoveryEndpoint ?? new UdpDiscoveryEndpoint();
        _discoveryClient = new DiscoveryClient(endpoint);
    }

    public DiscoveryClientHelper(string endpointConfigurationName)
    {
        _discoveryClient = new DiscoveryClient(endpointConfigurationName);
    }

    public void Find(Type contractType)
    {
        var criteria = new FindCriteria(contractType) { MaxResults = 1 };
        _discoveryClient.FindAsync(criteria, _token);
    }

    public void Cancel()
    {
        _discoveryClient.CancelAsync(_token);    
    }

    public void Close()
    {
        _discoveryClient.Close();
    }

    public event EventHandler<FindCompletedEventArgs> FindCompleted
    {
        add { _discoveryClient.FindCompleted += value; }
        remove { _discoveryClient.FindCompleted -= value; }
    }

    public event EventHandler<FindProgressChangedEventArgs> FindProgressChanged
    {
        add { _discoveryClient.FindProgressChanged += value; }
        remove { _discoveryClient.FindProgressChanged -= value; }
    }
}

 

Dans un certain nombre de cas, rendre un service détectable ne suffit pas. Il faudra aussi que le service soit capable d’annoncer qu’il démarre ou qu’il s’arrête. Dès lors, un client qui recherche un service alors que celui-ci n’est pas encore démarré se rattrapera en s’abonnant à l’annonce de son démarrage.

Reprenons la configuration de notre service pour ajouter son annonce :

<behavior name="discoveryBehavior">
  <serviceDiscovery>
    <announcementEndpoints />
  </serviceDiscovery>
</behavior>
 

Les clients pourront alors savoir si un service commence ou termine son écoute grâce au code suivant. Notez à l’avance qu’il sera nécessaire d’héberger un endpoint d’écoute. Je vous propose le code suivant :

static void Main(string[] args)
{
    var announcementServiceHelper = new AnnouncementServiceHelper {
        ContractTypeNamesFilter = new[] { "IMyService" },
    };

    announcementServiceHelper.OnlineAnnouncementReceived += (sender, e) =>
        Console.WriteLine("Service started: " + e.EndpointDiscoveryMetadata.Address.Uri);

    announcementServiceHelper.OfflineAnnouncementReceived += (sender, e) =>
        Console.WriteLine("Service stopped: " + e.EndpointDiscoveryMetadata.Address.Uri);
}

 

Avec la classe helper suivante :

public class AnnouncementServiceHelper
{
    readonly AnnouncementService _announcementService;

    public ServiceHost Host { get; private set; }

    public AnnouncementServiceHelper(TimeSpan? openTimeout = null, params Uri[] baseAddresses) :
        this(new UdpAnnouncementEndpoint(), openTimeout, baseAddresses)
    {
    }

    public AnnouncementServiceHelper(ServiceEndpoint serviceEndpoint, TimeSpan? openTimeout = null, params Uri[] baseAddresses)
    {
        ContractTypeNamesFilter = new List<string>(1);
        _announcementService = new AnnouncementService();

        _announcementService.OnlineAnnouncementReceived += OnOnlineAnnouncementReceived;
        _announcementService.OfflineAnnouncementReceived += OnOfflineAnnouncementReceived;

        Host = new ServiceHost(_announcementService, baseAddresses);
        Host.AddServiceEndpoint(serviceEndpoint);

        if (openTimeout == null)
            Host.BeginOpen(OpenCallback, null);
        else
            Host.BeginOpen(openTimeout.Value, OpenCallback, null);
    }

    public IEnumerable<string> ContractTypeNamesFilter { get; set; }

    private void OpenCallback(IAsyncResult ar)
    {
        Host.EndOpen(ar);
    }

    public void Close(TimeSpan? timeSpan = null)
    {
        if (timeSpan == null)
            Host.Close();
        else
            Host.Close(timeSpan.Value);
    }

    public event EventHandler<AnnouncementEventArgs> OnlineAnnouncementReceived;
    private void OnOnlineAnnouncementReceived(object sender, AnnouncementEventArgs e)
    {
        if (OnlineAnnouncementReceived != null)
            if (ContractTypeNamesFilter == null || e.EndpointDiscoveryMetadata.ContractTypeNames.Any(item => ContractTypeNamesFilter.Contains(item.Name)))
                OnlineAnnouncementReceived(this, e);
    }

    public event EventHandler<AnnouncementEventArgs> OfflineAnnouncementReceived;
    private void OnOfflineAnnouncementReceived(object sender, AnnouncementEventArgs e)
    {
        if (OfflineAnnouncementReceived != null)
            if (ContractTypeNamesFilter == null || e.EndpointDiscoveryMetadata.ContractTypeNames.Any(item => ContractTypeNamesFilter.Contains(item.Name)))
                OfflineAnnouncementReceived(this, e);
    }
}

Si vous hébergez votre service via les ServiceHost d’ASP.NET, il vous faudra changer le factory du host. Voici un exemple de fichier .svc :

<%@ ServiceHost Language="C#" Debug="true"
                Factory="DiscoveryService.DiscoveryServiceHostFactory"
                Service="DiscoveryService.Service2"
                CodeBehind="Service2.svc.cs" %>

 

Pour rendre le service détectable avec une annonce de démarrage, la factory pourrait ressembler au code suivant  :

public class DiscoveryServiceHostFactory : ServiceHostFactoryBase
{
    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
    {
        var factory = new ServiceHostFactory();
        ServiceHostBase serviceHostBase = factory.CreateServiceHost(constructorString, baseAddresses);
        serviceHostBase.EnableAnnouncementService();
        return serviceHostBase;
    }
}

Cette factory utilise la classe d’extension que je vous propose ci-dessous :

public static class WcfDiscoveryExtension
{
    public static ServiceDiscoveryBehavior EnableDiscoveryService(this ServiceHostBase serviceHost, ServiceEndpoint discoveryEndpoint = null)
    {
        var discoveryBehavior = new ServiceDiscoveryBehavior();
        serviceHost.Description.Behaviors.Add(discoveryBehavior);
        serviceHost.AddServiceEndpoint(discoveryEndpoint ?? new UdpDiscoveryEndpoint());
        return discoveryBehavior;
    }

    public static ServiceDiscoveryBehavior EnableAnnouncementService(this ServiceHostBase serviceHostBase, AnnouncementEndpoint announcementEndpoint = null)
    {
        var discoveryBehavior = serviceHostBase.Description.Behaviors.OfType<ServiceDiscoveryBehavior>().FirstOrDefault() ??
                                serviceHostBase.EnableDiscoveryService();

        discoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint ?? new UdpAnnouncementEndpoint());
        return discoveryBehavior;
    }
}

 

J’espère vous avoir donné toute les billes nécessaires pour implémenter la découverte de vos services et vous avoir donné envie d’en savoir plus sur le sujet. Je vous renvois encore une fois vers la documentations msdn somptueuse sur le sujet : http://msdn.microsoft.com/fr-fr/library/dd456782.aspx

Dans l’état, la découverte de vos services ne concerne que votre réseau intranet. Je vous proposerais dans un autre article d’exposer vos services sur internet via un proxy de découverte WCF.

Publicités

À propos de vpetetin

Lorsqu’en 1989 j'écris ma première ligne de code, c’est sans me rendre compte que j'avais été frappé par la foudre du dieu logiciel ! Séduit par les technologies Microsoft, j'ai débuté ma carrière en C avec les joies des interfaces MFC et objets COM. Toujours à la recherche d’industrialisation et de réutilisabilité, après plus de 12 ans d'expériences, je continue aujourd’hui ma carrière en tant qu’architecte consultant .NET et donne régulièrement des formations tout en m’orientiant vers la plateforme Windows Azure. Eternel enthousiaste, je ne manquerai jamais d’aborder avec vous les sujets de nos vies quotidiennes avec bonne humeur et philosophie.

Discussion

Pas encore de commentaire.

Laisser un commentaire

Choisissez une méthode de connexion pour poster votre commentaire:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Catégories

Mises à jour Twitter

Entrez votre adresse email pour suivre ce blog et recevoir les notifications de nouveaux messages par email.

Rejoignez 3 autres abonnés

%d blogueurs aiment cette page :