Compartilhar via


WCF Extensibility – Channels

This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.

Channels are possibly the most fundamental concept in WCF that (almost) nobody knows about. This is quite a heavy subject, so I’ll break it down in two parts: this post will deal with the concepts for channels and the client part, and the next one will dive deeper into the server part of the channels.

Channels (also known as “protocol channels”) make up the stack over which WCF messages are sent and received. They are called “protocol channels” because they’re usually used to implement some sort of, well, protocol between the communicating parties. Security (WS-Security), reliable messaging (WS-RM), transactions (WS-Transaction), composite duplex (where two HTTP connections are used to implement a duplex pattern over HTTP, which is a request/reply protocol), those are all implemented as channels in WCF. Other examples, such as the Polling Duplex protocol from Silverlight, or the chunking protocol implemented by the Chunking Channel sample are also implemented internally as channels. Channels can do essentially anything with the message: change it, add / remove headers, fiddle with its properties, invoke user code (which I used on the post about extensibility points for Windows Phone / Silverlight 3), even change the message completely. The channel then passes the message to the “inner” channel until it reaches the final destination - for outgoing messages (the request on the client side, the reply on the server), it’s the transport which will send the message over the wire; for incoming messages (request on server, reply on client), it’s the formatter which will unwrap the parameters out of the message to either call the service operation or send the result back to the client code. The diagram below has been drawn in many similar ways and shapes, but it illustrates quite well the path that a request takes from the client to the server (for the response, just invert the direction of the vertical arrows).

Channels

But this diagram doesn’t even tell everything that a channel can do: it doesn’t have to receive one message, play with it and pass it along. It can take one message, then send several of them – for example, a very time-sensitive client can send multiple requests to different services, and take the first response thus reducing the odds it will hit a dead server. It can, even before a client sends a request, to do some communication with the server side – the protocol can do some handshaking when the channel is being opened (and also do some teardown handshaking when the channel is being closed). It can even not send the message to the inner channel – and thus bypass the whole stack returning a message directly to the caller. The same can be done for incoming messages as well – a channel can simply inspect / modify the incoming message and pass it along, it can collect many messages and aggregate them into a single message to pass it along (like in the Chunking Channel sample), or it can simply deal with the message directly without passing it along (in the case of handshaking messages, for example). Channels can also be used where the “normal” extensibility points don’t work, for example, to catch errors in the channels themselves, outside of the service model realm.

One aside about encoders: many diagrams for channels in WCF put them in the same category as the other channels. In fact, they should not be considered as such. On outgoing messages, the last protocol channel in the stack doesn’t hand its message to the encoder for processing – it hands it over to the last channel in the stack, the transport channel. It’s the transport which may use an encoder in the binding context to convert the message into bytes to send over the wire (it doesn’t have to, it can do the encoding itself if the implementation so desires). Similarly for incoming messages, the transport may use the encoder to decode the bytes into a Message object, and then it passes it along to the next protocol channel in the stack. I’ll cover encoders in more details in an upcoming post about them.

Channel Shapes

There are many different shapes (or kinds) of channels in the WCF pipeline. Depending on the message exchanging pattern which is being used, a certain kind of channel needs to be used, so the type of custom channel you need to write will be determined by the pattern as well. The list below shows briefly the types of WCF channels:

  • IOutputChannel: Defines a channel which can only send messages. Useful in one-way communication.
  • IInputChannel: The receiving counterpart of the IOutputChannel, defines a channel which can only receive messages
  • IRequestChannel: Defines a channel which can send a message, and return a response to it. Useful in request/reply protocols such as HTTP
  • IReplyChannel: The receiving counterpart of the IRequestChannel, defines a channel which can receive a request and reply with another message
  • IDuplexChannel: Defines a channel which can both send and receive messages independently. A duplex channel is equivalent to a pair of IInputChannel and IOutputChannel.

In addition to those basic shapes, there are also their session-full equivalent which extend their contract by also implementing ISessionChannel to support session-full communication: IOutputSessionChannel, IInputSessionChannel, IRequestSessionChannel, IReplySessionChannel and IDuplexSessionChannel.

Notice that the channels in the stack don’t need to be all of the same shape: it’s possible that protocol channels are used to modify the shape of the communication as well. For example, the composite duplex channel (used in the WSDualHttpBinding) converts an output (in the sender) and input (in the receiver) channels into a duplex channel (a duplex session channel, actually), by essentially creating a “server” at the client side. The reliable messaging channel, used in WSHttpBinding, adds a session to the HTTP transport (essentially transforming an IRequestChannel into an IRequestSessionChannel at the client, and an IReplyChannel into an IReplySessionChannel at the server). And so on.

So, channels are very powerful, very flexible, and can do a lot of nice features. Why aren’t they used more widely then? First of all, they’re difficult to implement. Even for simple channels which just pass along a message to the inner channel and don’t do anything like shape changing, there is a lot of code which needs to be written (in the example later in this post you’ll see what I’m talking about). Also, channels deal with Message objects. Unless one is very versed in SOAP-speak, chances are they just want to define a service and let WCF (or whichever stack they’re using) take care of the protocols underneath. And start throwing asynchronous code paths which can be used, the implementation of even the simplest of the channels can become complex quickly. So most people (with very good reason), myself included, avoid going down to the channel level unless it’s absolutely necessary.

Public implementations in WCF

None. All channels in WCF (and in most examples I’ve seen) are implemented either as internal or nested (private) classes of the binding elements which add them. Since the channels can only be created following a certain pattern (the binding element creates the channel factory / listener, then the factory / listener creates the appropriate channel), it doesn’t make sense for those classes to be public (maybe with the exception for unit testing purposes)

Interface declarations

The interfaces for the channel shapes have so many methods that I’ll skip this section in this post – follow the links on the channel shapes section for them.

How to add custom channels (client side)

Channels stacks are created using the factory pattern from the binding. So to create a custom channel, one needs to write a binding element, and create a binding containing this element. The binding element class would override CanBuildChannelFactory<TChannel> (to determine whether the channel shape is supported) and BuildChannelFactory<TChannel> to actually return a channel factory which is capable of building the requested channel type. The channel factory implementation needs to override the CreateChannel methods (or OnCreateChannel, if the class inherits from ChannelFactoryBase<TChannel>), besides a lot of boilerplate methods (Open, BeginOpen, EndOpen, Close, etc.) to finally return the channel which will be used in the pipeline. To illustrate the verboseness of all of this, this is I think the simplest code to write a client channel that I can think of (it just passes the message along, and supports only IRequestChannel).

  1. public class MyBindingElement : BindingElement
  2. {
  3.     public override BindingElement Clone()
  4.     {
  5.         return new MyBindingElement();
  6.     }
  7.  
  8.     public override T GetProperty<T>(BindingContext context)
  9.     {
  10.         return context.GetInnerProperty<T>();
  11.     }
  12.  
  13.     public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
  14.     {
  15.         return typeof(TChannel) == typeof(IRequestChannel) &&
  16.             context.CanBuildInnerChannelFactory<TChannel>();
  17.     }
  18.  
  19.     public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
  20.     {
  21.         if (typeof(TChannel) != typeof(IRequestChannel))
  22.         {
  23.             throw new InvalidOperationException("Only IRequestChannel is supported");
  24.         }
  25.  
  26.         var factory = new MyFactory(context.BuildInnerChannelFactory<IRequestChannel>());
  27.         return (IChannelFactory<TChannel>)factory;
  28.     }
  29.  
  30.     class MyFactory : ChannelFactoryBase<IRequestChannel>
  31.     {
  32.         IChannelFactory<IRequestChannel> innerFactory;
  33.         public MyFactory(IChannelFactory<IRequestChannel> innerFactory)
  34.         {
  35.             this.innerFactory = innerFactory;
  36.         }
  37.  
  38.         protected override IRequestChannel OnCreateChannel(EndpointAddress address, Uri via)
  39.         {
  40.             return new MyChannel(this, this.innerFactory.CreateChannel(address, via));
  41.         }
  42.  
  43.         protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
  44.         {
  45.             return this.innerFactory.BeginOpen(timeout, callback, state);
  46.         }
  47.  
  48.         protected override void OnEndOpen(IAsyncResult result)
  49.         {
  50.             this.innerFactory.EndOpen(result);
  51.         }
  52.  
  53.         protected override void OnOpen(TimeSpan timeout)
  54.         {
  55.             this.innerFactory.Open(timeout);
  56.         }
  57.  
  58.         protected override void OnAbort()
  59.         {
  60.             this.innerFactory.Abort();
  61.         }
  62.  
  63.         public override T GetProperty<T>()
  64.         {
  65.             return this.innerFactory.GetProperty<T>();
  66.         }
  67.  
  68.         protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
  69.         {
  70.             return this.innerFactory.BeginClose(timeout, callback, state);
  71.         }
  72.  
  73.         protected override void OnClose(TimeSpan timeout)
  74.         {
  75.             this.innerFactory.Close(timeout);
  76.         }
  77.  
  78.         protected override void OnEndClose(IAsyncResult result)
  79.         {
  80.             this.innerFactory.EndClose(result);
  81.         }
  82.     }
  83.  
  84.     class MyChannel : ChannelBase, IRequestChannel
  85.     {
  86.         IRequestChannel innerChannel;
  87.         public MyChannel(ChannelManagerBase channelManager, IRequestChannel innerChannel)
  88.             : base(channelManager)
  89.         {
  90.             this.innerChannel = innerChannel;
  91.         }
  92.  
  93.         protected override void OnAbort()
  94.         {
  95.             this.innerChannel.Abort();
  96.         }
  97.  
  98.         protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
  99.         {
  100.             return this.innerChannel.BeginClose(timeout, callback, state);
  101.         }
  102.  
  103.         protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
  104.         {
  105.             return this.innerChannel.BeginOpen(timeout, callback, state);
  106.         }
  107.  
  108.         protected override void OnClose(TimeSpan timeout)
  109.         {
  110.             this.innerChannel.Close(timeout);
  111.         }
  112.  
  113.         protected override void OnEndClose(IAsyncResult result)
  114.         {
  115.             this.innerChannel.EndClose(result);
  116.         }
  117.  
  118.         protected override void OnEndOpen(IAsyncResult result)
  119.         {
  120.             this.innerChannel.EndOpen(result);
  121.         }
  122.  
  123.         protected override void OnOpen(TimeSpan timeout)
  124.         {
  125.             this.innerChannel.Open(timeout);
  126.         }
  127.  
  128.         public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
  129.         {
  130.             return this.innerChannel.BeginRequest(message, timeout, callback, state);
  131.         }
  132.  
  133.         public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
  134.         {
  135.             return this.innerChannel.BeginRequest(message, callback, state);
  136.         }
  137.  
  138.         public Message EndRequest(IAsyncResult result)
  139.         {
  140.             return this.innerChannel.EndRequest(result);
  141.         }
  142.  
  143.         public EndpointAddress RemoteAddress
  144.         {
  145.             get { return this.innerChannel.RemoteAddress; }
  146.         }
  147.  
  148.         public Message Request(Message message, TimeSpan timeout)
  149.         {
  150.             return this.innerChannel.Request(message, timeout);
  151.         }
  152.  
  153.         public Message Request(Message message)
  154.         {
  155.             return this.innerChannel.Request(message);
  156.         }
  157.  
  158.         public Uri Via
  159.         {
  160.             get { return this.innerChannel.Via; }
  161.         }
  162.     }
  163. }

As I mentioned before, that’s a lot of code for a channel which does absolutely nothing…

Real world scenario: a Chaos Monkey channel

One very interesting blog post I’ve seen lately was the Netflix team describing their experience using Amazon Web Services for their product, and Jeff Atwood’s post about it in the aftermath of the big outage suffered by AWS. One of the techniques used by Netflix to ensure a good response is what they called the “Chaos Monkey”, whose job is to randomly kill instances and services in their architecture, to ensure that the other parts which depended on them were resilient to errors and normal downtime of the network systems. With that chaos resiliency, Netflix didn’t suffer nearly as much as other companies when AWS stopped working for extended periods of time.

If we are building an application which talks to services, and we want to emulate that technique, we can also periodically disconnect / shut down the services and add guards in our application to deal with those situations. But in many cases we don’t really own the services we’re talking to, so we can’t do exactly that. Enter the “chaos monkey channel”, one implementation of a WCF channel which occasionally throws wrenches in the code by returning a custom messages (normally a fault for SOAP-based services). In this implementation I’m using a “chaos controller” which lets the user define via an interface how much “chaos” they want to introduce into the system.

And before I go further, the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few contracts and it worked, but I cannot guarantee that it will work for all scenarios - it does not work for TCP (duplex) or one way (IInput / IOutput) channels. Please let me know if you find a bug or something missing. A more complete implementation would also include support for more channel types, and possibly provide some helper methods for the user to create known faults (i.e., from the fault contract of the operation) instead of leaving it completely up to the user.

To start off, the interface which allows the user to add “chaos” to the system. I tried to keep it simple, but it could be made more user-friendly so that the user wouldn’t need to deal with Message objects directly, but I opted for simplicity on the channel itself.

  1. public interface IChaosController
  2. {
  3.     bool ShouldFault(ref Message request, out Message fault);
  4. }

Next I had to decide which channel shape to use. Since HTTP is the most common protocol used in communication, I started with an IRequestChannel implementation, which worked fine for a custom binding based on the BasicHttpBinding. Here’s the binding element which can be added.

  1. public class ChaosMonkeyBindingElement : BindingElement
  2. {
  3.     IChaosController controller;
  4.     public ChaosMonkeyBindingElement(IChaosController controller)
  5.     {
  6.         if (controller == null)
  7.         {
  8.             throw new ArgumentNullException("controller");
  9.         }
  10.  
  11.         this.controller = controller;
  12.     }
  13.  
  14.     public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
  15.     {
  16.         if (context == null)
  17.         {
  18.             throw new ArgumentNullException("context");
  19.         }
  20.  
  21.         if (typeof(TChannel) == typeof(IRequestChannel))
  22.         {
  23.             bool innerCanBuild = context.CanBuildInnerChannelFactory<TChannel>();
  24.             return innerCanBuild;
  25.         }
  26.  
  27.         return false;
  28.     }
  29.  
  30.     public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
  31.     {
  32.         if (context == null)
  33.         {
  34.             throw new ArgumentNullException("context");
  35.         }
  36.  
  37.         if (typeof(TChannel) != typeof(IRequestChannel))
  38.         {
  39.             throw new InvalidOperationException("Only IRequestChannel is supported");
  40.         }
  41.  
  42.         var factory = new ChaosMonkeyRequestChannelFactory(context.BuildInnerChannelFactory<IRequestChannel>(), this.controller);
  43.         return (IChannelFactory<TChannel>)factory;
  44.     }
  45.  
  46.     public override BindingElement Clone()
  47.     {
  48.         return new ChaosMonkeyBindingElement(this.controller);
  49.     }
  50.  
  51.     public override T GetProperty<T>(BindingContext context)
  52.     {
  53.         return context.GetInnerProperty<T>();
  54.     }
  55. }

On to the channel factory. Implementing it for this scenario was fairly simple, just with a bunch of boilerplate methods which delegated the call to the factory of the next element in the binding element stack.

  1. class ChaosMonkeyRequestChannelFactory : ChannelFactoryBase<IRequestChannel>
  2. {
  3.     IChannelFactory<IRequestChannel> innerFactory;
  4.     IChaosController controller;
  5.  
  6.     public ChaosMonkeyRequestChannelFactory(IChannelFactory<IRequestChannel> innerFactory, IChaosController controller)
  7.     {
  8.         this.innerFactory = innerFactory;
  9.         this.controller = controller;
  10.     }
  11.  
  12.     protected override IRequestChannel OnCreateChannel(EndpointAddress address, Uri via)
  13.     {
  14.         return new ChaosMonkeyRequestChannel(this, this.innerFactory.CreateChannel(address, via), this.controller);
  15.     }
  16.  
  17.     #region Boilerplate methods which simply delegate to the inner factory
  18.     public override T GetProperty<T>()
  19.     {
  20.         return this.innerFactory.GetProperty<T>();
  21.     }
  22.  
  23.     protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
  24.     {
  25.         return this.innerFactory.BeginOpen(timeout, callback, state);
  26.     }
  27.  
  28.     protected override void OnEndOpen(IAsyncResult result)
  29.     {
  30.         this.innerFactory.EndOpen(result);
  31.     }
  32.  
  33.     protected override void OnOpen(TimeSpan timeout)
  34.     {
  35.         this.innerFactory.Open();
  36.     }
  37.  
  38.     protected override void OnAbort()
  39.     {
  40.         this.innerFactory.Abort();
  41.     }
  42.  
  43.     protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
  44.     {
  45.         return this.innerFactory.BeginClose(timeout, callback, state);
  46.     }
  47.  
  48.     protected override void OnClose(TimeSpan timeout)
  49.     {
  50.         this.innerFactory.Close(timeout);
  51.     }
  52.  
  53.     protected override void OnEndClose(IAsyncResult result)
  54.     {
  55.         this.innerFactory.EndClose(result);
  56.     }
  57.     #endregion
  58. }

Finally the channel itself. To keep the channel simple, I removed the code which dealt with messages and moved it into a separate (private) helper class, so the implementation of the request channel itself would be kept simple.

  1. class ChaosMonkeyRequestChannel : ChannelBase, IRequestChannel
  2. {
  3.     IRequestChannel innerChannel;
  4.     IChaosController controller;
  5.     RequestChannelHelper helper;
  6.  
  7.     public ChaosMonkeyRequestChannel(ChannelManagerBase channelManager, IRequestChannel innerChannel, IChaosController controller)
  8.         : base(channelManager)
  9.     {
  10.         this.innerChannel = innerChannel;
  11.         this.controller = controller;
  12.         this.helper = new RequestChannelHelper(innerChannel, controller);
  13.     }
  14.  
  15.     public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
  16.     {
  17.         return this.helper.BeginRequest(message, timeout, callback, state);
  18.     }
  19.  
  20.     public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
  21.     {
  22.         return this.BeginRequest(message, this.DefaultSendTimeout, callback, state);
  23.     }
  24.  
  25.     public Message EndRequest(IAsyncResult result)
  26.     {
  27.         return this.helper.EndRequest(result);
  28.     }
  29.  
  30.     public Message Request(Message message, TimeSpan timeout)
  31.     {
  32.         return this.helper.Request(message, timeout);
  33.     }
  34.  
  35.     public Message Request(Message message)
  36.     {
  37.         return this.Request(message, this.DefaultSendTimeout);
  38.     }
  39.  
  40.     #region Boilerplate methods which simply delegate to the inner channel
  41.     protected override void OnAbort()
  42.     {
  43.         this.innerChannel.Abort();
  44.     }
  45.  
  46.     protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
  47.     {
  48.         return this.innerChannel.BeginClose(timeout, callback, state);
  49.     }
  50.  
  51.     protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
  52.     {
  53.         return this.innerChannel.BeginOpen(timeout, callback, state);
  54.     }
  55.  
  56.     protected override void OnClose(TimeSpan timeout)
  57.     {
  58.         this.innerChannel.Close(timeout);
  59.     }
  60.  
  61.     protected override void OnEndClose(IAsyncResult result)
  62.     {
  63.         this.innerChannel.EndClose(result);
  64.     }
  65.  
  66.     protected override void OnEndOpen(IAsyncResult result)
  67.     {
  68.         this.innerChannel.EndOpen(result);
  69.     }
  70.  
  71.     protected override void OnOpen(TimeSpan timeout)
  72.     {
  73.         this.innerChannel.Open(timeout);
  74.     }
  75.  
  76.     public override T GetProperty<T>()
  77.     {
  78.         return this.innerChannel.GetProperty<T>();
  79.     }
  80.  
  81.     public EndpointAddress RemoteAddress
  82.     {
  83.         get { return this.innerChannel.RemoteAddress; }
  84.     }
  85.  
  86.     public Uri Via
  87.     {
  88.         get { return this.innerChannel.Via; }
  89.     }
  90.     #endregion
  91. }

The synchronous path of the helper class is fairly straightforward: if the controller says that we should return a fault, we just return the message returned by the chaos controller, and the rest of the stack is bypassed (the request won’t be sent to the server). On the asynchronous case, we need to return an IAsyncResult to the caller, and depending on whether the chaos should happen or not, this asynchronous result will come from different sources. Here I used the same pattern as I described in the post about operation invokers, in which the response is wrapped in a custom IAsyncResult implementation (I’ll omit the class CorrelatingAsyncResult in this post, it can be seen in the code for this post link at the bottom).

  1. class RequestChannelHelper
  2. {
  3.     IRequestChannel innerChannel;
  4.     IChaosController controller;
  5.  
  6.     public RequestChannelHelper(IRequestChannel innerChannel, IChaosController controller)
  7.     {
  8.         this.innerChannel = innerChannel;
  9.         this.controller = controller;
  10.     }
  11.  
  12.     public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
  13.     {
  14.         Message result;
  15.         if (this.controller.ShouldFault(ref message, out result))
  16.         {
  17.             Func<Message> getMessage = () => result;
  18.             CorrelationState correlationState = new CorrelationState
  19.             {
  20.                 OriginalCallback = callback,
  21.                 OriginalAsyncState = state,
  22.                 FaultMessage = result,
  23.             };
  24.             IAsyncResult funcResult = getMessage.BeginInvoke(this.MonkeyRequestCallback, correlationState);
  25.             return new CorrelatingAsyncResult(funcResult, state, correlationState);
  26.         }
  27.         else
  28.         {
  29.             return this.innerChannel.BeginRequest(message, timeout, callback, state);
  30.         }
  31.     }
  32.  
  33.     public Message EndRequest(IAsyncResult result)
  34.     {
  35.         CorrelatingAsyncResult correlatingResult = result as CorrelatingAsyncResult;
  36.         if (correlatingResult != null)
  37.         {
  38.             return ((CorrelationState)correlatingResult.CorrelationData).FaultMessage;
  39.         }
  40.         else
  41.         {
  42.             return this.innerChannel.EndRequest(result);
  43.         }
  44.     }
  45.  
  46.     public Message Request(Message message, TimeSpan timeout)
  47.     {
  48.         Message result;
  49.         if (this.controller.ShouldFault(ref message, out result))
  50.         {
  51.             return result;
  52.         }
  53.         else
  54.         {
  55.             return this.innerChannel.Request(message, timeout);
  56.         }
  57.     }
  58.  
  59.     private void MonkeyRequestCallback(IAsyncResult asyncResult)
  60.     {
  61.         CorrelationState state = (CorrelationState)asyncResult.AsyncState;
  62.         (((System.Runtime.Remoting.Messaging.AsyncResult)asyncResult).AsyncDelegate as Func<Message>).EndInvoke(asyncResult);
  63.         IAsyncResult newAsyncResult = new CorrelatingAsyncResult(asyncResult, state.OriginalAsyncState, state);
  64.         state.OriginalCallback(newAsyncResult);
  65.     }
  66.  
  67.     class CorrelationState
  68.     {
  69.         public AsyncCallback OriginalCallback { get; set; }
  70.         public object OriginalAsyncState { get; set; }
  71.         public Message FaultMessage { get; set; }
  72.     }
  73. }

And that’s it – we can now test it. For that I’ll use a very simple server (a simple Add operation) and controller (which randomly creates problems for 20% of the messages).

  1. [ServiceContract]
  2. public interface ITest
  3. {
  4.     [OperationContract]
  5.     int Add(int x, int y);
  6. }
  7. public class Service : ITest
  8. {
  9.     public int Add(int x, int y)
  10.     {
  11.         Console.WriteLine("[service] {0} + {1}", x, y);
  12.         return x + y;
  13.     }
  14. }
  15. class MyMonkeyController : IChaosController
  16. {
  17.     Random rndGen = new Random();
  18.  
  19.     public bool ShouldFault(ref Message request, out Message fault)
  20.     {
  21.         if (this.rndGen.Next(0, 5) == 0)
  22.         {
  23.             var exception = new FaultException("Chaos!!!", new FaultCode("chaos!"));
  24.             var messageFault = exception.CreateMessageFault();
  25.             fault = Message.CreateMessage(request.Version, messageFault, "Chaos");
  26.             return true;
  27.         }
  28.         else
  29.         {
  30.             fault = null;
  31.             return false;
  32.         }
  33.     }
  34. }

And running the code below, we see that many of the requests to the service throw an exception, just as we wanted. We have chaos!

  1. static void Main(string[] args)
  2. {
  3.     string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
  4.     ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
  5.     BasicHttpBinding basicBinding = new BasicHttpBinding();
  6.     host.AddServiceEndpoint(typeof(ITest), basicBinding, "");
  7.     host.Open();
  8.     Console.WriteLine("Host opened");
  9.  
  10.     CustomBinding binding = new CustomBinding(basicBinding);
  11.     MyMonkeyController chaosController = new MyMonkeyController();
  12.     binding.Elements.Insert(0, new ChaosMonkeyBindingElement(chaosController));
  13.     ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress));
  14.     ITest proxy = factory.CreateChannel();
  15.  
  16.     for (int i = 0; i < 30; i++)
  17.     {
  18.         try
  19.         {
  20.             Console.WriteLine("10 + {0} = {1}", i, proxy.Add(10, i));
  21.         }
  22.         catch (FaultException ex)
  23.         {
  24.             Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
  25.         }
  26.     }
  27. }

Since it worked for a simple HTTP binding, I tried changing it to WSHttpBinding instead of a BasicHttpBinding. And now nothing worked – the channel in the client could not be created! The problem was that while the HTTP transport can only build IRequestChannel instances, WSHttpBinding adds a ReliableMessagingBindingElement to the stack, and it changes the shape to an IRequestSessionChannel. And we have to go back to the binding element to add support for it again… which means writing a new factory (IChannelFactory<IRequestSessionChannel>) and a new ISessionChannel implementation. And here goes a lot more code… This is the new implementations of [Can]BuildChannelFactory<TChannel> on the binding element.

  1. public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
  2. {
  3.     if (context == null)
  4.     {
  5.         throw new ArgumentNullException("context");
  6.     }
  7.  
  8.     if (typeof(TChannel) == typeof(IRequestChannel) || typeof(TChannel) == typeof(IRequestSessionChannel))
  9.     {
  10.         bool innerCanBuild = context.CanBuildInnerChannelFactory<TChannel>();
  11.         return innerCanBuild;
  12.     }
  13.  
  14.     return false;
  15. }
  16.  
  17. public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
  18. {
  19.     if (context == null)
  20.     {
  21.         throw new ArgumentNullException("context");
  22.     }
  23.  
  24.     if (typeof(TChannel) != typeof(IRequestChannel) && typeof(TChannel) != typeof(IRequestSessionChannel))
  25.     {
  26.         throw new InvalidOperationException("Only IRequestChannel / IRequestSessionChannel are supported");
  27.     }
  28.  
  29.     if (typeof(TChannel) == typeof(IRequestChannel))
  30.     {
  31.         var factory = new ChaosMonkeyRequestChannelFactory(context.BuildInnerChannelFactory<IRequestChannel>(), this.controller);
  32.         return (IChannelFactory<TChannel>)factory;
  33.     }
  34.     else
  35.     {
  36.         var factory = new ChaosMonkeyRequestSessionChannelFactory(context.BuildInnerChannelFactory<IRequestSessionChannel>(), this.controller);
  37.         return (IChannelFactory<TChannel>)factory;
  38.     }
  39. }

The implementation of the factory and the channel are really similar to the IRequestChannel. But since we’re passing an inner factory / channel of a certain type, we can’t really reuse much of the code and we end up with a lot of copy/paste code.

Finally, channels are powerful. And I’ve mentioned it before and will do it again – only use it when no other extensibility points can be used for your needs. But if you really need them, hopefully this post will help you a little in understanding them.

Coming up

More channels – this time at the server side (listeners).

[Code for this post]

[Back to the index]

Comments

  • Anonymous
    July 29, 2011
    Hi,Your articles look very nice!! I have a question on the diagram mentioned above. Where does the OperationSelector fit in the diagram?ThanksRajasekhar
  • Anonymous
    July 29, 2011
    The comment has been removed
  • Anonymous
    July 29, 2011
    Thank you very much for the information.It is very usefulRajasekhar
  • Anonymous
    December 19, 2011
    Hi Carlos,Great post. i have a question about the channel stack which will be build for a WsHttpBinding. Like you mentioned in your post the WsHttpBinding contains a reliable messaging channel which will convert a IRequestChannel into an IRequestSessionChannel (client) and an IReplyChannel into an IReplySessionChannel (server). Does this convertion only happen for the reliable messaging channel or will it affect the whole channel stack?Thanks
  • Anonymous
    December 19, 2011
    Hi Dennis. This conversion hapens at RM channel, so anything below it is still an IRequestChannel, but anything above it is now sessionful. If you add a channel "under" the RM channel (which in practice means having its corresponding binding element in a position greater than the RM one), then the shape is still IRequestChannel. For channels "above" it, then they see an IRequestSessionChannel.
  • Anonymous
    December 20, 2011
    Hi Carlos,This makes sense to me. Thanks!
  • Anonymous
    April 09, 2015
    very informative