[Note: all the information was gathered based on a close-to-Beta 1 build of .NET Framework 4.0 and Visual Studio 2010. Details may vary and change]
"WCF is so easy, easy as ABC!" I am sure we all can still hear this marketing sentence echoing down the halls of building 42 on Redmond campus.
For exposing WCF services we need endpoints and endpoints need A, B, and C - yeah, you know that.
For consuming WCF services we need the endpoint information from the service, the A(ddress), the B(inding), and the (C)ontract - yeah you know that.
But wait...
What if the endpoints change frequently? What if the actual binding information is changed from time to time? We always need to make sure that all the consumers have the same exact updated endpoint configuration. Tedious, error-prone, often unnecessary.
Hm, what if I have a closed environment where I could use something like dynamic discovery of available endpoints? Something like a UDP-based multicast message exchange which receives the endpoint information from an available service. Based on the discovery information (which is the endpoint address and some more information not that necessary for now) sent back by this service I as a consumer can then go ahead and talk to this service.
It turns out there is something called WS-Discovery and WCF in .NET Framework 4 supports this standard.
The service discovery feature enables client applications to dynamically discover service addresses at runtime in an interoperable way using WS-Discovery. The WS-Discovery specification outlines the message exchange patterns required for performing light-weight discovery of services, both by multicast (ad hoc) and unicast (utilizing a dedicated network resource, often referred to as a proxy).
I will be talking about the ad-hoc approach in this post.
How does it work?
The following is the config file for a sample Hello service. Nothing needs to be done or changed in code, so you can use your very own common WCF services from yesterday.
<configuration>
<system.serviceModel>
<services>
<service name="HelloService"
behaviorConfiguration="serviceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:7777/Services/Hello"/>
</baseAddresses>
</host>
<endpoint address=""
binding="basicHttpBinding"
contract="IHelloService" />
<endpoint name="udpDiscovery" kind="udpDiscoveryEndpoint"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceDiscovery />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
With this basic configuration for a service host we enable service and endpoint discovery for our service. Here we use a new feature of WCF4 called standard endpoints (by specifiying the "kind" attribute on the udpDiscovery endpoint). Some more information on standard endpoints in a later post.
If now a consumer wants to talk to a service which supports endpoints for IHelloService she can use the client-side discovery API like this:
DiscoveryClient discoveryClient = new DiscoveryClient("udpDiscoveryEndpoint");
FindCriteria findCriteria = new FindCriteria(typeof(IHelloService));
FindResponse findResponse = discoveryClient.Find(findCriteria);
if (findResponse.Endpoints.Count > 0)
{
EndpointAddress address = findResponse.Endpoints[0].Address;
ChannelFactory<IHelloServiceChannel> factory = new ChannelFactory<IHelloServiceChannel>(
new BasicHttpBinding(), address);
IHelloServiceChannel client = new factory.CreateChannel();
client.SayIt("Hello from WCF4!");
client.Close();
factory.Close();
}
Pretty easy, eh? We need the contract, the binding and get the actual endpoint address as a result from the discovery process. Cool!
But wait...
I still need to know about the binding. I still need to kn ow that here we need a basicHttpBinding. But what if the binding changes? I mean not something dramatic like from a session-less to a session-bound binding (which most probably would also mean changing parts of at least the consuming code) - more something like moving from basicHttpBinding to a custom binding with binary-over-http.
There is rescue. And this rescue has been in the WCF stack from the very first version on and is nothing new in WCF4. It is called MetadataResolver.
We can pass a MEX (MetadataExchange, based on WS-MetadataExchange) address to the static Resolve method. The MetadataResolver will then look for the service's MEX endpoint and retrieve metadata for a given contract (in our case this would be IHelloService).
First we need to add a MEX endpoint to our service:
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
Then the consumer can act like this:
ServiceEndpointCollection endpoints = MetadataResolver.Resolve(typeof(IHelloService), mexAddress);
We then have a collection of endpoints matching the contract criteria in our hands.
Now let's take one step further and combine the two ideas:
- Expose the MEX endpoint of a service with discovery.
- Let the consumer discover the MEX endpoint via DiscoveryClient (and just this one).
- Let the consumer obtain endpoint information via the MEX endpoint by using the MetadataResolver.
- Let the consumer call the service, with A,B, and C - but originally (read: at compile time) the consuming code only needs to know about the contract, the functional interface.
public class SimpleDiscoveryChannelFactory<TChannel> : ChannelFactory<TChannel>
{
private bool initialized;
protected override void ApplyConfiguration(string configurationName)
{
if (!initialized)
{
base.ApplyConfiguration(configurationName);
var dc = new DiscoveryClient(new UdpDiscoveryEndpoint());
var fc = FindCriteria.CreateMexEndpointCriteria();
fc.MaxResults = 1;
var fr = dc.Find(fc);
if (fr.Endpoints.Count > 0)
{
var eps = MetadataResolver.Resolve(typeof(TChannel), fr.Endpoints[0].Address);
if (eps.Count > 0)
{
initialized = true;
this.InitializeEndpoint(eps[0]);
}
else throw new MetadataException("Could not find appropriate endpoint for contract.");
}
else throw new DiscoveryException("Could not find appropriate MEX endpoint.");
}
}
}
Note: it is not really recommended to do network calls before Open() is called, so this is just for illustration purposes here.
I use ad-hoc discovery here through UDP and tell the client API to stop discovering when one matching MEX service endpoint was found based on the FindCriteria.
Then the consumer just has some few lines of actual code to call a service based on the contract at hand:
var cf = new SimpleDiscoveryChannelFactory<IHelloServiceChannel>();
var client = cf.CreateChannel();
Console.WriteLine(client.SayIt("Hello from WCF4!");
client.Close();
cf.Close();
Yes, it works :)
There you are: "Look ma: I just need the contract to talk to my service(s)!" We get the current binding and endpoint address dynamically.
Note:
We can only use on the consuming side what is actually exposed via metadata. As you all know the throttles and quotas on the bindings are not exposed via metadata.
That's it for now for a first introduction to dynamic discovery in WCF4.
Stay tuned for more.
Hi, Do you know some scenario where can I use this feature?
Posted by: Kevin | 05/14/2009 at 12:48 AM
Hi Kevin, I would say where ever you -can afford to have ad-hoc discovery via UDP - and -expect often changing endpoint addresses and differing binding config (even if only small changes...) Do you have any other ideas? Cheers, -Christian
Posted by: Christian Weyer | 05/14/2009 at 12:13 PM
Hi Chris, But discovery feature only work in an internal network, allright? Or this feature will find avaliable services in a specific machine? Is it possible use WS-Discovery for discovery all endpoints in internet?
Posted by: Kevin | 05/14/2009 at 03:34 PM
Not sure what you mean by discovery all endpoints in internet?...
Posted by: Christian Weyer | 05/17/2009 at 12:29 PM
The article you have been written have great influence on the modern society.
Posted by: Nike Shox Deliver | 07/12/2010 at 04:36 AM