WCF 4 comes with a bunch of new features where I find the service announcement and service discovery really cool. In this article I will implement a couple of services using these new features.
A couple of key words and classes for the scenario are SeviceDiscoveryBehavior, UdpAnnouncemenEndpoint, AnnouncementService. I will describe those classes along the way, but on MSDN you can find more information here: WCF Discovery Overview.
In my previous article I linked to a great article written by Aaron Skonnard, don’t miss it: A Developer’s Introduction to Windows Communication Foundation 4. It covers most of the new features in WCF 4, the simplified configuration, Workflow Services and a bunch of other stuff.
I will describe the scenario below along with a couple of code snippets.
The scenario
I want to try the announcement and discovery features in WCF 4 by implementing a publisher-subscriber system. There will be a ProxyService (a message carrier) that receives messages from some distributor and sends the message to one ore more subscribers. The scenario looks like this:

When a subscriber comes online, it will first look for a ProxyService to register its interest in receiving messages. It will also start listening to online and offline announcement messages from a ProxyService. The subscriber will register for messages as soon as possible, either if it finds a ProxyService or when it receives an online announcement from a ProxyService. If a subscriber receives an offline announcement messages from the ProxyService it will shutdown.
The ProxyService will announce its online and offline state upon startup and shutdown. Take a look at the sequence below for a possible scenario:

All services will be hosted in in separate console applications and the incoming message will be sent from the WcfTestClient tool (read about it here: (WCF Test Client (WcfTestClient.exe)).
Implementation
First I will describe the implementation of the ProxyService and try it out using the WcfTestClient. After that I will implement a subscriber service and then make the ProxyService distribute a message to the registered subscribers.
The WCF services below is located in separate projects in the Visual Studio solution, WCF Service Library projects. The hosts for the services are Console Application projects.
The ProxyService (IMsgCarrier)
The ProxyService will implement i ServiceContract (the IMsgCarrier interface). This ServiceContract contains to operations, RecieveAndDistributeMessage and RegisterSubscription. The interface looks like this:
using System.ServiceModel;
namespace ProxyService
{
[ServiceContract]
public interface IMsgCarrier
{
[OperationContract]
void ReceiveAndDistributeMessage(string message);
[OperationContract]
bool RegisterSubscription(string endPoint);
}
}
The implementation of the interface is for now rather simple, just echoing stuff to the console:
using System;
namespace ProxyService
{
public class MsgCarrier : IMsgCarrier
{
public void ReceiveAndDistributeMessage(string message)
{
Console.WriteLine(
string.Format("Received a message: {0}", message));
}
public bool RegisterSubscription(string endPoint)
{
Console.WriteLine(
string.Format("Received a registration from: {0}", endPoint));
return true;
}
}
}
The service needs to be hosted and I choose to host it in a console application. Here is the code for the static void Main, I will comment the code below:
using System;
using System.ServiceModel;
using System.ServiceModel.Discovery;
// The namespace of the service
using ProxyService;
namespace ProxyHost
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost serviceHost = new ServiceHost(typeof(MsgCarrier)))
{
// Make the service discoverable via Udp
serviceHost.AddServiceEndpoint(new UdpDiscoveryEndpoint());
// Create a behavior for announcement
ServiceDiscoveryBehavior discoveryBehavior =
new ServiceDiscoveryBehavior();
// The discovery behavior will also announce via Udp
discoveryBehavior.AnnouncementEndpoints.Add(
new UdpAnnouncementEndpoint());
// Add the behavior to the standard behaviors
serviceHost.Description.Behaviors.Add(discoveryBehavior);
// Handle a number of events from the service host,
// to write "debug" information to the console
serviceHost.Opening += new EventHandler(serviceHost_Opening);
serviceHost.Opened += new EventHandler(serviceHost_Opened);
serviceHost.Closing += new EventHandler(serviceHost_Closing);
serviceHost.Closed += new EventHandler(serviceHost_Closed);
// Bring the service online
serviceHost.Open();
// Wait for enter to take the service offline
Console.ReadLine();
// Bring the service offline
serviceHost.Close();
// Pause
Console.ReadLine();
}
}
static void serviceHost_Opening(object sender, EventArgs e)
{
// The service is coming online
Console.WriteLine("Brining MessageCarrier online...");
}
static void serviceHost_Opened(object sender, EventArgs e)
{
// The service is online
Console.WriteLine("MessageCarrier online...");
Console.WriteLine("Press <Enter> to take offline");
}
static void serviceHost_Closing(object sender, EventArgs e)
{
// The service is shutting down
Console.WriteLine("Taking MessageCarrier offline...");
}
static void serviceHost_Closed(object sender, EventArgs e)
{
// The service is offline
Console.WriteLine("MessageCarrier is offline...");
Console.WriteLine("Press <Enter> to exit");
}
}
}
- Line 6: The service library is referenced in the hosting console application and the namespace for the service is used here.
- Line 20-28: To make the service announcing when it comes online and offline a UdpAnnouncementEndpoint object is needed. This object is added to a ServiceDiscoveryBehavior that is added to the standard behaviors for the service host. This also makes the service discoverable by subscribers looking for the service on the local network.
- Line 32-35 and 51-75: Here a number of events are handled just to make the application more fun to watch. The event handlers just writes to the console what happens. Below is a screen shot when starting the service host.
- Line 38: The service is brought online.
- Line 44: The service is brought offline.
The console application looks like this, first the service is brought online, enter is pressed and the service is brought offline:

That pretty much ends the implementation of the ProxyService, for now. Let’s try it out with the WcfTestClient:

Ok, let’s get on with the subscriber service implementation.
The Subscriber (IRecipient)
The subscriber service host might look a little bit more complicated than the ProxyService host. A subscriber needs to listen to announcements, find a proxy service and expose a service for receiving messages from a ProxyService. It’s easier to describe those after the code, so here comes the code for the receiver service (IRecipient service contract):
using System.ServiceModel;
namespace SubscriberService
{
[ServiceContract]
public interface IRecipient
{
[OperationContract]
bool ReceiveMessage(string message);
}
}
The implementation of the service interface is easy:
using System;
namespace SubscriberService
{
public class Recipient : IRecipient
{
public bool ReceiveMessage(string message)
{
Console.WriteLine(string.Format("Received message: {0}", message));
return true;
}
}
}
No comments needed on this code. The service will just echo the received message to the console of the host application.
The service host is a little bit more interesting. The code looks like this (comments below):
using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Discovery;
using System.ServiceModel.Description;
// This is the namespace for the IRecipient service
using SubscriberService;
// The ProxyService has to be available for registration
using SubscriberHost.RegistrationService;
namespace SubscriberHost
{
class Program
{
// These are the two service hosts needed
// The recipient service host
private static ServiceHost serviceHost = null;
// The announcement service host
private static ServiceHost announcementsServiceHost = null;
// Flag indication whether the subscriber is registered or not
private static bool registered = false;
static void Main(string[] args)
{
using (serviceHost = new ServiceHost(typeof(Recipient)))
{
// Bring the subscriber online
serviceHost.Open();
// Create stuff for listening to announcements
AnnouncementService proxyAnnouncementService =
new AnnouncementService();
proxyAnnouncementService.OnlineAnnouncementReceived +=
new EventHandler(
proxyAnnouncementService_OnlineAnnouncementReceived);
proxyAnnouncementService.OfflineAnnouncementReceived +=
new EventHandler(
proxyAnnouncementService_OfflineAnnouncementReceived);
// Host the announcement listener
announcementsServiceHost = new ServiceHost(proxyAnnouncementService);
announcementsServiceHost.AddServiceEndpoint(
new UdpAnnouncementEndpoint());
// Start listening to announcements
announcementsServiceHost.Open();
// Create a discovery client and handle its completed event
DiscoveryClient proxyServiceFinder =
new DiscoveryClient(new UdpDiscoveryEndpoint());
proxyServiceFinder.FindCompleted +=
new EventHandler(
proxyServiceFinder_FindCompleted);
// Make the discovery client look for services
// implementing the IMsgCarrier contract
proxyServiceFinder.FindAsync(new FindCriteria(typeof(IMsgCarrier)));
Console.WriteLine("Subscriber online, waiting for messages");
Console.WriteLine("Press to tear down");
Console.ReadLine();
// The service hosts may have been closed some where else
if(announcementsServiceHost.State == CommunicationState.Opened ||
serviceHost.State == CommunicationState.Opened)
{
announcementsServiceHost.Close();
serviceHost.Close();
Console.WriteLine("Service hosts shutdown, press to exit");
Console.ReadLine();
}
}
}
////////
// Event handler for consuming online announcement messages received
// by the proxyAnnouncement service
static void proxyAnnouncementService_OnlineAnnouncementReceived(
object sender, AnnouncementEventArgs e)
{
Console.WriteLine("Some service sent 'Hello'");
if (!registered)
{
// Check the contract name to make sure it's the one we want
if (e.EndpointDiscoveryMetadata.ContractTypeNames.FirstOrDefault(
x => x.Name == typeof(IMsgCarrier).Name) != null)
{
Console.WriteLine("This is the right contract");
// Let's register
RegisterForSubscription(e.EndpointDiscoveryMetadata.Address);
}
else
{
Console.WriteLine(
string.Format(
"Received online announcement from type {0}, not interested",
e.EndpointDiscoveryMetadata.ContractTypeNames.First().Name));
}
}
else
{
Console.WriteLine("Already registered, waiting... for messages");
}
}
////////
// Event handler for consuming offline announcement messages received
// by the proxyAnnouncement service
static void proxyAnnouncementService_OfflineAnnouncementReceived(
object sender, AnnouncementEventArgs e)
{
Console.WriteLine("Some service sent 'Good Bye'");
// Check the contract name to make sure it's the one we're interested in
if (e.EndpointDiscoveryMetadata.ContractTypeNames.FirstOrDefault(
x => x.Name == typeof(IMsgCarrier).Name) != null)
{
Console.WriteLine("It was the right contract");
Console.WriteLine("Let's exit");
// Shut down the hosts
serviceHost.Close();
announcementsServiceHost.Close();
Console.WriteLine("Service hosts shut down, press to exit");
}
else
{
Console.WriteLine(
string.Format(
"Received offline announcement from type {0}, not interested",
e.EndpointDiscoveryMetadata.ContractTypeNames.First().Name));
}
}
////////
// Event handler for the finder client find async completed event
static void proxyServiceFinder_FindCompleted(
object sender, FindCompletedEventArgs e)
{
if (e.Result.Endpoints.Count > 0)
{
Console.WriteLine("Found a proxy service...");
if (registered)
{
Console.WriteLine("Already registered, waiting... for messages");
return;
}
// Get the service endpoint and register
FindResponse response = e.Result;
EndpointAddress proxyAddress = response.Endpoints[0].Address;
RegisterForSubscription(proxyAddress);
}
else if(!registered)
{
Console.WriteLine(
"Didn't find any proxy service, " +
"waiting for online announcement...");
}
}
////////
// Method for invoking the registration method on the proxy service
public static void RegisterForSubscription(EndpointAddress proxyAddress)
{
try
{
Console.WriteLine("Trying to register");
// Create a registration client and register
RegistrationService.MsgCarrierClient registrationClient =
new RegistrationService.MsgCarrierClient();
registrationClient.ChannelFactory.Endpoint.Address = proxyAddress;
registered = registrationClient.RegisterSubscription(
serviceHost.Description.Endpoints[0].Address.Uri.AbsoluteUri);
if (registered)
Console.WriteLine("Registered, waiting for messages now");
else
throw new Exception("Proxy service returned false on registration");
}
catch (Exception ex)
{
// Something went wrong
Console.WriteLine(
string.Format("Failed to subscribe {0}, let's exit", ex.Message));
// Close the service
serviceHost.Close();
announcementsServiceHost.Close();
Console.WriteLine("Service hosts shut down, press to exit");
}
}
}
}
As written above a subscriber needs to find a proxy upon startup, listen announcements and expose a method for receiving messages from the proxy. These three features are implemented in the code above:
- Line 52-62: A finder client is created, an event handler is attached and search is invoked via FindAsync on line 62. Note the FindCriteria created and fed to the FindAsync method, we are only interested in searching for services implementing the IMsgCarrier service contract. The FindComplete event is handled on line 146-169.
- Line 34-50: A service host for listening to announcements is created and opened. The service handles both online and offline announcements, line 37 and line 40 and line 84-143. Note that when receiving an online or offline message, the service contract of the announcing service needs to be checked to make sure that it is an announcement we are interested in. This is done on line 92 and 123.
- Line 32: A service host for the IRecipient service is brought online, waiting for messages.
- NOTE: To make the announcement listening service and the IRecipient service work properly side-by-side I had to give the service behavior in app.config a specific name and the service configuration point that behavior out. Otherwise the default, no name, service behavior interfered with the announcement listening service. The config looks like this:

There are two scenarios when the subscriber service host is started, one when the ProxyService is already online and one when the ProxyService comes online. The console should look something like this in the two scenarios:

Here the finder client found a proxy service and registered.

Here the finder client timed out, but later on the ProxyService came online announced it. The subscriber reacted and registered.
Note that the event handlers are not synchronizing their output, which makes it look a little bit strange. Take a look at: “Received online announcement from …., not interested” and “Trying to register”. These two outputs comes from different events.
When the ProxyService sends an offline message the subscriber client will look something like this:

Note that the ProxyService sends two announcements because of its two endpoints, one for Metadata and one for the service itself.
This pretty much ends the implementation of the subscriber service. The ProxyService need to be modified to actually register the subscribers and send the messages on to the registered subscribers. I will not cover this here though.
Summary
What has been accomplished here:
- A ProxyService is implemented, announcing via Udp over the local network its online and offline status. This service is also discoverable for the subscriber services.
- A subscriber service is implemented, listening to announcements and trying to find ProxyServices on the local network.
These new features in WCF 4, announcement and service discovery, are really cool stuff. It makes the service oriented systems even more loosely coupled, making way for moving services around the local network without affecting the service consumers at all, if they are implemented with the support for service discovery. This is good stuff!
Great artical
Is there any source code attached?
**- I am really thankful to this topic because it really gives great information “.”
Excellent post!! I really like your site!!
I like your post. Good job!