One of the cool new features in WCF 4 is the support for WS-Discovery.

 

So what makes WCF Discovery so cool?

Normally when a client application wants to connect to a WCF service it has to know several thing about the service like the operations it supports and where it is located. Knowing the operations at design time makes sense, after all this is functionality you are going to call. But the address makes less sense, that is a deployment things and something we don’t really want to be concerned with at design time. So we can specify the service address in the configuration file of the client app and we can change this as needed. However that does mean that whenever we move the WCF service to a different location, for example a new server, we need to go into each clients configuration file and update it with the new address. Not so nice and error prone as we are manually editing a bunch of XML

Decoupling the client from the actual address of the WCF service is what WCF Discovery does meaning we can move our WCF service to a new machine and each client will automatically start using the new address. Now how cool is that?

 

The two modes of WCF Discovery

There are two different ways of using WCF Discovery. The first is add-hoc and is real simple because there is no central server involved and the second being managed which is more capable but also depends on some central service being available. In his post I will be taking a closer look at the simple add-hoc mode.

 

The beauty of add-hoc WCF Discovery

The real nice thing about add-hoc WCF Discovery is that a service can just add a discovery endpoint and the client can check for this whenever it needs to call the service. This is done using the UDP protocol through a broadcast message meaning that a service will just discoverable as long as it is on the same subnet. This means that on lots of cases where a service is used on a LAN this will just work without any extra configuration of a central discovery service. Nice and simple, just the way I like it

 

Adding add-hoc WCF Discovery to a service

To demonstrate how to use add-hoc discovery I wrote a real simple service and self host it using the following code:

using System;
using System.ServiceModel;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(MyService)))
            {
                host.Open();
                Console.WriteLine("The host is listening at:");
                foreach (var item in host.Description.Endpoints)
                {
                    Console.WriteLine("{0}\n\t{1}", item.Address, item.Binding                        );
                }
                Console.WriteLine();
                Console.ReadLine();
            }
        }
    }
 
    [ServiceContract]
    interface IMyService
    {
        [OperationContract]
        string GetData(int value);
    }
 
 
    class MyService : IMyService
    {
        public string GetData(int value)
        {
            var result = string.Format("You entered \"{0}\" via \"{1}\"", 
                value, 
                OperationContext.Current.RequestContext.RequestMessage.Headers.To);
            
            Console.WriteLine(result);
            return result;
        }
    }
}

Note there is nothing about WCF Discovery here at all, it is just a standard WCF service.

 

This WCF service has a configuration file that is almost standard. It exposes the service using three different endpoints, each with a different binding. The WCF Discovery extensions are added by adding an extra endpoint of kind udpDiscoveryEndpoint and the <serviceDiscovery/> element in the service behavior section.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="ConsoleApplication1.MyService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9000"/>
            <add baseAddress="net.tcp://localhost:9001/"/>
          </baseAddresses>
        </host>
        <endpoint address="basic"
                  binding="basicHttpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint address="ws"
                  binding="wsHttpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint address="tcp"
                  binding="netTcpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint kind="udpDiscoveryEndpoint" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceDiscovery/>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

 

If we run this service it prints a message to the console for each endpoint it is listening on.

Most of these endpoints are very standard, it’s the last one urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01 that stands out and is the UDP discovery endpoint.

 

Using this discovery enabled WCF service from a client application

The first option on the client is to use the DiscoveryClient and have it determine all possible endpoints for the service. This type lives in the System.ServiceModel.Discovery.dll assembly so you will need to add a reference to that first. Using the DiscoveryClient a FindCriteria we can now search for published endpoints and use one like this:

var client = new DiscoveryClient(new UdpDiscoveryEndpoint());
var criteria = new FindCriteria(typeof(IMyService));
var findResult = client.Find(criteria);
 
foreach (var item in findResult.Endpoints)
{
    Console.WriteLine(item.Address);
}
Console.WriteLine();
var address = findResult.Endpoints.First(ep => ep.Address.Uri.Scheme == "http").Address;
Console.WriteLine(address);
var factory = new ChannelFactory<IMyService>(new BasicHttpBinding(), address);
var proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(42));
((ICommunicationObject)proxy).Close();

This code will work just fine but there are two problems with it. First of all the client.Find() call will take 20 seconds. The reason is it wants to find as many endpoints as it can and the default time allowed is 20 seconds. You can set this to a shorter time but in that case you run the risk of making the timeout too short and the service not being discovered. The other option is to set the criteria.MaxResults to one so the client.Find() call return as soon as single endpoint is found.

The bigger problem here is the binding. I am exposing a BasicHttpBinding, a WSHttpBinding and a NetTcpBinding from my service and the DiscoveryClient doesn’t tell me which binding needs to be used with which address.

 

The DynamicEndpoint to the rescue

There is a different way of using WCF Discovery from the client that is far easier if all we want to do is call a specific service and we don’t really care about all addresses exposed and that is the DynamicEndpoint. Using the DynamicEndpoint we can specify which service we want to call and which binding we want to use and it will just do it for us if there is an endpoint for that binding. The following code calls the service with each of the three bindings supported.

foreach (var binding in new Binding[] { new BasicHttpBinding(), new WSHttpBinding(), new NetTcpBinding() })
{
    var endpoint = new DynamicEndpoint(ContractDescription.GetContract(typeof(IMyService)), binding);
    var factory = new ChannelFactory<IMyService>(endpoint);
    var proxy = factory.CreateChannel();
    Console.WriteLine(proxy.GetData(Environment.TickCount));
    ((ICommunicationObject)proxy).Close();
}

This results in the following output where we can see the different endpoints being used.

 

What happens if there is no endpoint with the required binding varies depending on the missing binding. If I remove the BasicHttpBinding from the service and run the client it will find the WSHttpBinding instead and try to call that resulting in a System.ServiceModel.ProtocolException error with message “Content Type text/xml; charset=utf-8 was not supported by service http://localhost:9000/ws.  The client and service bindings may be mismatched.”. The fact that DynamicEndpoint tries to use the address with a different HTTP binding sounds like a bug in WCF Discovery to me. On the other hand removing the WSHttpBinding from the service and running the client produces a much better System.ServiceModel.EndpointNotFoundException error with the message “2 endpoint(s) were discovered, but the client could not create or open the channel with any of the discovered endpoints.”.

 

Conclusion

WCF Discovery is a nice way to decouple clients from the physical address of the service giving us an extra bit of flexibility when deploying our WCF services.

 

Enjoy!

www.TheProblemSolver.nl

Wiki.WindowsWorkflowFoundation.eu