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 :-)

Comments are closed.