A generic class for implementing protocol handlers in Indigo

In my “Indigo adventures” post I showed how one could write a new channel which can be used to implement a WS-* protocol. After watching the Indigo SDR content, I still think this to be the recommended way of implementing an infrastructure protocol (even though I am still not absolutely certain… Indigo folks may correct me). There are also behaviours available but their use is not meant to be visible to the outside world.

So, if this is indeed the preferred way, wouldn’t it be nice if programmers had a similar experience to implementing protocol handlers like that found in WSE 2.0 where one just has to implement IOutputFilter/IInputFilter? Well, I think so… 🙂

Protocol Handlers for Indigo

I created the following interface :

namespace Savas.Indigo.Utils
{
   public interface IProtocolHandler
   {
      void ProcessMessage(Message msg);
   }
}

Then, all I had to do was to convert the previous code into a generic class, like this (also changed the name of the classes):

namespace Savas.Indigo.Utils
{
   public class ProtocolBindingElement<THandler> : BindingElement, IChannelBuilder
      where THandler : IProtocolHandler
   {
      public override ChannelProtectionRequirements GetProtectionRequirements()
      {
         return null;
      }

      public IChannelFactory BuildChannelFactory(ChannelBuildContext context)
      {
         ProtocolChannelFactory<THandler> factory = new ProtocolChannelFactory<THandler>();

         factory.InnerChannelFactory = context.BuildInnerChannelFactory();

         return factory;
      }

      public IListenerFactory BuildListenerFactory(ChannelBuildContext context)
      {
         throw new Exception("The method or operation is not implemented.");
      }
   }
}

The ProtocolChannelFactory<THandler>:

namespace Savas.Indigo.Utils
{
   internal class ProtocolChannelFactory<THandler> : ChannelFactoryBase
      where THandler : IProtocolHandler
   {
      public override bool CanCreateChannel<TChannel>()
      {
         return (typeof(TChannel) == typeof(IOutputChannel))
                 || this.InnerChannelFactory.CanCreateChannel<TChannel>();
      }

      protected override TChannel OnCreateChannel<TChannel>(EndpointAddress to)
      {
         TChannel channel = this.InnerChannelFactory.CreateChannel<TChannel>(to);
         return (TChannel)(IOutputChannel)
            new ProtocolChannel<THandler>(this, (IOutputChannel)channel);
      }

      public override MessageVersion MessageVersion
      {
         get { return this.InnerChannelFactory.MessageVersion; }
      }

      public override string Scheme
      {
         get { return this.InnerChannelFactory.Scheme; }
      }
   }
}

Finally, the ProtocolChannel<THandler>:

namespace Savas.Indigo.Utils
{
   internal class ProtocolChannel<THandler> : ChannelBase, IOutputChannel
      where THandler : IProtocolHandler
   {
      private IOutputChannel innerChannel = null;

      public ProtocolChannel(ChannelManagerBase manager, IOutputChannel channel)
         : base(manager)
      {
         this.innerChannel = channel;
      }

      protected override void OnAbort()
      {
         this.innerChannel.Abort();
      }

      protected override void OnClose()
      {
         this.innerChannel.Close();
      }

      protected override void OnOpen()
      {
         this.innerChannel.Open();
      }

      public IAsyncResult BeginSend(Message message, AsyncCallback callback, object state)
      {
         return this.innerChannel.BeginSend(message, callback, state);
      }

      public void EndSend(IAsyncResult result)
      {
         this.innerChannel.EndSend(result);
      }

      public EndpointAddress RemoteAddress
      {
         get { return this.innerChannel.RemoteAddress; }
      }

      public event EventHandler RemoteAddressChanged;

      public void Send(Message message)
      {
         // Here we instantiate and call the protocol handler
         THandler handler = Activator.CreateInstance<THandler>();
         handler.ProcessMessage(message);

         this.innerChannel.Send(message);
      }
   }
}

Cool! The above code can now be packaged in a library so it can be reused. But, how do we use it in our code? First, we implement our protocol handlers like the two bellow:

class MyProtocolHandler1 : IProtocolHandler
{
   public void ProcessMessage(Message msg)
   {
      msg.Headers.Add(MessageHeader.CreateHeader("header1", "urn:savas:protocol-1", "hello"));
   }
}

class MyProtocolHandler2 : IProtocolHandler
{
   public void ProcessMessage(Message msg)
   {
      msg.Headers.Add(MessageHeader.CreateHeader("header2", "urn:savas:protocol-2", "hello"));
   }
}

Then, when we create the binding for a proxy, we just have to use the ProtocolBindingElement generic class as bellow (the rest of the test program is the same as the one in my previous post):

CustomBinding customBinding = new CustomBinding();
customBinding.Elements.Add(new ProtocolBindingElement<MyProtocolHandler1>());
customBinding.Elements.Add(new ProtocolBindingElement<MyProtocolHandler2>());
customBinding.Elements.Add(new TextMessageEncodingBindingElement(
   MessageVersion.Soap11Addressing1, System.Text.Encoding.UTF8));
customBinding.Elements.Add(new HttpTransportBindingElement());
IMyService proxy = ChannelFactory.CreateChannel<IMyService>(serviceUri, customBinding);

The resulting headers of the outgoing SOAP messages (using the excellent message logging facilities of Indigo) look like this:

<s:Header>
  <a:Action s:mustUnderstand="1">urn:savas:myoperation</a:Action>
  <header1 xmlns="urn:savas:protocol-1">hello</header1>
  <header2 xmlns="urn:savas:protocol-2">hello</header2>
  <a:To s:mustUnderstand="1">http://localhost:8080/myservice</a:To>
</s:Header>

Now, the Listener part of the protocol channel remains to be implemented and incorporated into the generic class and we are ready to start implementing WS-* protocols 🙂

Recent Posts

Digital Twin (my playground)

I am embarking on a side project that involves memory and multimodal understanding for an…

9 months ago

“This is exactly what LLMs are made for”

I was in Toronto, Canada. I'm on the flight back home now. The trip was…

1 year ago

AI is enhancing me

AI as an enhancer of human abilities.

1 year ago

“How we fell out of love with voice assistants”

The BBC article "How we fell out of love with voice assistants" by Katherine Latham…

2 years ago

Ontology-based reasoning with ChatGPT’s help

Like so many others out there, I played a bit with ChatGPT. I noticed examples…

2 years ago

Break from work

Hi all… It’s been a while since I posted on this blog. It’s been an…

2 years ago