Chapter 1 Introducing WCF (“Indigo”) Channels

1.1Services, Clients and Endpoints

An Indigo Service is a program that can receive and process, and may also send, Indigo Messages. A Service communicates with the world through Endpoints. A Client is a program that exchanges messages with one or more Endpoints.

Each Endpoint represents a portal at a specific network address that is used to receive and send certain Messages. The set of messages that an Endpoint communicates is defined by a contract. The set of protocols that an Endpoint uses in communicating those messages is defined by a binding.  

Note that an application may simultaneously expose services and act as a client for other services.

1.2Channel Stack

Indigo Endpoints communicate with the world using a communication stack called the channel stack. It’s interesting to compare the channel stack with other communication stacks e.g. TCP/IP (see figure 1).

 

Figure 1: Comparing the Indigo Channel Stack and the TCP stack.

 

First, the similarities: In both cases, each layer of the stack provides some abstraction of the world below this layer and exposed this abstraction to the layer directly above it. Each layer uses the abstraction of the layer directly below it. Also in both cases, when two stacks engage in a communication, each layer communicates with the corresponding layer in the other stack e.g. the IP layer communicates with the IP layer and the TCP layer with the TCP layer etc.

Now, the differences: While the TCP stack was designed to provide an abstraction of the physical network, the channel stack is designed to provide an abstraction of the transport and everything below it. This abstraction is achieved by requiring the bottom channel in the stack to adapt the underlying transport protocol to the channel stack architecture and then relying on channels further up in the stack to provide communication features such as reliability guarantees and security.

Messages flow through the communication stack as Message objects. As shown in figure 1, the bottom channel is called a transport channel. It is the channel responsible for sending/receiving messages to/from other parties. This includes the responsibility of transforming the Message object to and from the format used to communicate with other parties. Above the transport channel there can be any number of protocol channels each responsible for providing a communication function such as reliable delivery guarantees. Protocol channels operate on messages flowing through them in the form of the Message object. They typically either transform the message e.g. by adding headers or encrypting the body, and/or send and receive their own protocol control messages e.g. receipt acknowledgements.

 

1.3Programming with the Channel Stack

Channel stacks are typically created using a factory pattern where a binding creates the channel stack. On the send side, a binding is used to build a ChannelFactory which in turn builds a channel stack and returns a reference to the top channel in the stack. The application can then use this channel to send messages. Similarly, on the receive side a binding is used to build a ChannelListener which listens for incoming messages. The ChannelListener provides messages to the listening application by creating channel stacks and handing the application a reference to the top channel. The application then uses this channel to receive incoming messages. Let’s walk through the code for this process.

1.3.1Receiving messages

The first step in listening for and receiving messages is creating a binding. The code in listing 1 creates an instance of CustomBinding and adds a TcpTransportBindingElement to its Elements collection. This Elements collection contains a binding elements that contribute channels to the channel stack. In this example, because the elements collection has only the TcpTransportBindingElement, the resulting channel stack will have only the TCP transport channel.

After creating a binding, we call BuildChannelListener to build the channel listener. The type T is the type of channel shape you want to create. We’ll discuss channel shapes in detail in chapter 4, for now think of them as fundamental message exchange patterns. In this case, the message exchange pattern we want to support is input (i.e. receive but not send) which corresponds to IInputChannel. BuildChannelListener takes in a BindingParameterCollection which is a way to pass parameters to the channel listener being created. We’ll discuss how to use BindingParameterCollection in detail later in this book.

Once a listener is created, we call SetUri() on it to set the network address on which it will listen. Each transport channel will support one (or possibly several) address schemes. In this example we’re using the TCP transport channel which supports only the address scheme net.tcp. Then we call Open() on the listener and start accepting channels. We now have an endpoint located at net.tco://localhost/channelapp and ready to receive messages. The Open() and Close() methods you see in this code are part of a common state machine that all channels, channel factories and channel listeners implement. This state machine is defined by ICommunicationObject and will be discussed in detail in chapter 2.

The listener returns a channel that implements IInputChannel. To receive messages on this channel we first call Open() on it to open it then we call Receive() which will block until a message arrives.

When Receive() returns a Message, we write out the message’s action and body content (which we assume is a string). We then close the message, the channel and the listener.[1]

 

using System;

using System.ServiceModel;

 

namespace ProgrammingChannels

{

    class Service

    {

        static void RunService()

        {

            CustomBinding binding = new CustomBinding();

            binding.Elements.Add(new TcpTransportBindingElement());

            BindingParameterCollection bpcol = new

                                     BindingParameterCollection();

            using (IChannelListener<IInputChannel> listener =

                    binding.BuildChannelListener<IInputChannel>(bpcol))

            {

                listener.SetUri(

                     new Uri("net.tcp://localhost/channelapp"));

                listener.Open();

                IInputChannel channel = listener.AcceptChannel();

                Console.WriteLine("Listening for messages");

                channel.Open();

                Message message = channel.Receive();

                Console.WriteLine("Message received");

                Console.WriteLine("Message action: {0}",

                                   message.Headers.Action);

                Console.WriteLine("Message content: {0}",

                                   message.GetBody());

                message.Close();

                channel.Close();

                listener.Close();

            }

        }

        public static void Main()

        {

            Service.RunService();

            Console.WriteLine("Press enter to exit");

            Console.ReadLine();

        }

    }

}

 

1.3.2Sending messages

Similarly, sending messages starts by creating a binding and adding a TcpTransportBindingElement to its Elements collection. Instead of creating a channel listener, this time we create a channel factory by calling BuildChannelFactory on the binding. In this case, T is an IOutputChannel since we just want to send messages.

After opening the factory, we call CreateChannel on it to create a channel to the service’s endpoint specifying the same endpoint address we specified before opening the channel listener on the service side.

We then create a new message and call Send on the IOutputChannel to send the message. The TCP transport channel will convert[2] the Message object to a byte array and send that over a TCP connection to the service endpoint. The TCP transport on the service side will receive the bytes from the TCP connection and create a Message object from them then return that message to the service application.

 

using System;

using System.ServiceModel;

 

namespace ProgrammingChannels

{

    class Client

    {

        public static void Main()

        {

            RunClient();

        }

        static void RunClient()

        {

            CustomBinding binding = new CustomBinding();

            binding.Elements.Add(new TcpTransportBindingElement());

            BindingParameterCollection bpcol =

                                new BindingParameterCollection();

            using (IChannelFactory<IOutputChannel> factory =

                    binding.BuildChannelFactory<IOutputChannel>(bpcol))

            {

                factory.Open();

                IOutputChannel channel=factory.CreateChannel(

                       new Uri("net.tcp://localhost/channelapp"));

                channel.Open();

                Message message = Message.CreateMessage(

                            "some action", "This is the body data");

                channel.Send(message);

                channel.Close();

                factory.Close();

            }

        }

    }

}

 

 

                

1.4Summary

The WCF channel stack is a layered communication stack with one or more channels that process messages. At the bottom of the stack is a transport channel that is responsible for adapting the channel stack to the underlying transport (e.g. TCP, HTTP, SMTP etc.). Channels provide a low-level programming model for  sending and receiving messages. This programming model relies on several interfaces and other types collectively known as the WCF Channel Model. The rest of this book explores the Channel Model in detail with a focus on creating custom channels.

 

 



[1] This service is peculiar because it receives only one message. In a real service you’d want to keep accepting channels and receiving messages until the service shuts down.

[2] Transport channels typically use message encoders and decoders to convert between Message objects and byte[] or Stream.