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 🙂
See "BrainExpanded - Introduction" for context on this post. Notes and links Over the years,…
This is the first post, in what I think is going to be a series,…
Back in February, I shared the results of some initial experimentation with a digital twin.…
I am embarking on a side project that involves memory and multimodal understanding for an…
I was in Toronto, Canada. I'm on the flight back home now. The trip was…