January 2010

Volume 25 Number 01

Foundations - Discover a New WCF with Discovery

By Juval Lowy | January 2010

Download the Code Sample

All the Windows Communication Foundation (WCF) calls possible with the Microsoft .NET Framework 3.5 share two constraints. First, the port or pipe assigned to the service must be available. The application developer or administrator literally has to guess or have some way of reserving them. Second, the client must apriori know the address of the service endpoints, both the port number and the service machine, or the pipe name.

It would be great if the service could use any available address. The client, in turn, would need to discover that address at runtime. In fact, there is an industry standard-based solution that stipulates how that discovery takes place. That solution, called simply discovery (and its supporting mechanisms), is the subject of this column. I will also introduce several useful tools and helper classes. The source code for these is available at https://code.msdn.microsoft.com/mag2010WCF.

Address Discovery

Discovery relies on the User Datagram Protocol (UDP). Unlike the Transmission Control Protocol (TCP), UDP is a connectionless protocol, and no direct connection is required between the packets sender and the receiver. The client uses UDP to broadcast discovery requests for any endpoint supporting a specified contract type. These requests are received by dedicated discovery endpoints that the services support. The implementation of the discovery endpoint responds back to the client with the address of the service endpoints that support the specified contract. Once the client discovers the services, it continues to invoke them as with regular WCF calls. This sequence is illustrated in Figure 1.

Figure 1 Address Discovery over UDP

image: Address Discovery over UDP

Much like the Metadata Exchange (MEX) endpoint, WCF offers a standard discovery endpoint with the type UdpDiscoveryEndpoint:

public class DiscoveryEndpoint : ServiceEndpoint

{...}

public class UdpDiscoveryEndpoint : DiscoveryEndpoint

{...}

The service can have the host implement that endpoint by adding the ServiceDiscoveryBehavior to the collections of behaviors supported by the service. You can do that programmatically like this:

ServiceHost host = new ServiceHost(...); 
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
ServiceDiscoveryBehavior discovery = new ServiceDiscoveryBehavior();
host.Description.Behaviors.Add(discovery);
host.Open();

Figure 2 shows how to add the discovery endpoint and the discovery behavior using the service config file.

Figure 2 Adding Discovery Endpoint in Config

<services>
   <service name = "MyService">
      <endpoint 
         kind = "udpDiscoveryEndpoint"
      />
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior>
         <serviceDiscovery/>
      </behavior>
   </serviceBehaviors>
</behaviors>

Dynamic Addresses

Discovery is independent of how exactly the service host defines its endpoints. However, what if the client is expected to use discovery to find the service address? In that case, the service is at liberty to configure its endpoint addresses on the fly, dynamically, based on any available port or pipe. 

To automate using dynamic addresses, I wrote the DiscoveryHelper static helper class with the two properties AvailableIpcBaseAddress and AvailableTcpBaseAddress:

public static class DiscoveryHelper
{
   public static Uri AvailableIpcBaseAddress
   {get;}
   public static Uri AvailableTcpBaseAddress
   {get;}
}

Implementing AvailableIpcBaseAddress is straightforward—because any uniquely named pipe will do, the property uses a new globally unique identifier (GUID) to name the pipe.  Implementing AvailableTcpBaseAddress is done by finding an available TCP port via opening port zero.

Figure 3 shows how to use AvailableTcpBaseAddress.

Figure 3 Using Dynamic Addresses

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.AddDefaultEndpoints();
host.Open();
<service name = "MyService">
   <endpoint 
      kind = "udpDiscoveryEndpoint"
   />
</service>
<serviceBehaviors>
   <behavior>
      <serviceDiscovery/>
   </behavior>
</serviceBehaviors>

If all you want is the dynamic base address for your service, the code in Figure 3 is less than perfect, because it still requires you to add discovery, either in the config file or programmatically. 
You can streamline these steps with my EnableDiscovery host extension, defined as:

public static class DiscoveryHelper
{
   public static void EnableDiscovery(this ServiceHost host,bool enableMEX = true);
}

When using EnableDiscovery there is no need for programmatic steps or a config file:

Uri baseAddress = DiscoveryHelper.AvailableTcpBaseAddress;
ServiceHost host = new ServiceHost(typeof(MyService),baseAddress);
host.EnableDiscovery();
host.Open();

If the host has not already defined endpoints for the service, EnableDiscovery will add the default endpoints. EnableDiscovery 
will also default to adding to the service the MEX endpoint on its base addresses.

Client-Side Steps

The client uses the DiscoveryClient class to discover all endpoint addresses of all services that support a specified contract:

public sealed class DiscoveryClient : ICommunicationObject
{
    public DiscoveryClient();
    public DiscoveryClient(string endpointName);
    public DiscoveryClient(DiscoveryEndpoint discoveryEndpoint);
    public FindResponse Find(FindCriteria criteria);
   //More members
}

Logically, DiscoveryClient is a proxy to the discovery endpoint. Like all proxies, the client must provide the proxy’s constructor with the information about the target endpoint. The client can use a config file to specify the endpoint or programmatically provide the standard UDP discovery endpoint for that purpose, since no further details (such as address or binding) are required. The client then calls the Find method, providing it with the contract type to discover via an instance of FindCriteria:

public class FindCriteria

{

   public FindCriteria(Type contractType);
   //More members

}

Find returns an instance of FindResponse, which contains a collection of all the discovered endpoints:

public class FindResponse

{
   public Collection<EndpointDiscoveryMetadata> Endpoints
   {get;}
   //More members
}

Each endpoint is represented by the EndpointDiscoveryMetadata class:

public class EndpointDiscoveryMetadata
{
   public EndpointAddress Address
  {get;set;}
   //More members
}

The main property of the EndpointDiscoveryMetadata is Address, which finally contains the discovered endpoint address. Figure 4 shows how a client can use these types in conjunction to discover the endpoint address and invoke the service.

Figure 4 Discovering and Invoking an Endpoint

DiscoveryClient discoveryClient = 
   new DiscoveryClient(new UdpDiscoveryEndpoint());
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
//Just grab the first found
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpBinding();
IMyContract proxy = 
   ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

There are several noteworthy problems with Figure 4.

While the client may discover multiple endpoints supporting the desired contract, it has no logic to resolve which one to invoke. It simply invokes the first one in the returned collection.

Discovery is geared toward addresses only. There’s no information about which binding to use to invoke the service. Figure 4 simply hardcodes the use of the TCP binding. The client will have to repeat these minute steps over and over every time it needs to discover the service address.

Discovery takes time. By default, Find will wait for 20 seconds for the services to respond to the UDP discovery request. Such a delay makes discovery inadequate for use in many applications, certainly when the application performs a high volume of tight calls. While you could shorten that timeout, if you do so, you run the risk of not discovering any or all of the services. DiscoveryClient does offer an asynchronous discovery, but that is of no use for a client that needs to invoke the service before continuing with its execution. 

You will see several approaches to addressing these problems in this column.

Scopes

The use of discovery implies a somewhat loose relationship between the client and the service or services it discovers. This presents another set of problems—how can the client know it has discovered the right endpoint? When multiple compatible endpoints are discovered, which one should the client invoke?

Clearly, there is a need for some mechanism that will help the client filter the results of discovery. This is exactly what scopes are about. A scope is merely a valid URL associated with the endpoint. The service can associate a scope or even multiple scopes with each of its endpoints. The scopes are bundled along with the addresses in the response to the discovery request. In turn, the client can filter the discovered addresses based on the scopes found or, better yet, try to find only relevant scopes in the first place.

Scopes are immensely useful in customizing discovery and in adding sophisticated behavior to your application, especially when writing a framework or administration tools. The classic use for scopes is to enable the client to distinguish among polymorphic services from different applications. However, this is somewhat of a rare occurrence. I find scopes handy when it comes to distinguishing among endpoint types in the same application.

For example, suppose for a given contract you have multiple implementations. You have the operational mode used in production and the simulation mode used in testing or diagnostics. Using scopes, the client can pick and choose the correct implementation type needed, and different clients never conflict with one another by consuming one another’s services. You can also have the same client pick up a different endpoint based on the context of the invocation. You could have endpoints for profiling, debugging, diagnostics, testing, instrumentation and so on. 

The host assigns scopes on a per-endpoint basis using the EndpointDiscoveryBehavior class. For example, to apply across all endpoints, use a default endpoint behavior:

<endpointBehaviors>
   <behavior>
      <endpointDiscovery>
         <scopes>
            <add scope = "net.tcp://MyApplication"/>
         </scopes>
      </endpointDiscovery>
   </behavior>
</endpointBehaviors>

You apply scopes discretely, based on the type of service, by assigning the behaviors explicitly per endpoint, as shown in Figure 5.

Figure 5 Explicit Behavior Assignment

<service name = "MySimulator">
   <endpoint behaviorConfiguration = "SimulationScope"
      ...
   />
   ...
</service>
...
   <behavior name = "SimulationScope">
      <endpointDiscovery>
         <scopes>
            <add scope = "net.tcp://Simulation"/>
         </scopes>
      </endpointDiscovery>
   </behavior>

A single discovery behavior can list multiple scopes:

<endpointDiscovery>
   <scopes>
      <add scope = "net.tcp://MyScope1"/> 
      <add scope = "net.tcp://MyScope2"/>
   </scopes>
</endpointDiscovery>

If an endpoint has multiple scopes associated with it, when the client tries to discover the endpoint based on scope matching, the client needs at least one of the scopes to match, but not all of them.

The client has two ways of using scopes. The first is to add the scope to the finding criteria:

public class FindCriteria
{
   public Collection<Uri> Scopes
  {get;}
   //More members
}

Now the Find method will return only compatible endpoints that also list that scope. If the client adds multiple scopes, then Find will return only endpoints that support all of the listed scopes. Note that the endpoint may support additional scopes not provided to Find.

The second way of using scopes is to examine the scopes returned in FindResponse:

public class EndpointDiscoveryMetadata
{
   public Collection<Uri> Scopes
   {get;}
  //More members
}

These scopes are all the scopes supported by the endpoint, and they are useful for additional filtering. 

Discovery Cardinality

Whenever relying on discovery, the client must deal with what I call discovery cardinality, that is, how many endpoints are discovered and which one, if any, to invoke. There are several cases of cardinality:

  • No endpoint is discovered. In this case the client needs to deal with the absence of the service. This is no different from any other WCF client whose service is unavailable.
  • Exactly one compatible endpoint is discovered. This is by far the most common case—the client simply proceeds to invoke the service.
  • Multiple endpoints are discovered. Here the client, in theory, has two options. The first is to invoke all of them. This is the case with a publisher firing an event at subscribers, as discussed later on, and is a valid scenario. The second option is to invoke some (including only one), but not all of the discovered endpoints. I find that scenario to be moot. Any attempt to place logic in the client that resolves which endpoint to invoke 
creates too much coupling across the system. It negates the very notion of runtime discovery, namely, that any discovered endpoint will do. If it is possible to discover undesirable endpoints, then using discovery is a poor design choice, and you should instead provide static addresses to the client.

If the client expects to discover exactly one endpoint (cardinality of one), then the client should instruct Find to return as soon as it finds that endpoint. Doing so will drastically reduce the discovery latency and make it adequate for the majority of cases.

The client can configure the cardinality using the MaxResults property of FindCriteria:

public class FindCriteria
{
   public int MaxResults
   {get;set;}
   //More members
}
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
criteria.MaxResults = 1;

You can streamline the case of cardinality of one using my Discovery​Helper.DiscoverAddress<T> helper method:

public static class DiscoveryHelper
{
   public static EndpointAddress DiscoverAddress<T>(Uri scope = null);
   //More members
}

Using DiscoverAddress<T>, Figure 4 is reduced to:

EndpointAddress address = DiscoveryHelper.DiscoverAddress<IMyContract>();
Binding binding = new NetTcpBinding();
IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(binding,address);
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

Streamlining Discovery

So far, the client has had to hardcode the binding to use. However, if the service supports a MEX endpoint, the client can discover the MEX endpoint address, then proceed to retrieve and process the metadata in order to obtain the binding to use, along with the endpoint address. To help with MEX endpoint discovery, the FindCriteria class offers the static method CreateMetadataExchangeEndpointCriteria:

public class FindCriteria
{
   public static FindCriteria CreateMetadataExchangeEndpointCriteria();
//More members
}

To streamline this sequence, use my DiscoveryFactory.CreateChannel<T> method:

public static class DiscoveryFactory
{
   public static T CreateChannel<T>(Uri scope = null);
   //More members
}

Using CreateChannel<T>, Figure 4 is reduced to:

IMyContract proxy = DiscoveryFactory.CreateChannel<IMyContract>(); 
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

CreateChannel<T> assumes cardinality of one with the MEX endpoint (that is, only a single discoverable MEX endpoint is found in the local network), and that the metadata contains exactly one endpoint whose contract is the specified type parameter T.

Note that CreateChannel<T> uses the MEX endpoint both for the endpoint binding and address. The service is expected to support both a MEX endpoint and a discovery endpoint (although the client never uses the discovery endpoint to find the actual endpoint).

In case there are multiple services supporting the desired service contract, or there are multiple MEX endpoints, DiscoveryFactory also offers the CreateChannels<T> method:

public static class DiscoveryHelper
{
   public static T[] CreateChannels<T>(bool inferBinding = true);
   //More members
}

CreateChannels<T> by default will infer the binding to use from the scheme of the service endpoint. If inferBinding is false, it will discover the binding from the MEX endpoints.

CreateChannels<T> does not assume a cardinality of one on the compatible service endpoints or the MEX endpoints, and will return an array of all compatible endpoints.

Announcements

The discovery mechanism as presented thus far is passive from the perspective of the service. The client queries the discovery endpoint and the service responds. As an alternative to this passive address discovery, WCF offers an active model, where the service broadcasts its status to all clients and provides its address. The service host broadcasts a “hello” announcement when the host is opened and a “bye” announcement when the host shuts down gracefully. If the host is aborted ungracefully, no “bye” announcement is sent. These announcements are received on a special announcements endpoint hosted by the client (see Figure 6).

Figure 6 The Announcement Architecture

image: The Announcement Architecture

Announcements are an individual endpoint-level mechanism, not a host-level one. The host can choose which endpoint to announce. Each announcement contains the endpoint address, its scopes and its contract.

Note that announcements are unrelated to address discovery. The host may not support a discovery endpoint at all, and there is no need for the discovery behavior. On the other hand, the host may chose to both support the discovery endpoint and announce its endpoints, as shown in Figure 6.

The host can automatically announce its endpoints. All you need to do is provide the information about the client announcement endpoint for the discovery behavior. For example, when using a config file:

<behavior>
   <serviceDiscovery>
      <announcementEndpoints>
         <endpoint 
            kind = "udpAnnouncementEndpoint"
         />
      </announcementEndpoints>
   </serviceDiscovery>
</behavior>

My EnableDiscovery extension method also adds the announcement endpoint to the discovery behavior.

For the use of the client, WCF provides a pre-canned implementation of an announcements endpoint with the AnnouncementService class, as shown in Figure 7.

Figure 7 WCF Implementation of an Announcements Endpoint

public class AnnouncementEventArgs : EventArgs
{
   public EndpointDiscoveryMetadata EndpointDiscoveryMetadata
   {get;}
   //More members
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                 ConcurrencyMode = ConcurrencyMode.Multiple)]
public class AnnouncementService : ...
{
   public event EventHandler<AnnouncementEventArgs> OfflineAnnouncementReceived;
   public event EventHandler<AnnouncementEventArgs> OnlineAnnouncementReceived;
   //More members 
}

AnnouncementService is a singleton configured for concurrent access. AnnouncementService provides two event delegates that the client can subscribe to in order to receive the announcements. The client should host the AnnouncementService using the constructor of ServiceHost, which accepts a singleton instance. This is required so that the client is able to interact with the instance and subscribe to the events. In addition, the client must add the UDP announcement endpoint to the host:

AnnouncementService announcementService = new AnnouncementService();
announcementService.OnlineAnnouncementReceived  += OnHello; 
announcementService.OfflineAnnouncementReceived += OnBye;
ServiceHost host = new ServiceHost(announcementService);
host.AddServiceEndpoint(new UdpAnnouncementEndpoint());
host.Open();
void OnHello(object sender,AnnouncementEventArgs args)
{...}   
void OnBye(object sender,AnnouncementEventArgs args)
{...}

There is one important detail related to receiving announcements. The client would receive all notifications of all services in the 
intranet, regardless of contract type or, for that matter, applications or scopes. The client must filter out the relevant announcements.

Streamlining Announcements

You can greatly simplify and improve on the raw steps required of the client to utilize announcements using my AnnouncementSink<T> class defined as:

public class AnnouncementSink<T> : AddressesContainer<T> where T: class
{
   public event Action<T> OnHelloEvent;
   public event Action<T> OnByeEvent;
}

AnnouncementSink<T> automates hosting the announcements endpoint by encapsulating the steps of Figure 7. While AnnouncementSink<T> hosts an instance of AnnouncementService internally, it improves on its deficiencies. First, AnnouncementSink<T> offers two event delegates for notifications. Unlike the raw AnnouncementService, AnnouncementSink<T> fires these delegates concurrently. In addition, AnnouncementSink<T> disables the synchronization context affinity of AnnouncementService, so that it can accept the announcements on any incoming thread, making it truly concurrent.

AnnouncementSink<T> filters the contract types and only fires its events when compatible endpoints announce themselves. The only thing the client needs to do is to open and close AnnouncementSink<T>, in order to indicate when to start and stop receiving notifications.

AnnouncementSink<T> derives my general-purpose address container called AddressesContainer<T>.

AddressesContainer<T> is a rich address-management helper collection that you can use whenever you need to manipulate multiple addresses. AddressesContainer<T> supports several iterators, indexers, conversion methods and queries.

Figure 8 demonstrates using AnnouncementSink<T>.

Figure 8 Using AnnouncementSink<T>

class MyClient : IDisposable
{
   AnnouncementSink<IMyContract> m_AnnouncementSink;
   public MyClient()
   {
      m_AnnouncementSink = new AnnouncementSink<IMyContract>();
      m_AnnouncementSink.OnHelloEvent += OnHello; 
      m_AnnouncementSink.Open();
   }
   void Dispose()
   {
      m_AnnouncementSink.Close();
   }
   void OnHello(string address)
   {     
      EndpointAddress endpointAddress = new EndpointAddress(address);
   IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(
      new NetTcpBinding(),endpointAddress);
      proxy.MyMethod();
      (proxy as ICommunicationObject).Close();
   } 
}

The MEX Explorer

In my book Programming WCF Services Third Edition (O’Reilly, 2008), I presented a tool I call the MEX Explorer (see Figure 9). You can provide a MEX address to the MEX Explorer and use it to reflect the service endpoints (their address, binding properties and contract). The introduction of discovery enabled me to revamp the MEX Explorer.

Figure 9 The MEX Explorer

image: The MEX Explorer

Clicking the Discover button triggers a discovery request for all MEX endpoints without any limit on cardinality. The tool then 
visualizes all discovered endpoints in the tree. In addition, The MEX Explorer utilizes announcements of MEX endpoints. In responding 
to the announcements, the MEX Explorer refreshes itself and 
presents the new endpoints or removes from the tree those that are no longer running.

Discovery-Driven Publish-Subscribe Pattern

In the October 2006 article, What You Need To Know About One-Way Calls, Callbacks and Events, I present my framework for supporting a publish-subscribe pattern in WCF. You can use the mechanisms of discovery and announcements to provide yet another way of implementing a publish-subscribe system.

Unlike the techniques in that article, a discovery-based solution is the only publish-subscribe case that requires no explicit steps by the subscribers or administrator. When utilizing discovery, there is no need to explicitly subscribe either in code or in config. In turn, this significantly simplifies the deployment of the system, and it enables great flexibility in the presence of both publishers and subscribers. You can easily add or remove subscribers and publishers without any additional administration steps or programming.

When taking advantage of discovery for a publish-subscribe system, the subscribers can provide a discovery endpoint so that the publish-subscribe service can discover them, or they can announce their event-handling endpoints, or even do both.

The publishers should not discover the subscribers directly, because that may incur the discovery latency on every event firing (having the cardinality of all endpoints). Instead, the publishers should discover the publish-subscribe service, which is a one-time negligible cost. The publish-subscribe service should be a singleton (enabling fast discovery since it has cardinality of one). The publish-subscribe service exposes the same event endpoint as the subscribers, so it looks like a meta-subscriber to the publishers. That is, it requires the same code to fire the event at the publish-subscribe service as against an actual subscriber.

The events endpoint of the publish-subscribe service must use a particular scope. This scope enables the publishers to find the publish-subscribe service rather than the subscribers. In addition to supporting discovering that scoped events endpoint, the publish-subscribe service provides an announcement endpoint.

The publish-subscribe service maintains a list of all subscribers. The publish-subscribe service can keep that list current by constantly trying to discover the subscribers using some ongoing background activity. Note again that having the publish-subscribe service’s events endpoint associated with a special scope will also prevent the publish-subscribe service from discovering itself when discovering all events endpoints. The publish-subscribe service can also provide an announcement endpoint to monitor subscribers. Figure 10 depicts this architecture.

Figure 10 Discovery-Driven Publish-Subscribe System

image: Discovery-Driven Publish-Subscribe System

The Publish-Subscribe Service

To facilitate deploying your own publish-subscribe service, I wrote the DiscoveryPublishService<T> defined as:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class DiscoveryPublishService<T> : IDisposable where T: class
{
   public static readonly Uri Scope;
   protected void FireEvent(params object[] args);
   //More members
}

All you need to do is to derive your publish-subscribe service from DiscoveryPublishService<T> and specify the events contract as the type parameter. Then implement the operations of the event contract by calling the FireEvent method.

For example, consider this events contract:

[ServiceContract]
interface IMyEvents
{
   [OperationContract(IsOneWay = true)]
   void OnEvent1();
   [OperationContract(IsOneWay = true)]
   void OnEvent2(int number);
}

Figure 11 shows how to implement your publish-subscribe service using DiscoveryPublishService<T>.

Figure 11 Implementing a Publish-Subscribe Service

class MyPublishService : DiscoveryPublishService<IMyEvents>,IMyEvents
{
   public void OnEvent1()
   {
      FireEvent();
   }
   public void OnEvent2(int number)
   {
      FireEvent(number);
   }
}

Internally, DiscoveryPublishService<T> uses another one of my AddressContainer<T> derived classes called DiscoveredServices<T>, defined as:

public class DiscoveredServices<T> : AddressesContainer<T> where T: class
{
   public DiscoveredServices();
   public void Abort();
}

DiscoveredServices<T> is designed to maintain as much as possible an up-to-date list of all discovered services, and it stores the addresses it discovers in its base class. DiscoveredServices<T> spins off an ongoing discovery on a background thread, and it is useful in cases where you want a current repository of discovered addresses.

The FireEvent method extracts from the message headers the operation name. It then queries the subscribers list for all subscribers that do not support the publish-subscribe scope (to avoid self discovery). FireEvent then merges the lists into a union of unique entries (this is required to deal with subscribers that both announce themselves and are discoverable). For each subscriber, FireEvent infers the binding from the address scheme and creates a proxy to fire at the subscriber. Publishing the events is done concurrently using threads from the thread pool.

To host your publish-subscribe service, use the static helper method CreateHost<S> of DiscoveryPublishService<T>:

public class DiscoveryPublishService<T> : IDisposable where T: class
{
   public static ServiceHost<S> CreateHost<S>() 
     where S : DiscoveryPublishService<T>,T;
   //More members
}

The type parameter S is your subclass of DiscoveryPublishService<T>, and T is the events contract. CreateHost<S> returns an instance of a service host you need to open:

ServiceHost host = DiscoveryPublishService<IMyEvents>.
  CreateHost<MyPublishService>();
host.Open();

In addition, CreateHost<S> will also obtain an available TCP base address and add the events endpoint so there is no need for a config file.

The Publisher

The publisher needs a proxy to the events service. For that, use my DiscoveryPublishService<T>.CreateChannel:

public class DiscoveryPublishService<T> : IDisposable where T : class
{
   public static T CreateChannel();
   //More members
}

DiscoveryPublishService<T>.CreateChannel discovers the publish-subscribe service and creates a proxy to it. That discovery is fast since the cardinality is one. The code of the publisher is straightforward:

IMyEvents proxy = DiscoveryPublishService<IMyEvents>.CreateChannel();
proxy.OnEvent1();
(proxy as ICommunicationObject).Close();

When it comes to implementing a subscriber, there is nothing special to do. Simply support the events contract on a service, and add either discovery or announcements (or both) of the events endpoint.


Juval Lowy is a software architect with IDesign and provides WCF training and architecture consulting. His latest book is Programming WCF Services Third Edition (O’Reilly, 2010). He is also the Microsoft regional director for the Silicon Valley. Contact Lowy at www.idesign.net.