October 2010

Volume 25 Number 10

WCF Architecture - Microsoft Azure Service Bus Discovery

By Juval Lowy | October 2010

In my January 2010 article, “Discover a New WCF with Discovery” (msdn.microsoft.com/magazine/ee335779), I presented the valuable discovery facility of Windows Communication Foundation (WCF) 4. WCF Discovery is fundamentally an intranet-oriented technique, as there’s no way to broadcast address information across the Internet.

Yet the benefits of dynamic addresses and decoupling clients and services on the address axis would apply just as well to services that rely on the service bus to receive client calls.

Fortunately, you can use events relay binding to substitute User Datagram Protocol (UDP) multicast requests and provide for discovery and announcements. This enables you to combine the benefit of easy deployment of discoverable services with the unhindered connectivity of the service bus. This article walks through a small framework I wrote to support discovery over the service bus—bringing it on par with the built-in support for discovery in WCF—along with my set of helper classes. It also serves as an example of rolling your own discovery mechanism.

Azure Service Bus Background

If you’re unfamiliar with the Azure Service Bus, you can read these past articles:

Solution Architecture

For the built-in discovery of WCF, there are standard contracts for the discovery exchange. Unfortunately, these contracts are defined as internal. The first step in a custom discovery mechanism is to define the contracts used for discovery request and callbacks. I defined the IServiceBusDiscovery contract as follows: 

[ServiceContract]
public interface IServiceBusDiscovery
{
  [OperationContract(IsOneWay = true)]
  void OnDiscoveryRequest(string contractName,string contractNamespace,
    Uri[] scopesToMatch,Uri replayAddress);
}

The single-operation IServiceBusDiscovery is supported by the discovery endpoint. OnDiscoveryRequest allows the clients to discover service endpoints that support a particular contract, as with regular WCF. The clients can also pass in an optional set of scopes to match.

Services should support the discovery endpoint over the events relay binding. A client fires requests at services that support the discovery endpoint, requesting the services call back to the client’s provided reply address.

 The services call back to the client using the IServiceBusDiscoveryCallback, defined as:

[ServiceContract]
public interface IServiceBusDiscoveryCallback
{
  [OperationContract(IsOneWay = true)]
  void DiscoveryResponse(Uri address,string contractName,
    string contractNamespace, Uri[] scopes);
}

The client provides an endpoint supporting IServiceBusDiscoveryCallback whose address is the replayAddress parameter of OnDiscoveryRequest. The binding used should be the one-way relay binding to approximate unicast as much as possible. Figure 1 depicts the discovery sequence.

image: Discovery over the Service Bus

Figure 1 Discovery over the Service Bus

The first step in Figure 1 is a client firing an event of discovery request at the discovery endpoint supporting IServiceBusDiscovery. Thanks to the events binding, this event is received by all discoverable services. If a service supports the requested contract, it calls back to the client through the service bus (step 2 in Figure 1). Once the client receives the service endpoint (or endpoints) addresses, it proceeds to call the service as with a regular service bus call (step 3 in Figure 1).

Discoverable Host

Obviously a lot of work is involved in supporting such a discovery mechanism, especially for the service. I was able to encapsulate that with my DiscoverableServiceHost, defined as:

public class DiscoverableServiceHost : ServiceHost,...
{ 
  public const string DiscoveryPath = "DiscoveryRequests";
  protected string Namespace {get;}
  public Uri DiscoveryAddress {get;set;}
      public NetEventRelayBinding DiscoveryRequestBinding {get;set;}
      
  public NetOnewayRelayBinding DiscoveryResponseBinding {get;set;}
  public DiscoverableServiceHost(object singletonInstance,
    params Uri[] baseAddresses);
  public DiscoverableServiceHost(Type serviceType,
    params Uri[] baseAddresses);
}

Besides discovery, DiscoverableServiceHost always publishes the service endpoints to the service bus registry. To enable discovery, just as with regular WCF discovery, you must add a discovery behavior and a WCF discovery endpoint. This is deliberate, so as to both avoid adding yet another control switch and to have in one place a single consistent configuration where you turn on or off all modes of discovery.

You use DiscoverableServiceHost like any other service relying on the service bus:

Uri baseAddress = 
  new Uri("sb://MyServiceNamespace.servicebus.windows.net/MyService/");
ServiceHost host = new DiscoverableServiceHost(typeof(MyService),baseAddress);
           
// Address is dynamic
host.AddServiceEndpoint(typeof(IMyContract),new NetTcpRelayBinding(),
  Guid.NewGuid().ToString());
 
// A host extension method to pass creds to behavior
host.SetServiceBusCredentials(...);
host.Open();

Note that when using discovery, the service address can be completely dynamic.

Figure 2provides the partial implementation of pertinent elements of DiscoverableServiceHost.

Figure 2 Implementing DiscoverableServiceHost (Partial)

public class DiscoverableServiceHost : ServiceHost,...
{  
  Uri m_DiscoveryAddress;
  ServiceHost m_DiscoveryHost;
    
  // Extracts the service namespace out of the endpoints or base addresses
  protected string Namespace
  {
    get
    {...}
  }
  bool IsDiscoverable
  {
    get
    {
      if(Description.Behaviors.Find<ServiceDiscoveryBehavior>() != null)
      {
        return Description.Endpoints.Any(endpoint => 
          endpoint is DiscoveryEndpoint);
      }
      return false;
    }
  }
   
  public Uri DiscoveryAddress
  {
    get
    {
      if(m_DiscoveryAddress == null)
      {
        m_DiscoveryAddress =  
          ServiceBusEnvironment.CreateServiceUri("sb",Namespace,DiscoveryPath);
      }
      return m_DiscoveryAddress;
    }
    set
    {
      m_DiscoveryAddress = value;
    }
  }
  public DiscoverableServiceHost(Type serviceType,params Uri[] 
    baseAddresses) : base(serviceType,baseAddresses)
  {}
  void EnableDiscovery()
  {
    // Launch the service to monitor discovery requests
    DiscoveryRequestService discoveryService = 
      new DiscoveryRequestService(Description.Endpoints.ToArray());
    discoveryService.DiscoveryResponseBinding = DiscoveryResponseBinding;
    m_DiscoveryHost = new ServiceHost(discoveryService);
    m_DiscoveryHost.AddServiceEndpoint(typeof(IServiceBusDiscovery),
      DiscoveryRequestBinding, DiscoveryAddress.AbsoluteUri);
     m_DiscoveryHost.Open();
  }
  protected override void OnOpening()
  {
    if(IsDiscoverable)
    {
      EnableDiscovery();
    }
     
    base.OnOpening();
  }
  protected override void OnClosed()
  {
    if(m_DiscoveryHost != null)
    {
      m_DiscoveryHost.Close();
    }
    base.OnClosed();
  }
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,...]
  class DiscoveryRequestService : IServiceBusDiscovery
  {
    public DiscoveryRequestService(ServiceEndpoint[] endpoints);
    public NetOnewayRelayBinding DiscoveryResponseBinding
    {get;set;}
  }   
}

The helper property IsDiscoverable of DiscoverableServiceHost returns true only if the service has a discovery behavior and at least one discovery endpoint. DiscoverableServiceHost overrides the OnOpening method of ServiceHost. If the service is to be discoverable, OnOpening calls the EnableDiscovery method.

EnableDiscovery is the heart of DiscoverableServiceHost. It creates an internal host for a private singleton class called DiscoveryRequestService (see Figure 3).

Figure 3 The DiscoveryRequestService Class (Partial)

public class DiscoverableServiceHost : ServiceHost
{
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
    UseSynchronizationContext = false)]
  class DiscoveryRequestService : IServiceBusDiscovery
  {
    readonly ServiceEndpoint[] Endpoints;
    public NetOnewayRelayBinding DiscoveryResponseBinding
    {get;set;}
    public DiscoveryRequestService(ServiceEndpoint[] endpoints)
    {
      Endpoints = endpoints;
    }
    void IServiceBusDiscovery.OnDiscoveryRequest(string contractName,
      string contractNamespace, Uri[] scopesToMatch, Uri responseAddress)
    {
      ChannelFactory<IServiceBusDiscoveryCallback> factory = 
        new ChannelFactory<IServiceBusDiscoveryCallback>(
        DiscoveryResponseBinding, new EndpointAddress(responseAddress));
      IServiceBusDiscoveryCallback callback = factory.CreateChannel();
      foreach(ServiceEndpoint endpoint in Endpoints)
      {
        if(endpoint.Contract.Name == contractName && 
          endpoint.Contract.Namespace == contractNamespace)
        {
          Uri[] scopes = DiscoveryHelper.LookupScopes(endpoint);
          if(scopesToMatch != null)
          {
            bool scopesMatched = true;
            foreach(Uri scope in scopesToMatch)
            {
              if(scopes.Any(uri => uri.AbsoluteUri == scope.AbsoluteUri) 
                == false)
              {
                scopesMatched = false;
                break;
              }
            }
            if(scopesMatched == false)
            {
              continue;
            }
          }
          callback.DiscoveryResponse(endpoint.Address.Uri,contractName,
            contractNamespace,scopes);
        }
      }
      (callback as ICommunicationObject).Close();
    }
  }   
}
public static class DiscoveryHelper
{
  static Uri[] LookupScopes(ServiceEndpoint endpoint)
  {
    Uri[] scopes = new Uri[]{};
    EndpointDiscoveryBehavior behavior = 
      endpoint.Behaviors.Find<EndpointDiscoveryBehavior>();
    if(behavior != null)
    {
      if(behavior.Scopes.Count > 0)
      {
        scopes = behavior.Scopes.ToArray();
      }
    }
    return scopes;
  }
  // More members
}

The constructor of DiscoveryRequestService accepts the service endpoints for which it needs to monitor discovery requests (these are basically the endpoints of DiscoverableServiceHost).

EnableDiscovery then adds to the host an endpoint implementing IServiceBusDiscovery, because DiscoveryRequestService actually responds to the discovery requests from the clients. The address of the discovery endpoint defaults to the URI “DiscoveryRequests” under the service namespace. However, you can change that before opening DiscoverableServiceHost to any other URI using the DiscoveryAddress property. Closing DiscoverableServiceHost also closes the host for the discovery endpoint. 

Figure 3 lists the implementation of DiscoveryRequestService.

OnDiscoveryRequest first creates a proxy to call back the discovering client. The binding used is a plain NetOnewayRelayBinding, but you can control that by setting the DiscoveryResponseBinding property. Note that DiscoverableServiceHost has a corresponding property just for that purpose. OnDiscoveryRequest then iterates over the collection of endpoints provided to the constructor. For each endpoint, it checks that the contract matches the requested contract in the discovery request. If the contract matches, OnDiscoveryRequest looks up the scopes associated with the endpoint and verifies that those scopes match the optional scopes in the discovery request. Finally, OnDiscoveryRequest calls back the client with the address, contract and scope of the endpoint.

Discovery Client

For the client, I wrote the helper class ServiceBusDiscoveryClient, defined as:

public class ServiceBusDiscoveryClient : ClientBase<IServiceBusDiscovery>,...
{         
  protected Uri ResponseAddress
  {get;}
  public ServiceBusDiscoveryClient(string serviceNamespace,string secret);
  public ServiceBusDiscoveryClient(string endpointName);
  public ServiceBusDiscoveryClient(NetOnewayRelayBinding binding,
    EndpointAddress address);
   public FindResponse Find(FindCriteria criteria);
}

I modeled ServiceBusDiscoveryClient after the WCF DiscoveryClient, and it’s used much the same way, as shown in Figure 4.

Figure 4 Using ServiceBusDiscoveryClient

string serviceNamespace = "...";
string secret = "...";
ServiceBusDiscoveryClient discoveryClient = 
  new ServiceBusDiscoveryClient(serviceNamespace,secret);
         
FindCriteria criteria = new FindCriteria(typeof(IMyContract));
FindResponse discovered = discoveryClient.Find(criteria);
discoveryClient.Close();
        
EndpointAddress address = discovered.Endpoints[0].Address;
Binding binding = new NetTcpRelayBinding();
ChannelFactory<IMyContract> factory = 
  new ChannelFactory<IMyContract> (binding,address);
// A channel factory extension method to pass creds to behavior
factory.SetServiceBusCredentials(secret);
IMyContract proxy = factory.CreateChannel();
proxy.MyMethod();
(proxy as ICommunicationObject).Close();

ServiceBusDiscoveryClient is a proxy for the IServiceBusDiscovery discovery events endpoint. Clients use it to fire the discovery request at the discoverable services. The discovery endpoint address defaults to “DiscoveryRequests,” but you can specify a different address using any of the constructors that take an endpoint name or an endpoint address. It will use a plain instance of NetOnewayRelayBinding for the discovery endpoint, but you can specify a different binding using any of the constructors that take an endpoint name or a binding instance. ServiceBusDiscoveryClient supports cardinality and discovery timeouts, just like DiscoveryClient.

Figure 5shows partial implementation of ServiceBusDiscoveryClient.

Figure 5 Implementing ServiceBusDiscoveryClient (Partial)

public class ServiceBusDiscoveryClient : ClientBase<IServiceBusDiscovery> 
{         
   protected Uri ResponseAddress
   {get;private set;}
   public ServiceBusDiscoveryClient(string endpointName) : base(endpointName)
   {
      string serviceNamespace =  
        ServiceBusHelper.ExtractNamespace(Endpoint.Address.Uri);
      ResponseAddress = ServiceBusEnvironment.CreateServiceUri(
        "sb",serviceNamespace,"DiscoveryResponses/"+Guid.NewGuid());
    }
   public FindResponse Find(FindCriteria criteria)
   {
      string contractName = criteria.ContractTypeNames[0].Name;
      string contractNamespace = criteria.ContractTypeNames[0].Namespace;
      FindResponse response = DiscoveryHelper.CreateFindResponse();
      ManualResetEvent handle = new ManualResetEvent(false);
     Action<Uri,Uri[]> addEndpoint = (address,scopes)=>
     {
       EndpointDiscoveryMetadata metadata = new EndpointDiscoveryMetadata();
       metadata.Address = new EndpointAddress(address);
         if(scopes != null)
         {
           foreach(Uri scope in scopes)
             {
               metadata.Scopes.Add(scope);
             }
         }
         response.Endpoints.Add(metadata);
                                         
         if(response.Endpoints.Count >= criteria.MaxResults)
         {
           handle.Set();
         }
     };
     DiscoveryResponseCallback callback = 
       new DiscoveryResponseCallback(addEndpoint);
     ServiceHost host = new ServiceHost(callback);
        

     host.AddServiceEndpoint(typeof(IServiceBusDiscoveryCallback),
       Endpoint.Binding,ResponseAddress.AbsoluteUri);
     host.Open();
     try
     {         
       DiscoveryRequest(criteria.ContractTypeNames[0].Name,
         criteria.ContractTypeNames[0].Namespace,
         criteria.Scopes.ToArray(),ResponseAddress);
       handle.WaitOne(criteria.Duration);
     }
     catch
     {}
     finally
     {
       host.Abort();
     }
     return response;
   }
   void DiscoveryRequest(string contractName,string contractNamespace,
     Uri[] scopesToMatch,Uri replayAddress)
   {
     Channel.OnDiscoveryRequest(contractName,contractNamespace,
       scopesToMatch, replayAddress);
   }
   [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
     UseSynchronizationContext = false)]
   class DiscoveryResponseCallback : IServiceBusDiscoveryCallback
   {
     readonly Action<Uri,Uri[]> Action;
     public DiscoveryResponseCallback(Action<Uri,Uri[]> action)
     {
       Action = action;
     }
     public void DiscoveryResponse(Uri address,string contractName,
       string contractNamespace,Uri[] scopes)
     {
       Action(address,scopes);
     }
   }
}
public static class DiscoveryHelper
{
  internal static FindResponse CreateFindResponse()
  {
    Type type = typeof(FindResponse);
    ConstructorInfo constructor =  

      type.GetConstructors(BindingFlags.Instance|BindingFlags.NonPublic)[0];
     return constructor.Invoke(null) as FindResponse;
  }  
  // More members
}

The Find method needs to have a way of receiving callbacks from the discovered services. To that end, every time it’s called, Find opens and closes a host for an internal synchronized singleton class called DiscoveryResponseCallback. Find adds to the host an endpoint supporting IServiceBusDiscoveryCallback. The constructor of DiscoveryResponseCallback accepts a delegate of the type Action<Uri,Uri[]>. Every time a service responds back, the implementation of DiscoveryResponse invokes that delegate, providing it with the discovered address and scope. The Find method uses a lambda expression to aggregate the responses in an instance of FindResponse. Unfortunately, there’s no public constructor for FindResponse, so Find uses the CreateFindResponse method of DiscoveryHelper, which in turn uses reflection to instantiate it. Find also creates a waitable event handle. The lambda expression signals that handle when the cardinality is met. After calling DiscoveryRequest, Find waits for the handle to be signaled, or for the discovery duration to expire, and then it aborts the host to stop processing any discovery responses in progress.

More Client-Side Helper Classes

Although I wrote ServiceBusDiscoveryClient to be functionally identical to DiscoveryClient, it would benefit from a streamlined discovery experience offered by my ServiceBusDiscoveryHelper:

public static class ServiceBusDiscoveryHelper
{
  public static EndpointAddress DiscoverAddress<T>(
    string serviceNamespace,string secret,Uri scope = null);
  public static EndpointAddress[] DiscoverAddresses<T>(
    string serviceNamespace,string secret,Uri scope = null);
   
  public static Binding DiscoverBinding<T>(
    string serviceNamespace,string secret,Uri scope = null);
}

DiscoverAddress<T> discovers a service with a cardinality of one, DiscoverAddresses<T> discovers all available service endpoints (cardinality of all) and DiscoverBinding<T> uses the service metadata endpoint to discover the endpoint binding. Much the same way, I defined the class ServiceBusDiscoveryFactory:

public static class ServiceBusDiscoveryFactory
{
  public static T CreateChannel<T>(string serviceNamespace,string secret,
    Uri scope = null) where T : class;
   
  public static T[] CreateChannels<T>(string serviceNamespace,string secret,
    Uri scope = null) where T : class;
}

CreateChannel<T> assumes cardinality of one, and it uses the metadata endpoint to obtain the service’s address and binding used to create the proxy. CreateChannels<T> creates proxies to all discovered services, using all discovered metadata endpoints.

Announcements

To support announcements, you can again use the events relay binding to substitute UDP multicast. First, I defined the IServiceBusAnnouncements announcement contract:

[ServiceContract]
public interface IServiceBusAnnouncements
{
  [OperationContract(IsOneWay = true)]
  void OnHello(Uri address, string contractName,
    string contractNamespace, Uri[] scopes);
  [OperationContract(IsOneWay = true)]
  void OnBye(Uri address, string contractName, 
    string contractNamespace, Uri[] scopes);
}

As shown in Figure 6, this time, it’s up to the clients to expose an event binding endpoint and monitor the announcements.

image: Availability Announcements over the Service Bus

Figure 6 Availability Announcements over the Service Bus

The services will announce their availability (over the one-way relay binding) providing their address (step 1 in Figure 6), and the clients will proceed to invoke them (step 2 in Figure 6).

Service-Side Announcements

My DiscoveryRequestService supports announcements:

public class DiscoverableServiceHost : ServiceHost,...
{
   public const string AnnouncementsPath = "AvailabilityAnnouncements";   
   public Uri AnnouncementsAddress
   {get;set;}
      
   public NetOnewayRelayBinding AnnouncementsBinding
   {get;set;}
  // More members 
}

However, on par with the built-in WCF announcements, by default it won’t announce its availability. To enable announcements, you need to configure an announcement endpoint with the discovery behavior. In most cases, this is all you’ll need to do. DiscoveryRequestService will fire its availability events on the “AvailabilityAnnouncements” URI under the service namespace. You can change that default by setting the AnnouncementsAddress property before opening the host. The events will be fired by default using a plain one-way relay binding, but you can provide an alternative using the AnnouncementsBinding property before opening the host. DiscoveryRequestService will fire its availability events asynchronously to avoid blocking operations during opening and closing of the host. Figure 7 shows the announcement-support elements of DiscoveryRequestService.

Figure 7 Supporting Announcements with DiscoveryRequestService

public class DiscoverableServiceHost : ServiceHost,...
{
   Uri m_AnnouncementsAddress;
   bool IsAnnouncing
   {
      get
      {
         ServiceDiscoveryBehavior behavior = 
           Description.Behaviors.Find<ServiceDiscoveryBehavior>();
         if(behavior != null)
         {
           return behavior.AnnouncementEndpoints.Any();
         }
         return false;
      }
   }
   public Uri AnnouncementsAddress
   {
      get
      {
        if(m_AnnouncementsAddress == null)
        {
          m_AnnouncementsAddress = ServiceBusEnvironment.
            CreateServiceUri("sb",Namespace,AnnouncementsPath);
        }
        return m_AnnouncementsAddress;
     }
     set
     {
       m_AnnouncementsAddress = value;
     }
  }
  IServiceBusAnnouncements CreateAvailabilityAnnouncementsClient()
  {    
    ChannelFactory<IServiceBusAnnouncements> factory = 
      new ChannelFactory<IServiceBusAnnouncements>(
      AnnouncementsBinding,new EndpointAddress(AnnouncementsAddress));
    return factory.CreateChannel();
  }
   protected override void OnOpened()
   {
      base.OnOpened();
      if(IsAnnouncing)
      {
        IServiceBusAnnouncements proxy =  
          CreateAvailabilityAnnouncementsClient();
        PublishAvailabilityEvent(proxy.OnHello);
      }
   }
   protected override void OnClosed()
   {
     if(IsAnnouncing)
     {
       IServiceBusAnnouncements proxy = 
         CreateAvailabilityAnnouncementsClient();
       PublishAvailabilityEvent(proxy.OnBye);
     }
     ...
   }
  void PublishAvailabilityEvent(Action<Uri,string,string,Uri[]> notification)
  {
    foreach(ServiceEndpoint endpoint in Description.Endpoints)
    {
      if(endpoint is DiscoveryEndpoint || endpoint is 
        ServiceMetadataEndpoint)
      {
        continue;
      }
      Uri[] scopes = LookupScopes(endpoint);
      WaitCallback fire = delegate
      {
        try
        {
          notification(endpoint.Address.Uri, endpoint.Contract.Name,
            endpoint.Contract.Namespace, scopes);
          (notification.Target as ICommunicationObject).Close();
        }
        catch
        {}
      };
      ThreadPool.QueueUserWorkItem(fire); 
    }
  }
}

The CreateAvailabilityAnnouncementsClient helper method uses a channel factory to create a proxy to the IServiceBusAnnouncements announcements events endpoint. After opening and before closing DiscoveryRequestService, it fires the notifications. DiscoveryRequestService overrides both the OnOpened and OnClosed methods of ServiceHost. If the host is configured to announce, OnOpened and OnClosed call CreateAvailabilityAnnouncementsClient to create a proxy and pass it to the PublishAvailabilityEvent method to fire the event asynchronously. Because the act of firing the event is identical for both the hello and bye announcements, and the only difference is which method of IServiceBusAnnouncements to call, PublishAvailabilityEvent accepts a delegate for the target method. For each endpoint of DiscoveryRequestService, PublishAvailabilityEvent looks up the scopes associated with that endpoint and queues up the announcement to the Microsoft .NET Framework thread pool using a WaitCallback anonymous method. The anonymous method invokes the provided delegate and then closes the underlying target proxy.

Receiving Announcements

I could have mimicked the WCF-provided AnnouncementService, as described in my January article, but there’s a long list of things I’ve improved upon with my AnnouncementSink<T>, and I didn’t see a case where you would prefer to use AnnouncementService in favor of AnnouncementSink<T>. I also wanted to leverage and reuse the behavior of AnnouncementSink<T> and its base class.

Therefore, for the client, I wrote ServiceBusAnnouncementSink<T>, defined as:

[ServiceBehavior(UseSynchronizationContext = false,
  InstanceContextMode = InstanceContextMode.Single)]
public class ServiceBusAnnouncementSink<T> : AnnouncementSink<T>,
   IServiceBusAnnouncements, where T : class
{
  public ServiceBusAnnouncementSink(string serviceNamespace,string secret);
  public ServiceBusAnnouncementSink(string serviceNamespace,string owner,
    string secret);
  public Uri AnnouncementsAddress get;set;}
  public NetEventRelayBinding AnnouncementsBinding {get;set;}
}

The constructors of ServiceBusAnnouncementSink require the service namespace.

ServiceBusAnnouncementSink supports IServiceBusAnnouncements as a self-hosted singleton. ServiceBusAnnouncementSink also publishes itself to the service bus registry. ServiceBusAnnouncementSink subscribes by default to the availability announcements on the “AvailabilityAnnouncements” URI under the service namespace. You can change that (before opening it) by setting the AnnouncementsAddress property. ServiceBusAnnouncementSink uses (by default) a plain NetEventRelayBinding to receive the notifications, but you can change that by setting the AnnouncementsBinding before opening ServiceBusAnnouncementSink. The clients of ServiceBusAnnouncementSink can subscribe to the delegates of AnnouncementSink to receive the announcements, or they can just access the address in the base address container. For an example, see Figure 8

Figure 8 Receiving Announcements

class MyClient 
{
   AddressesContainer<IMyContract> m_Addresses;
   public MyClient()
   {
      string serviceNamespace = "...";
      string secret = "...";
      m_Addresses = new ServiceBusAnnouncementSink<IMyContract>(
        serviceNamespace,secret);
      m_Addresses.Open();
      ...
   }
   public void OnCallService()
   {  
      EndpointAddress address = m_Addresses[0];
      IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(
         new NetTcpRelayBinding(),address);
      proxy.MyMethod();
      (proxy as ICommunicationObject).Close();
   }
   ...
}

Figure Figure 9shows the partial implementation of ServiceBusAnnouncementSink without some of the error handling.

Figure 9 Implementing ServiceBusAnnouncementSink (Partial)

[ServiceBehavior(UseSynchronizationContext = false,
   InstanceContextMode = InstanceContextMode.Single)]
public class ServiceBusAnnouncementSink<T> : AnnouncementSink<T>,
  IServiceBusAnnouncements
{
   Uri m_AnnouncementsAddress;
   readonly ServiceHost Host;
   readonly string ServiceNamespace;
   readonly string Owner;
   readonly string Secret;
   public ServiceBusAnnouncementSink(string serviceNamespace,
     string owner,string secret)
   {
      Host = new ServiceHost(this);
      ServiceNamespace = serviceNamespace;
      Owner = owner;
      Secret = secret;
   }
   public Uri AnnouncementsAddress
   {
      get
      {
         if(m_AnnouncementsAddress == null)
         {
           m_AnnouncementsAddress = 
             ServiceBusEnvironment.CreateServiceUri(
             "sb",ServiceNamespace,
             DiscoverableServiceHost.AnnouncementsPath);
         }
         return m_AnnouncementsAddress;
      }
      set
      {
        m_AnnouncementsAddress = value;
      }
   }
   public override void Open()
   {
      base.Open();
      Host.AddServiceEndpoint(typeof(IServiceBusAnnouncements),
        AnnouncementsBinding, AnnouncementsAddress.AbsoluteUri);
      Host.SetServiceBusCredentials(Owner,Secret);
      Host.Open();
   }
   public override void Close()
   {
      Host.Close();
      base.Close();
   }
   void IServiceBusAnnouncements.OnHello(Uri address,string contractName,
     string contractNamespace,Uri[] scopes)
   {
      AnnouncementEventArgs args = 
        DiscoveryHelper.CreateAnnouncementArgs(
        address,contractName,contractNamespace,scopes);
      OnHello(this,args); //In the base class AnnouncementSink<T>
   }
   void IServiceBusAnnouncements.OnBye(Uri address,string contractName,
     string contractNamespace,Uri[] scopes)
   {
     AnnouncementEventArgs args = DiscoveryHelper.CreateAnnouncementArgs(
       address,contractName,contractNamespace,scopes);        
     OnBye(this,args); //In the base class AnnouncementSink<T>
   }
}
public static class DiscoveryHelper
{
  static AnnouncementEventArgs CreateAnnouncementArgs(Uri address,
    string contractName, string contractNamespace, Uri[] scopes)
  {
    Type type = typeof(AnnouncementEventArgs);
    ConstructorInfo constructor = 
      type.GetConstructors(BindingFlags.Instance|BindingFlags.NonPublic)[0];
    ContractDescription contract = 
      new ContractDescription(contractName,contractNamespace);
    ServiceEndpoint endpoint = 
      new ServiceEndpoint(contract,null,new EndpointAddress(address));
    EndpointDiscoveryMetadata metadata = 
      EndpointDiscoveryMetadata.FromServiceEndpoint(endpoint);
    return constructor.Invoke(
      new object[]{null,metadata}) as AnnouncementEventArgs;
  }
  // More members
}

The constructor of ServiceBusAnnouncementSink<T> hosts itself as a singleton and saves the service namespace. When you open ServiceBusAnnouncementSink<T>, it adds to its own host an endpoint supporting IServiceBusAnnouncements. The implementation of the event handling methods of IServiceBusAnnouncements creates an AnnouncementEventArgs instance, populating it with the announced service address, contract and scopes, and then calls the base class implementation of the respective announcement methods, as if it was called using 
regular WCF discovery. This both populates the base class of the AddressesContainer<T> and fires the appropriate events of AnnouncementSink<T>. Note that to create an instance of AnnouncementEventArgs, you must use reflection due to the lack of a public constructor.

The Metadata Explorer

Using my support for discovery for the service bus, I extended the discovery feature of the Metadata Explorer tool (presented in previous articles) to support the service bus. If you click the Discover button (see Figure 10), for every service namespace you have already provided credentials for, the Metadata Explorer will try to discover metadata exchange endpoints of discoverable services and display the discovered endpoints in the tree.

image: Configuring Discovery over the Service Bus

Figure 10 Configuring Discovery over the Service Bus

The Metadata Explorer will default to using the URI “DiscoveryRequests” under the service namespace. You can change that path by selecting Service Bus from the menu, then Discovery, to bring up the Configure Azure Service Bus Discovery dialog (see Figure 10).

For each service namespace of interest, the dialog lets you configure the desired relative path of the discovery events endpoint in the Discovery Path text box.

The Metadata Explorer also supports announcements of service bus metadata exchange endpoints. To enable receiving the availability notification, bring up the discovery configuration dialog box and check the Enable checkbox under Availability Announcements. The Metadata Explorer will default to using the “AvailabilityAnnouncements” URI under the specified service namespace, but you can configure for each service namespace any other desired path for the announcements endpoint.

The support in the Metadata Explorer for announcements makes it a simple, practical and useful service bus monitoring tool.


Juval Lowy  is a software architect with IDesign providing .NET and architecture training and consulting. This article contains excerpts from his recent book, “Programming WCF Services 3rd Edition” (O’Reilly, 2010). He’s also the Microsoft regional director for the Silicon Valley. Contact Lowy at idesign.net.

Thanks to the following technical expert for reviewing this article: Wade Wegner