MSDN Home >  MSDN Library >  Windows Development > 

Digging into Channel Types

Yasser Shohoud

March 10, 2004

Summary: Yasser Shohoud discusses the Indigo channel types and shows you how to build one-way, duplex, request/reply, and reliable messaging applications. (14 printed pages)

Download the associated IndigoLingo2Source.msi sample code.

Introduction

In my last column, I covered the basics of building Indigo applications using the PDC release of Visual Studio® .NET. Once you've learned the basics, you'll probably want to experiment with various MEPs (message exchange patterns). In this installment, I'll dig deeper into the Indigo channel types to show you how to build one-way, duplex, request/reply, and reliable messaging applications.

Ports, Channels, and Messages

Ports, channels, and messages are the three key ingredients of life in the Indigo world. An application comes to life on the network by using an Indigo port, which acts as the gateway between the application and the network. Each port is identified by a unique URI known as the port's identity role. If the application doesn't need its port to be addressed by other applications on the network (e.g., the port is used only to send messages but never to receive messages), such a port may not have an identity role, in which case it is said to be anonymous.

Messages flow from the application to the port (and vice versa) through channels. Depending on how a channel is created, some channels perform message addressing by adding the necessary headers to outgoing messages, so that the application developer doesn't have to do this to each message before sending it. As Figure 1 shows, it's possible to have many channels connecting an application to its port. In fact, each port has by default two channels: a SendChannel and a ReceiveChannel.

Figure 1. Multiple channels connecting an application to its port

Messages are the unit of communication between Indigo applications. The members of an Indigo Message object reflect the XML Infoset of a SOAP 1.2 message. That is, the Message.Headers collection and the Message.Content correspond to SOAP 1.2's headers collection and body, respectively. Each SOAP 1.2 header is represented by a class that inherits from MessageHeader that again exposes the XML Infoset of a SOAP 1.2 header through properties such as Name, Namespace, and Role.

Using the Default Send and Receive Channels

We'll start our exploration of the Indigo family of channels with the default SendChannel. This channel hangs off the SendChannel property of a Port object. This channel itself is not tied to any particular network destination, so you can use it to send messages to any number of arbitrary destinations. To do this, you must create a message and add to it a ToHeader, a ReplyInfoHeader, and a PathHeader. The ToHeader contains the URL the message is destined for. The PathHeader contains a forward and reverse path comprised of an ordered list of SOAP nodes. In the simplest case, PathHeader contains only a forward path with just one URL representing the destination node. The ReplyInfoHeader contains a list of headers that are to be sent back in the reply message. In the simplest case, the ReplyInfoHeader contains the ToHeader to be sent in the reply message.

Listing 1 shows an example of using the default SendChannel. To prepare the message to be sent, you first create a message with the appropriate action and possibly some data, then add to it a ToHeader, a ReplyInfoHeader and a PathHeader. You then create a new Port passing in a URI that becomes the Port's identity role and call Port.Open(). At this point, you're ready to use the Port's SendChannel to send the message.

Listing 1: Using the port's default SendChannel to send a message

public class UsingSendChannel
{
   static Uri action = new Uri(
           "http://tempuri.org/channels/OneWayServiceAction");
   static Uri destination = new Uri(
      String.Format(
         "soap.tcp://{0}:50005/TheClient", 
          System.Environment.MachineName));
   static Uri destination = new Uri(
      String.Format(
         "soap.tcp://{0}:50004/OneWayService", 
          System.Environment.MachineName));

   public void Run()
   {
      Message message = new Message(action, 
                      "A message from OneWayClient");
      ToHeader toHeader = new ToHeader(destination);
      ReplyInfoHeader replyToHeader = new ReplyInfoHeader(myUri);
      PathHeader pathHeader = new PathHeader(destination);

      message.Headers.Add(toHeader);
      message.Headers.Add(replyToHeader);
      message.Headers.Add(pathHeader);
      Port port = new Port(myUri);
      port.Open();
      port.SendChannel.Send(message);
   }
}

Receiving a message is simply a matter of hooking up an implementation of IMessageHandler to the default ReceiveChannel. When a message arrives, the ReceiveChannel will invoke IMessageHandler.ProcessMessage passing it the message object.

As shown in Listing 2, you first open a Port passing in its identity role. You then set Port.ReceiveChannel.Handler to an implementation of IMessageHandler. In this example, OneWayMessageHandler inherits from SyncMessageHandler that is an abstract class that provides the foundation for synchronous message handlers. A typical implementation of IMessageHandler.ProcessMessage will check the received message's Action property to determine how the message should be processed or dispatched. To simplify things, the example in Listing 2 just gets the string data out of the message contents.

Listing 2: Using the port's default ReceiveChannel

public class OneWayService : IExampleService
{
   Port port;
   static Uri myUri = new Uri(
      String.Format(
         "soap.tcp://{0}:50004/OneWayService", 
            System.Environment.MachineName));
   public void Run()
   {
      port = new Port(myUri);
      OneWayMessageHandler handler = 
                           new OneWayMessageHandler(port);
      port.ReceiveChannel.Handler = handler;
      port.Open();

   }
   public void Stop()
   {
      if (port.IsOpen)
         port.Close();
   }
}
public class OneWayMessageHandler: SyncMessageHandler
{
   Port port;
   public OneWayMessageHandler(Port port): base(true)
   {
      this.port = port;
   }
   public override bool ProcessMessage(Message message)
   {
      // Code to check message action has been omitted...

      // Read the message body as a string.
      string text = 
                 (string)message.Content.GetObject(typeof(string));
      return true;
   }
}

Destination-Specific SendChannels

Instead of writing all that code to fill in the various addressing headers, you can use the Port to create a destination-specific SendChannel that takes care of adding those headers for you. To do this, you simply call Port.CreateSendChannel to pass it the destination URL, for example:

Listing 3: Using destination-specific SendChannels

public class OneWayClient : IExampleClient
{
   Port port;
   SendChannel channel;
   static Uri action = new 
                Uri("http://tempuri.org/channels/OneWayServiceAction");
   static Uri destination = new Uri(
                String.Format("soap.tcp://{0}:50004/OneWayService", 
                System.Environment.MachineName));
   public void Run()
   {
      port = new Port();
      port.Open();
      channel = port.CreateSendChannel(destination);
   }
   public void SendMessage()
   {
      string data = "Some data from OneWay client";
      Message message = new Message(action, data);
      channel.Send(message);
   }      
   public void Stop()
   {
      if(port.IsOpen)
         port.Close();
   }
}

Duplex Communication

As explained above, each Indigo application uses at least one port to connect to the world and each port has, by default, a SendChannel and a ReceiveChannel. As a result, every application can expose and/or invoke services. In this egalitarian world, applications can communicate using a variety of MEPs, and the terms "client" and "service" often fail to describe a given application. For example, two Indigo applications can each send messages to the other in a free-flowing duplex communication pattern. In such a configuration, each application is acting in both the traditional "client" and "service" roles.

Listing 4 shows one such application that uses two distinct channels for communicating with its port: A destination-specific SendChannel and the port's default ReceiveChannel. Two such applications can exchange any number of messages in either direction in a duplex MEP.

Listing 4: An example application that sends messages using a destination-specific SendChannel and receives messages using the port's default ReceiveChannel

public class DuplexClient : IExampleClient
{
   Port port;
   SendChannel channel;
   static Uri action = new Uri(
                "http://tempuri.org/channels/duplexserviceaction");
   static Uri myUri = new Uri(String.Format(
                "soap.tcp://{0}:50003/DuplexClient", 
                System.Environment.MachineName));
   static Uri destination = new Uri(String.Format(
                "soap.tcp://{0}:50002/DuplexService", 
                System.Environment.MachineName));
   public void Run()
   {
      port = new Port(myUri);

      DuplexMessageHandler handler = 
                   new DuplexMessageHandler(port);
      port.ReceiveChannel.Handler = handler;
      channel = port.CreateSendChannel(destination);
      port.Open();
   }
   public void SendMessage()
   {
      for (int i = 0; i < 10; i++)
      {
         string data = "Some data from duplex client";
         Message message = new Message(action, data);

         channel.Send(message);
      }
   }
   public void Stop()
   {
      if (port.IsOpen)
         port.Close();
   }
}
public class DuplexMessageHandler: SyncMessageHandler
{
   private Port port;
   public DuplexMessageHandler(Port port): base(true)
   {
      this.port = port;
   }
   public override bool ProcessMessage(Message message)
   {
      // Code to check message action has been omitted...

      // Read the message body as a string.
      string text = 
               (string)message.Content.GetObject(
                       typeof(string));
      return true;
   }
}

Request/Reply Communication

Oftentimes applications want to exchanges messages in a request/reply manner, where one application sends a request message and the other application processes the request and sends back a reply message. Unlike duplex communication, which allows the applications to send any number of messages in each direction, request/reply implies that each request message has exactly one correlated reply message.

To invoke a service using a request/reply MEP, you use a RequestReplyManager to create a SendRequestChannel. Calling SendRequest on this type of channel sends a request message, waits for a reply message to be received, and then returns this reply message. The SendRequestChannel can also retry sending request messages if a reply is not received within a specific timeout. You can set this timeout value when you create the channel. Listing 5 shows the use of RequestReplyManager and SendRequestChannel.

Listing 5: An application using RequestReplyManager for a request/reply MEP

public class RequestReplyClient : IExampleClient
{
   Port port;
   SendRequestChannel channel;
   static Uri action = new Uri(
    "http://tempuri.org/channels/RequestReplyServiceAction"
                          );
   static Uri myUri = new Uri(
             String.Format(
               "soap.tcp://{0}:50006/RequestReplyClient", 
                System.Environment.MachineName));
   static Uri destination = new Uri(
             String.Format(
               "soap.tcp://{0}:50005/RequestReplyService", 
                System.Environment.MachineName));
   public void Run()
   {
      port = new Port(myUri);
      RequestReplyManager RRManager = 
                          new RequestReplyManager(port);
      channel = RRManager.CreateSendRequestChannel(
                          destination, new TimeSpan(0, 0, 10, 0));
      port.Open();
   }
   public void SendMessage()
   {
      string data = "Some data from Request/Reply client";
      Message message = new Message(action, data);
      Message replyMessage=channel.SendRequest(message);
      string reply= (string)
                  replyMessage.Content.GetObject(typeof(string));
   }
   public void Stop()
   {
      if (port.IsOpen)
         port.Close();
   }
}

On the other side of the wire, the receiving application does the usual setup steps of creating a port and hooking up an implementation of IMessageHandler to the port's ReceiveChannel. However, this time the IMessageHandler.ProcessMessage implementation will create a reply message and send it back to the application that sent the request. As Listing 6 shows, by calling CreateReply on the received message, the reply message can be easily created. The resulting reply message contains the necessary addressing headers to take it back to the requestor so it can be sent using the port's default SendChannel.

Listing 6: IMessageHandler implementation for a request/reply MEP

public class RequestReplyMessageHandler: SyncMessageHandler
{
   Port port;
   static Uri echoReplyAction = new Uri(
              "http://tempuri.org/channels/RequestReply");
   public RequestReplyMessageHandler(Port port): base(true)
   {
      this.port = port;
   }
   public override bool ProcessMessage(Message message)
   {

      StatusManager.ShowStatusText(
                     "Request/Reply service received a message");
      // Read the message body as a string.
      string text = (string)
                    message.Content.GetObject(typeof(string));
      StatusManager.ShowStatusText(
                      String.Format("Message content is {0}", text));
      // Create a reply message.
      StatusManager.ShowStatusText(
                      "Request/Reply service sending reply message");
      Message reply = message.CreateReply(echoReplyAction, text);
      this.port.SendChannel.Send(reply);

      return true;
   }

Reliable Communication

In a world where a message may traverse several nodes over a variety of transport protocols en route from source to destination, there's often a need for end-to-end reliable delivery. Indigo's DialogChannel provides reliable delivery guarantees, including "exactly once" ordered delivery. Using a DialogChannel requires that the two communicating applications maintain state across message exchanges in order to keep track of the dialog's state. When communicating, the infrastructure on both sides maintains state regarding the communication and establishes a session. That session can optionally be made durable, such that the state is preserved across application instances. Dialogs allow any number of messages to be sent in any direction. The example below shows a request/reply MEP, but dialogs support other MEPs as well.

Exposing a service using DialogChannel

To expose a service using DialogChannel, you create a DialogManager and wire up its DialogRequested event to a DialogRequestedEventHandler. You then open the port and wait for a dialog request to arrive.

When your DialogRequestedEventHandler is invoked you accept the dialog, which gives you an instance of DialogChannel representing a dialog between your service and the application that requested this dialog. After accepting the dialog, you wire up handlers for the events that you wish to handle and open the channel. You can keep this DialogChannel around and wire up some event handlers for its events. For example, you can wrap the DialogChannel in an object that contains the necessary event handlers and keep this object in a static collection hanging off the service. Alternatively, you could also cast the sender object passed into the event handler for each MessageAvailable, DoneReceiving, Done, and Error events, and use it to invoke the appropriate methods.

The code in Listing 7 takes the first approach in order to provide a ShutdownSession method where it can call DialogChannel.DeleteDialog(). Listing 8 shows the class named Session that contains the DialogChannel's event handlers.

Listing 7: Accepting an incoming dialog request

private void dialogMgr_DialogRequested(object sender, DialogRequestedEventArgs e)
{
   DialogChannel channel;
   
   //message validation omitted ...

   channel = dialogMgr.AcceptDialog(e.InitialMessage);         
   Session session = new Session(channel);

   lock (DialogService.Sessions)
   {
      DialogService.Sessions.Add(session);
   }
} 

Listing 8 shows a handler for the DialogChannel's MessageAvailable event. When this event handler is called, you receive the message by calling DialogChannel.Receive. You can then create a reply message and send it by calling DialogChannel.Reply. Other event handlers are omitted from Listing 8.

Listing 8: A class for handling DialogChannel's events

private class Session
{
   private DialogChannel dialogChannel;

   public Session(DialogChannel channel)
   {
        this.dialogChannel = channel;

        dialogChannel.Done += 
                      new EventHandler(dialogChannel_Done);
        dialogChannel.DoneReceiving += 
                      new EventHandler(dialogChannel_DoneReceiving);
        dialogChannel.Error += 
                      new DialogErrorEventHandler(dialogChannel_Error);
        dialogChannel.MessageAvailable += 
                      new EventHandler(dialogChannel_MessageAvailable);
   }

   private void dialogChannel_MessageAvailable(
                        object sender, EventArgs e)
   {
      try
      {
         Message message = dialogChannel.Receive();
         string data = 
                     (string)message.Content.GetObject(typeof(string));
         string response = String.Format(
                      "Data received from you was '{0}'", data);
         Uri ReplyAction = new System.Uri(
                      "http://tempuri.org/channels/DialogReply");
         Message reply = new Message(ReplyAction, response);
         dialogChannel.Send(reply);
      }
      catch (Exception ex)
      {
         ShutdownSession();
      }
   }
    //code omitted for brevity
}

Invoking a service using DialogChannel

Listing 9 shows an example application to invoke the above service. First, you create a DialogManager to create a DialogChannel specifying the desired delivery assurances. For example, you can choose DialogAssurances.ExactlyOnce, DialogAssurances.InOrder, or DialogAssurances.Full. As in the case of the server, you can send a message by simply calling DialogChannel.Send, and receive messages by wiring up that channel's MessageAvailable event and calling DialogChannel.Receive.

Listing 9. Invoking a service using DialogChannel

public class DialogClient
{
   DialogChannel channel;
   Port port;
   static Uri myUri = new Uri(
             String.Format("soap.tcp://{0}:50002/DialogClient", 
             System.Environment.MachineName));
   static Uri destination = new Uri(
             String.Format("soap.tcp://{0}:50001/DialogService", 
             System.Environment.MachineName));
   static Uri action = new Uri(
             "http://tempuri.org/channels/DialogRequest");
   public void Run()
   {

      port = new Port(myUri);
      DialogManager dlgMgr = new DialogManager(port);
      channel = dlgMgr.CreateDialog(destination, 
                        DialogAssurances.Full);
      channel.Done += new EventHandler(channel_Done);
      channel.Error += new 
                         DialogErrorEventHandler(channel_Error);
      channel.MessageAvailable += new 
                         EventHandler(channel_MessageAvailable);
      port.Open();
   }
public void Stop()
{
   if(port.IsOpen)
      port.Close();
}
public void SendMessage()
{
   string data = "some data from dialog client";
   if(port.IsOpen && (channel.Status != DialogStatus.Faulted && channel.Status != DialogStatus.Terminated))
   {
      Message requestMessage = new Message(action, data);         
      channel.Send(requestMessage);
      channel.DoneSending();
   }
}
}

Summary

Ports, channels, and messages are Indigo's three key abstractions used to connect applications. A service uses one or more channels to send messages through its port to the outside world. Using SendChannel and ReceiveChannel, you can build applications with one-way and duplex MEPs. The SendRequestChannel provides a RequestReply MEP, while the DialogChannel provides stateful, reliable communications with your choice of delivery guarantees.

Resources

Code Name Indigo: A Guide to Developing and Running Connected Systems with Indigo

Longhorn SDK

Longhorn and Visual Studio .NET Whidbey Previews on the PDC website

Indigo Frequently Asked Questions

The Road to Indigo Technology Roadmap

 


Indigo Lingo

Yasser Shohoud is a Program Manager on the Indigo team where he's responsible for Indigo performance and ASP.NET Web services. He's written several Web services articles and a book entitled Real World XML Web Services. In his free time, Yasser enjoys snowboarding in the Cascade Mountains near Seattle.


©2004 Microsoft Corporation. All rights reserved. Terms of Use |Privacy Statement
Microsoft