Building a custom Indigo transport binding for dynamic endpoint resolution

After my holidays and my trip to Seattle I am now finally slowly catching up with email and blogs. So, I once again had a little bit of time to have some fun with Indigo.

(Expect more frequent posting from now on. Amongst other things, there has been lots of commentary on description languages for Web Services and SSDL was mentioned. Expect my view on the topic soon. But first things first...)

Let's have some fun with a custom transport for Indigo. No, I am not interested in supporting a specific transport protocol like UDP. This post is not about transports but, rather, about how to dynamically and transparently-to-the-service-logic resolve or rewrite the destination endpoint of messages.

The motivation

When developers write Indigo code to communicate with a service, they have to specify the endpoint URI of that service. In the current bits this URI can be one of 'http', 'https', 'net.tcp', 'net.pipe', and 'net.msmq' (I hope I am not forgetting any). URIs can be used either in configuration or in code. Channels are created so that messages can be sent to a service listening at the supplied endpoint.

The URI can be supplied at runtime through the command line or a dialog box or be hard-coded. However, there might be situations when we want to interact with a service but we don't know its endpoint address at the time we send the message from the service logic. For example, we may want to have an abstract address for a service, like 'uuid:a15e17d0-c982-11d9-8cd5-0800200c9a66' (COM anyone?), and use a registry to dynamically discover the transport-specific endpoint. Alternatively, we may wish to send the SOAP message to a P2P network of nodes. This means that the address will not actually be resolved to a service-specific endpoint until after it has left the boundaries of the sender. Finally, it may be the case that we want to rewrite the supplied endpoint address transparently from the service logic. For example, I plan to use this approach in order to re-implement the service-logic-caching service I originally implemented using WSE.

The idea is that all the above can be done transparently to the service logic implementation. Supporting/enabling such a feature should be a deployment decision.

The implementation

It turns out that dynamically resolving or altering an endpoint is pretty straightforward to do in Indigo. I decided to implement this by building a custom transport binding.

First, let's start with how the sender code looks like.

CustomBinding proxyBinding = new CustomBinding(); proxyBinding.Elements.Add(
   new TextMessageEncodingBindingElement(MessageVersion.Soap12Addressing1,
      System.Text.Encoding.UTF8));
proxyBinding.Elements.Add(new DynamicResolutionTransportBindingElement()); IMyService proxy = ChannelFactory.CreateChannel<IMyService>(
   new Uri("uuid:a15e17d0-c982-11d9-8cd5-0800200c9a66"), proxyBinding);

Instead of using one of the Indigo standard transport bindings, we use the DynamicResolutionTransportBindingElement. Also note that the URI used for the service endpoint is a GUID (I do hope the COM lovers amongst you will appreciate this 🙂

Our custom transport binding element looks like this (I am only including the relevant method):

public class DynamicResolutionTransportBindingElement : BindingElement, IChannelBuilder
{
   public IChannelFactory BuildChannelFactory(ChannelBuildContext context)
   {
      DynamicResolutionChannelFactory factory = new DynamicResolutionChannelFactory();
      factory.MessageEncoderFactory =
         context.UnhandledBindingElements.
            Find<IMessageEncodingBindingElement>(true).CreateMessageEncoderFactory();
      return factory;
   }
}

All we do here is create a new transport-specific channel factory and then make sure that we associate it with the message encoder binding element.

Finally, the implementation of the DynamicResolutionChannelFactory.

class DynamicResolutionChannelFactory : TcpChannelFactory
{
   public DynamicResolutionChannelFactory()   {}    public override bool CanCreateChannel<TChannel>()
   {
      return ((typeof(TChannel) == typeof(IOutputChannel)) ||
              (typeof(TChannel) == typeof(IRequestChannel)));
   }    protected override TChannel OnCreateChannel<TChannel>(EndpointAddress to)
   {
      IChannelFactory factory = null;
      if (to.Uri.Scheme == Uri.UriSchemeHttp)
      {
         factory = new HttpChannelFactory();
      }
      else if (to.Uri.Scheme == "net.tcp")
      {
         factory = new TcpChannelFactory();
      }
      else if (to.Uri.Scheme == "uuid")
      {
         // This is where the resolution takes place. We could contact a registry here
         factory = new HttpChannelFactory();
         to = new EndpointAddress(new Uri("http://localhost:8080/myservice"));
      }
      factory.Open();
      return factory.CreateChannel<TChannel>(to);
   }    public override string Scheme
   {
      get { return null; }
   }    public override MessageVersion MessageVersion
   {
      get { return this.MessageEncoderFactory.MessageVersion; }
   }
}

The OnCreateChannel() method does the trick. The scheme of the endpoint's URI is checked and an appropriate channel is created from the corresponding channel factory. Of course a proper implementation will check for lots of cases there. Note how when the 'uuid' prefix is encountered the message's destination endpoint is changed. In the code above it's always changed to a specific HTTP address. In a proper implementation, a lookup to a registry (e.g., database or a service) would probably be used.

Effectively I have taken control of the creation of the underlying transport channel. When the stack of channels is created, I make sure that the appropriate transport channel is created depending on the supplied address.

That's it!!! Pretty simple and cool, eh?

Conclusion

Implementing a custom transport for outgoing messages to dynamically and transparently-to-the-service-logic change the destination endpoint of messages was very easy. Administrators could configure deployed services to use this approach in order to enable automatic redirection of outgoing messages.

Disclaimer

As always, all my Indigo-related examples come with the disclaimer that they represent my personal investigation into the technology without having access to any documentation. It's all about learning and having fun. I may be following the wrong or inefficient approach to what I am doing. Therefore, always treat these posts with caution 🙂 I hope that Indigo folks will correct me or point me to the right direction if they spot something.