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 :
<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 :
<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 :
<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 :
{
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 :
{
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 :
<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 :
{
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 :
{
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 :
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 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 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.
Discussion
Pas encore de commentaire.