Over the past year, I have become an avid viewer of the awful drama series Lost (awful because they show re-runs during the season). In honor of last night’s new episodes, I have written a distributed system that I like to call Lost Chat. It is reminiscent of the chat between Michael and “Walt”…

Lost Chat is comprised of two WCF (the technology formerly known as Indigo) console applications and one contract assembly. These components can be used in a traditional “chatting” scenario (hence the name). From a technical perspective, these components may be interesting since they use duplex communication, contain no configuration files, and interact directly with the Message type. While it may not always be feasible to use the Message type and a purely imperative coding model in production, I have found them to be extremely helpful in understanding WCF architecture. The VS solution containing the code will be available soon, and please forgive the formatting (or lack therof).

Let’s start with the contract. For simplicity, the contract is contained in a separate project and referenced by other Lost Chat components. The contract is simple:

 

// file: Contracts.cs

 

using System;

using System.ServiceModel;

 

// define the contract for the service

[ServiceContract(

    Namespace = "http://wintellect.com/LostChat",

    CallbackContract=typeof(IChatCallback),

    Session=false)]

public interface IChat

{

    [OperationContract(

        Action="urn:ServerChat",

        IsOneWay=true )]

    void SendChat(Message msg);

 

}

 

// define the contract for the callback

public interface IChatCallback

{

    [OperationContract(

        Action="urn:ServerChatCallback",

        IsOneWay=true)]

    void SendCallbackChat(Message msg);

}

Next, let’s build the receiver service (the entity that receives a message in Simplex communication). Remember that we are using the duplex message exchange pattern, so the hard-coded name “receiver” only has context in the first message exchange. Our receiver service has the following implementation:

 

// file: Receiver.cs

 

using System;

using System.Xml;

using System.Text;

using System.ServiceModel;

using System.Diagnostics;

 

class Service {

  public static IChatCallback Callback = null;

 

  static void Main() {

    // define the binding for the service

    WSDualHttpBinding binding =

        new WSDualHttpBinding(WSDualHttpSecurityMode.None);

    // use the Text encoder

    binding.MessageEncoding = WSMessageEncoding.Text;

   

    // define the address for the service

    Uri addressURI = new Uri(@"http://localhost:8000/Chat");

 

    // instantiate a Service host using the MyService type

    using (ServiceHost svc = new ServiceHost(typeof(MyService)))

    {

      // add an endpoint to the service with the address,

      // contract, and binding

      svc.AddServiceEndpoint(typeof(IChat), binding, addressURI);

          

      // open the service to start listening

      svc.Open();

      Console.WriteLine("LOST chat");

      Console.WriteLine("The service is ready");

      Console.WriteLine("4 8 15 16 23 42 to exit");

      Console.Write("You:> ");

      String cmdLine = null;

      while ((cmdLine = Console.ReadLine()) != "4 8 15 16 23 42"){

        if (Callback != null) {

          Callback.SendCallbackChat(GenerateMessage(cmdLine, "urn:ServerChatCallback"));

        }

        else{

          Console.WriteLine("You must wait to be contacted first");

        }       

        Console.Write("You:> ");

      }

    }

  }

 

  static Message GenerateMessage(String chatMessage, String action) {

    XmlDocument doc = new XmlDocument();

   

    // build the XML string

    StringBuilder xmlText = new StringBuilder();

    xmlText.Append("");

    xmlText.Append(chatMessage);

    xmlText.Append("");

 

    doc.LoadXml(xmlText.ToString());

    XmlNodeReader content = new XmlNodeReader(doc.DocumentElement);

 

    return Message.CreateMessage(action, content);

 }

}

 

// implement the service contract

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]

sealed class MyService : IChat {

  public MyService() {

    // get the callback channel

    // interesting sessions / InstanceContextMode behavior

    // will only work for one sender (OK for Lost theme)

    // may be better to put callbacks in collection for

    // multiple senders

    Debugger.Break();

    Service.Callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();

  }

 

  public void SendChat(Message msg) {

    // read the message and write to the console

    StringBuilder sb = new StringBuilder();

    using (XmlWriter xwriter = XmlWriter.Create(sb)) {

      XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(xwriter);

      msg.WriteBodyContents(writer);

    }

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(sb.ToString());

    XmlNodeList data = doc.GetElementsByTagName("ChatMessage");

    Console.WriteLine();

    Console.WriteLine("The Others:> {0}", data[0].InnerText);

    Console.Write("You:> ");

  }

}

 

The sender must connect to the receiver ( notice the use of the ChannelFactory as opposed to a code-gen proxy):

 

// file: Sender.cs

 

using System;

using System.Text;

using System.Xml;

using System.ServiceModel;

using System.Runtime.Serialization;

 

class Program {

 

  static void Main(string[] args) {

 

    Console.WriteLine("LOST chat");

    Console.WriteLine("Start Here - Press ENTER when the chat service is ready");

    Console.WriteLine("4 8 15 16 23 42 to exit");

    Console.ReadLine();

 

    // setup the callback

    InstanceContext inputInstance = new InstanceContext(new CallbackHandler());

   

    // point to the service (where)

    EndpointAddress address = new EndpointAddress(@"http://localhost:8000/Chat");

   

    // define how we will communicate with the service

    // use the pre-defined WS-* compliant HTTP binding

    WSDualHttpBinding binding =

      new WSDualHttpBinding(WSDualHttpSecurityMode.None);

    binding.MessageEncoding = WSMessageEncoding.Text;

       

    // specify a base address for WinXP SP2 operating systems

    binding.ClientBaseAddress = new Uri("http://localhost:9000/Chat");

           

    // create a transport channel

    ChannelFactory<IChat> channel = new ChannelFactory<IChat>(binding, address);

 

    // use the channel to create a proxy

    IChat proxy = channel.CreateDuplexChannel(inputInstance);

 

    String cmdLine = null;

    Message msg = null;

    Console.Write("You:> ");

 

    // loop through sent messages

    while ((cmdLine = Console.ReadLine()) != "4 8 15 16 23 42")

    {

      msg = GenerateMessage(cmdLine, "urn:ServerChat");

      proxy.SendChat(msg);

      Console.Write("You:> ");

    }

  }

 

  static Message GenerateMessage(String chatMessage, String action) {

 

    XmlDocument doc = new XmlDocument();

       

    // build the XML string

    StringBuilder xmlText = new StringBuilder();

    xmlText.Append("");

    xmlText.Append(chatMessage);

    xmlText.Append("");

 

    doc.LoadXml(xmlText.ToString());

    XmlNodeReader content = new XmlNodeReader(doc.DocumentElement);

 

    return Message.CreateMessage(action, content);

  }

}

 

The sender must also implement the callback contract, rather than the service contract:

 

// file: Sender.cs

// Define a class that implements the

// callback contract.

public class CallbackHandler : IChatCallback {

 

  public void SendCallbackChat(Message msg) {

    StringBuilder sb = new StringBuilder();

    using (XmlWriter xwriter = XmlWriter.Create(sb))

    {

      XmlDictionaryWriter writer = XmlDictionaryWriter.CreateDictionaryWriter(xwriter);

      msg.WriteBodyContents(writer);

    }

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(sb.ToString());

    XmlNodeList data = doc.GetElementsByTagName("ChatMessage");

    Console.WriteLine();

    Console.WriteLine("The Others:> {0}", data[0].InnerText);

    Console.Write("You:> ");

  }

}

 

After the initial message is sent from the Sender to the Receiver, both parties are free to communicate at will.