Table of Contents
In some business solutions it is required to have different type of service enabled devices which are constantly linking and sendoff to the network. That is, the runtime location of those services is dynamic and continuously shifting. To connect to those services, clients require determining the runtime location of those service endpoints dynamically. Windows Communication Foundation (WCF) offers support to enable those services to be discoverable at runtime using the WS-Discovery protocol. In this article I will describe the WCF 4.0 Discovery System.
Discovery depends on the User Datagram Protocol (UDP). UDP is a connectionless protocol, and there is no direct connection required between the client and server. The client usages UDP to broadcast finding requests for any endpoint supporting a specified contract type. The discovery endpoints that support this contract will receive the request. The implementation of the discovery endpoint responds back to the client with the address of the service endpoints. Once the client determines the services, it invokes the service to set up call.
WCF services can broadcast their availability to the network using a multicast memo or to a discovery proxy server. Client applications can pursuit the network or a discovery proxy server to discovery services that encounter a set of criteria. There are two modes of service discovery Ad-hoc Discovery and Managed Discovery. In the ad-hoc discovery clients can discover services on a local subnet and in Managed discovery clients can discover services on a larger managed network through a discovery proxy.
The easiest way to enable service discovery is through the ad hoc mode. To configure the service for discovery, we need to add the standard udpDiscoveryEndpoint endpoint as well as need to enable the serviceDiscovery behavior on the service. Once udpDiscoveryEndpoint is active, clients will be able to discover it over UDP and bind to the endpoint. If the service exposes the metadataExchange endpoint, the client can discover it too and dynamically bind to the end point.
<system.serviceModel>
<services>
<service name="Rashim.RND.AdhocDiscovery.Services.MessageService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8732/MessageService"/>
</baseAddresses>
</host>
<endpoint address="/TEST" binding="wsHttpBinding" contract="Rashim.RND.AdhocDiscovery.Services.IMessageService"/>
<endpoint kind="mexEndpoint"/>
<endpoint kind="udpDiscoveryEndpoint"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata/>
<serviceDiscovery/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<endpointDiscovery>
<scopes>
<add scope="ldap:///ou=people,o=rashim"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
In the service section we see ,MessageService has a base address and it disclosures three endpoints. The mexEndpoint is for metadataexchange where udpDiscoveryEndpoint is for UDP discovery. There are couple of service behavior defines using default behavior. In endpoint behavior we add a behavior called endpointDiscovery and in the endpoint discovery we add the scope. Scopes are vastly useful in modifying discovery and in adding stylish behavior. A scope is a URI that has meaning to the caller and service. The service needs to associate one or more scopes with each endpoint it’s going to publish for discovery. Clients can then discover the service based on the scope.
var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
var findCriteria = FindCriteria.CreateMetadataExchangeEndpointCriteria(typeof(IMessageService));
findCriteria.MaxResults = 1;
Debug.Assert(findCriteria.Scopes != null, "findCriteria.Scopes != null");
findCriteria.Scopes.Add(new Uri("ldap:///ou=people,o=rashim"));
var findResponse = discoveryClient.Find(findCriteria);
if (findResponse != null)
{
if(findResponse.Endpoints!=null)
{
if (findResponse.Endpoints.Count > 0)
{
var endpoints = MetadataResolver.Resolve(typeof (IMessageService),
findResponse.Endpoints[0].Address);
var factory = new ChannelFactory<imessageservice>(endpoints[0].Binding, endpoints[0].Address);
var channel = factory.CreateChannel();
Console.WriteLine("Say something and press enter");
var input = Console.ReadLine();
while (input != null && input.ToString(CultureInfo.InvariantCulture).ToLower()!="exit")
{
Console.WriteLine(channel.GetMessage(input));
input = Console.ReadLine();
}
((IChannel)channel).Close();
}
}
}
Here DiscoveryClient allows us to discover available services. FindCriteria is used to find IMessageService endpoints in the specified scope. Once the client has retrieved the collection of discovered endpoints, it can use one of them to actually invoke the target service. The whole thing we can also define via config file given below.
<system.serviceModel>
<client>
<endpoint contract="Rashim.RND.AdhocDiscovery.Services.IMessageService"
kind="dynamicEndpoint"
endpointConfiguration="myDynamicEndpointConfig" name="discovery">
</endpoint>
</client>
<standardEndpoints>
<dynamicEndpoint>
<standardEndpoint name="myDynamicEndpointConfig">
<discoveryClientSettings>
<endpoint kind="udpDiscoveryEndpoint" name="udpDiscoveryEndpoint"/>
<findCriteria maxResults="1">
<scopes>
<add scope="ldap:///ou=people,o=rashim"/>
</scopes>
</findCriteria>
</discoveryClientSettings>
</standardEndpoint>
</dynamicEndpoint>
</standardEndpoints>
</system.serviceModel>
The WCF discovery feature enables mechanisms where the service broadcasts its status to all clients and provides its address. That is, each announcement contains the endpoint address, its scopes and its contract. The service host broadcasts Hello and Bye announcements when it is being online and offline. . But if the host is aborted ungracefully, no “bye” announcement is sent. Clients can listen for such announcement messages and then do necessary stuffs. This announcement services reduces the amount of probing/multicast messaging.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDiscovery>
<announcementEndpoints>
<endpoint kind="udpAnnouncementEndpoint"/>
</announcementEndpoints>
</serviceDiscovery>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<endpointDiscovery enabled="true"/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="Rashim.Discovery.Announcement.Server.MessageService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8085/MessageService"/>
</baseAddresses>
</host>
<endpoint kind="mexEndpoint" />
<endpoint kind="udpDiscoveryEndpoint"/>
<!-- Application Endpoint -->
<endpoint address="/test"
binding="basicHttpBinding" contract="Rashim.Discovery.Announcement.Common.IMessageServices"/>
</service>
</services>
</system.serviceModel>
We configure a service with an announcement endpoint by using the serviceDiscovery behavior. The serviceDiscovery behavior allows you to define a collection of announcement endpoints that will be exposed by the service. You can use the standard udpAnnouncementEndpoint for most cases. In the config file we see that announcementEndpoints add one udpAnnouncementEndpoint that will broadcast the messages when it comes online as well as offline. We also need to be aware about endpointDiscovery.It should enable to true to make a particular endpoint automatically discoverable.
var announcementService = new AnnouncementService();
announcementService.OnlineAnnouncementReceived += (sender, e) =>
{
var mexContractDescrition = ContractDescription.GetContract(typeof(IMetadataExchange));
var mexQualifiedName = new XmlQualifiedName(mexContractDescrition.Name, mexContractDescrition.Namespace);
e.EndpointDiscoveryMetadata.ContractTypeNames.ToList().ForEach((name) =>
{
if (mexQualifiedName != name) return;
var endpoints = MetadataResolver.Resolve(typeof(IMessageServices), e.EndpointDiscoveryMetadata.Address);
if (endpoints.Count <= 0) return;
var factory = new ChannelFactory<IMessageServices>(endpoints[0].Binding, endpoints[0].Address);
var channel = factory.CreateChannel();
Console.WriteLine("\nService is online now..");
var replyMessage = channel.GetMessage("Server is responding");
Console.WriteLine(replyMessage);
((ICommunicationObject)channel).Close();
});
};
announcementService.OfflineAnnouncementReceived += (sender, e) =>
{
if (e.EndpointDiscoveryMetadata.ContractTypeNames.FirstOrDefault(contract => contract.Name == typeof(IMessageServices).Name) == null) return;
Console.WriteLine("\nService says 'Good Bye'");
};
using (var announecementServiceHost = new ServiceHost(announcementService))
{
announecementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint());
announecementServiceHost.Open();
Console.WriteLine("Please enter to exit\n\n");
Console.ReadLine();
}
Clients host an AnnouncementService to listen for announcement to respond to the Hello and Bye messages. The AnnouncementService provides two event handlers: OnlineAnnouncementReceived and OfflineAnnouncementReceived. Client applications can simply host an instance of the AnnouncementService using ServiceHost and also subscribe to the OnlineAnnouncementReceived and OfflineAnnouncementReceived events.Please have a look on the code.
Managed discovery is needed when we want to use WS-Discovery beyond the boundaries of our local network. Managed discovery allows us to locate services no matter where they are, as long as they are registered with a discovery proxy. A discovery proxy is a standalone service that contains a repository of services. Clients can query a discovery proxy to find discoverable services that the proxy is aware of. How a proxy is populated with services is up to the implementer.
The System.ServiceModel.Discovery namespace includes a base class to help us in building our Discovery Proxy. To implement our proxy we will have to override a number of methods provided by the System.ServiceModel.Discovery.DiscoveryProxy class.
var baseAddress =
new Uri("http://localhost/demo");
using (var host = new System.ServiceModel.ServiceHost(new DiscoveryProxyService(), baseAddress))
{
host.AddServiceEndpoint(new DiscoveryEndpoint(new WSHttpBinding(), new EndpointAddress(host.BaseAddresses[0]+"/probe")){IsSystemEndpoint = false});
host.AddServiceEndpoint(new AnnouncementEndpoint(new WSHttpBinding(), new EndpointAddress(host.BaseAddresses[0] + "/announcement")) { IsSystemEndpoint = false });
host.Description.Endpoints.ToList().ForEach((item)=> Console.WriteLine(item.ListenUri));
host.Open();
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
In the above code portion, the host add two service endpoints, one is DiscoveryEndpoint which specifies where the client should listen for the discovery messages another one is AnnouncementEndpoint which specifies where the services should send announcement messages.The discovery proxy class that I have used in the demo project has been taken from here.
<system.serviceModel>
<services>
<service name="Rashim.RND.ManagedDiscovery.DiscoverableService.StringService">
<host>
<baseAddresses>
<add baseAddress="http://localhost/StringService"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior>
<endpointDiscovery>
<scopes>
<add scope="ldap:///ou=people,o=rashim"/>
</scopes>
</endpointDiscovery>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
In the configuration file, there is a base address and a scope has been defined The details of this file have been discussed in above in the simple service discovery section.
var announcementAddress =
new Uri("http://localhost/demo/announcement");
var host = new System.ServiceModel.ServiceHost(typeof (StringService));
try
{
host.AddDefaultEndpoints();
var announcementEndpoint = new AnnouncementEndpoint(new WSHttpBinding(),
new EndpointAddress(announcementAddress));
var serviceDiscoveryBehavior = new ServiceDiscoveryBehavior();
serviceDiscoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint);
host.Description.Behaviors.Add(serviceDiscoveryBehavior);
host.Open();
host.Description.Endpoints.ToList().ForEach((endpoint) => Console.WriteLine(endpoint.ListenUri));
Console.WriteLine("Please enter to exit");
Console.ReadLine();
host.Close();
}
catch (Exception oEx)
{
Console.WriteLine(oEx.ToString());
}
In the above code the host adds the ServiceDiscoveryBehavior which controls the discoverability of service endpoints and ServiceDiscoveryBehavior add AnnouncementEndpoints with the address that DiscoveryProxy exposes for announcement.
var probeAddress = new Uri("http://localhost/demo/probe");
var discoveryEndpoint = new DiscoveryEndpoint(new WSHttpBinding(), new EndpointAddress(probeAddress));
var discoveryClient = new DiscoveryClient(discoveryEndpoint);
var findCriteria = new FindCriteria(typeof (IStringService));
findCriteria.Scopes.Add(new Uri("ldap:///ou=people,o=rashim"));
var findResponse = discoveryClient.Find(findCriteria);
if (findResponse != null)
{
if (findResponse.Endpoints != null)
{
if (findResponse.Endpoints.Count > 0)
{
var factory = new ChannelFactory<IStringService>(new BasicHttpBinding(),
findResponse.Endpoints[0].Address);
var channel = factory.CreateChannel();
Console.WriteLine(channel.ToUpper("Rashim"));
}
}
}
else
{
Console.WriteLine("Could not find the Services");
}
Client uses the probe address that DiscoveryProxy provide to search for the services and also use the scope to narrow the search result. Once the client has found the specified Service endpoint it invokes
There are three solutions
- Rashim.RND.AdhocDiscovery
- Rashim.Discovery.Announcement.Solution
- Rashim.RND.ManagedDiscovery
Rashim.RND.AdhocDiscovery is a sample app for ad-hoc discovery using the Simple Service Discovery. To see the output of the demo, first, you need to run Rashim.RND.AdhocDiscovery.ServiceHost and then need to run Rashim.RND.AdhocDiscovery.Client.
Rashim.Discovery.Announcement.Solution is also a demo app for ad-hoc discovery using the Announcement Service. For this solution, you need to run Rashim.Discovery.Announcement.Client and then need to run the Rashim.Discovery.Announcement.Server. Please note that, if you want to exit from the server or the client, you just press enter. For case of any ungraceful shutdown/exit of the server, the client might not get notification for the arrival and exit of the server.
Rashim.RND.ManagedDiscovery Solution is also another demo app for managed discovery using the DiscoveryProxy. To see the output, first you need to run the Rashim.RND.ManagedDiscovery.DiscoveryProxy, then need to run Rashim.RND.ManagedDiscovery.ServiceHost and finally also need to run Rashim.RND.ManagedDiscovery.Client. Please note that, you need to maintain the sequence of the project to run.
http://msdn.microsoft.com/en-us/library/dd456782.aspx
http://msdn.microsoft.com/en-us/library/ee354381.aspx
http://msdn.microsoft.com/en-us/magazine/ee335779.aspx
http://geekswithblogs.net/shaunxu/archive/2012/07/06/service-discovery-in-wcf-4.0-ndash-part-1.aspx
http://msdn.microsoft.com/en-us/library/dd456787.aspx
That's it
That's it for now, I hope this article will help you to start working with WCF 4.0 discovery system.