WCF Chat Application

Introduction

As you all know, Microsoft has launched .NET 3.0 with four very powerful foundations.

  1. WCF (Windows Communication Foundation)
  2. WPF (Windows Presentation Foundation)
  3. WF (Windows Workflow Foundation)
  4. Windows Cardspace

There is already a lot of material available on what WCF, WPF, WF and cardspace are. I am not going in details of their technologies. While exploring WCF, I came across many interesting things that are newly introduced. In my previous chat application using remoting http://www.codeguru.com/Csharp/.NET/net_general/eventsanddelegates/article.php/c10215/, I used an interface that is distributed across the client and server. There was an abstract class that was implemented at the client side. When the server wants to send a message to the client, the server should have the list of clients and something that is common in all clients. An interface implementation was the best way to achieve this. There were serialization, channels, protocols and much more in .NET remoting.

In WCF things, are made pretty simple. The first and the most important thing is that you can have as many endpoints as you want, depending upon the requirements of your application. If you want your application to be used by a .NET client or a Java client, you can go for TCP binding and basic HTTP binding. What you need to do is add those many end points in the configuration file of the server and start the server. It is responsibility of WCF to give you performance benefits of communication with different clients. The basic communication protocol for WCF is SOAP. But, when you establish communication between the WCF service and the WCF client, the foundation uses binary over SOAP to give you optimum performance. I will say that you can have a WCF service with everything under one roof. Currently, I am also in the process of exploring more about WCF and specifically the bindings. There are some features that are required but I do not have any idea about how to get them from netPeerTcpBinding and PeerResolvers.

About the Application

The simple WCF chat application I have written uses netTcpPeerBinding. This binding makes it very easy to create a simple intranet chat application that can be used over an intranet. You do not have to write much of the code and you do not even have to write any special interfaces or classes at the server side. Everything is encapsulated in the following:

  • System.ServiceModel
  • System.ServiceModel.Channels
  • System.ServiceModel.PeerResolvers

Chat Server

This is a very simple chat server with merely four lines of code. As said earlier, you do not need to write any special code. Hence, the server application looks like this.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.PeerResolvers;

namespace ChatServer
{
   public partial class ChatServer : Form
   {
      private CustomPeerResolverService cprs;
      private ServiceHost host;

      public ChatServer()
      {
         InitializeComponent();
         btnStop.Enabled = false;
      }

      private void btnStart_Click(object sender, EventArgs e)
      {
         try
         {
            cprs = new CustomPeerResolverService();
            cprs.RefreshInterval = TimeSpan.FromSeconds(5);
            host = new ServiceHost(cprs);
            cprs.ControlShape = true;
            cprs.Open();
            host.Open(TimeSpan.FromDays(1000000));
            lblMessage.Text = "Server started successfully.";
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.ToString());
         }
         finally
         {
            btnStart.Enabled = false;
            btnStop.Enabled = true;
         }
      }

      private void btnStop_Click(object sender, EventArgs e)
      {
         try
         {
            cprs.Close();
            host.Close();
            lblMessage.Text = "Server stopped successfully.";
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.ToString());
         }
         finally
         {
            btnStart.Enabled = true;
            btnStop.Enabled = false;
         }
      }
   }
}

The code is self explanatory. You create an object of CustomPeerResolverService and pass that object as an input parameter to the ServiceHost class. Open the peer resolver service and host and you are done. No new classes and no end points. Surprising, right? But wait, I have to show you the config file. The config file plays the most important role of specifying the required details.

?xml version="1.0" encoding="utf-8" ?
configuration
   system.serviceModel
      services
         service name="System.ServiceModel.PeerResolvers.
                    CustomPeerResolverService"
            host
               baseAddresses
                  add baseAddress="net.tcp://10.34.34.241/ChatServer"/
               baseAddresses
            host
            endpoint address="net.tcp://10.34.34.241/ChatServer"
                          binding="netTcpBinding"
                 bindingConfiguration="TcpConfig"
                 contract="System.ServiceModel.PeerResolvers.
                           IPeerResolverContract"
            endpoint
         service
      services

      bindings
         netTcpBinding
            binding name="TcpConfig"
                    security mode="None"/security
            binding
         netTcpBinding
      bindings
   system.serviceModel
configuration

I have removed '<' and '>' because I was not able to show config files directly. If you know how to show then, please let me know. The config file is very simple. You use a .NET predefined service in this case: System.ServiceModel.PeerResolvers.CustomPeerResolverService . Give the base address for hosting the resolver service as shown. The important thing about the end point is that it uses a System.ServiceModel.PeerResolvers.IPeerResolverContract contract that is already available in the foundation. As said earlier, you use a TCP end point for the communication. Hence, you specify the end point with TCP binding and configure it with security mode as none.

That's it; you are done. Just start the server and you are ready for your chat client.

WCF Chat Application

Chat Client

As compared to the Chat server, the client becomes a little bit complicated. It does everything on its own. As said earlier, you need something common that the server will use to communicate with clients and that can be established by using interfaces. The same concepts apply here also, and you have client code that looks like this.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace ChatClient
{
   // This is the service contract at client side which uses the
   // same contract as call back contract.
   // Using CallbackContract, server sends message to clients
   [ServiceContract(CallbackContract = typeof(IChatService))]
   public interface IChatService
   {
      // All operation contratcs are one way so that client can
      // fire the message and forget
      // When server responses, client catches it acts accordingly
      [OperationContract(IsOneWay = true)]
      void Join(string memberName);
      [OperationContract(IsOneWay = true)]
      void Leave(string memberName);
      [OperationContract(IsOneWay = true)]
      void SendMessage(string memberName, string message);
   }

   // An interface to create a channel for communication
   public interface IChatChannel : IChatService, IClientChannel
   {
   }

   public partial class ChatClient : Form, IChatService
   {
      // Different delegates that are used internally to raise
      // events when client joins,
      // leaves or sends a message
      private delegate void UserJoined(string name);
      private delegate void UserSendMessage(string name,
                                            string message);
      private delegate void UserLeft(string name);

      // Events are made static because we want to create only once
      // when client joins
      private static event UserJoined NewJoin;
      private static event UserSendMessage MessageSent;
      private static event UserLeft RemoveUser;

      private string userName;
      private IChatChannel channel;
      // As we need to establish duplex coomunication we use
      // DuplexChanelFactory
      private DuplexChannelFactory<IChatChannel> factory;

      public ChatClient()
      {
         InitializeComponent();
         this.AcceptButton = btnLogin;
      }

      public ChatClient(string userName)
      {
         this.userName = userName;
      }

      private void btnLogin_Click(object sender, EventArgs e)
      {
         if (!string.IsNullOrEmpty(txtUserName.Text.Trim()))
         {
            try
            {
               // Register an event
               NewJoin += new UserJoined(ChatClient_NewJoin);
               MessageSent +=
                  new UserSendMessage(ChatClient_MessageSent);
               RemoveUser += new UserLeft(ChatClient_RemoveUser);

               channel = null;
               this.userName = txtUserName.Text.Trim();
               // Create InstanceContext to handle call back interface
               // Pass the object of the CallbackContract implementor
               InstanceContext context = new InstanceContext(
                  new ChatClient(txtUserName.Text.Trim()));
               // We create a participant with the given end point
               // The communication is managed with CHAT MESH and
               // each client creates a duplex 
               // end point with the mesh. Mesh is nothing but the
               // named collection of nodes.
               factory =
                  new DuplexChannelFactory<IChatChannel>(context,
                     "ChatEndPoint");
               channel = factory.CreateChannel();
               channel.Open();
               channel.Join(this.userName);
               grpMessageWindow.Enabled = true;
               grpUserList.Enabled = true;
               grpUserCredentials.Enabled = false;
               this.AcceptButton = btnSend;
               rtbMessages.AppendText
                  ("*****************************WELCOME to Chat
                    Application*****************************\r\n");
               txtSendMessage.Select();
               txtSendMessage.Focus();
            }
            catch (Exception ex)
            {
               MessageBox.Show(ex.ToString());
            }
         }
      }

      void ChatClient_RemoveUser(string name)
      {
         try
         {
            rtbMessages.AppendText("\r\n");
            rtbMessages.AppendText(name + " left at " +
                                   DateTime.Now.ToString());
            lstUsers.Items.Remove(name);
         }
         catch (Exception ex)
         {
            System.Diagnostics.Trace.WriteLine(ex.ToString());
         }
      }

      void ChatClient_MessageSent(string name, string message)
      {
         if (!lstUsers.Items.Contains(name))
         {
            lstUsers.Items.Add(name);
         }
         rtbMessages.AppendText("\r\n");
         rtbMessages.AppendText(name + " says: " + message);
      }

      void ChatClient_NewJoin(string name)
      {
         rtbMessages.AppendText("\r\n");
         rtbMessages.AppendText(name + " joined at:
            [" + DateTime.Now.ToString() + "]");
         lstUsers.Items.Add(name);
      }
       
      #region IChatService Members

      public void Join(string memberName)
      {
         if (NewJoin != null)
         {
            NewJoin(memberName);
         }
      }

      public new void Leave(string memberName)
      {
         if (RemoveUser != null)
         {
            RemoveUser(memberName);
         }
      }

      public void SendMessage(string memberName, string message)
      {
         if (MessageSent != null)
         {
            MessageSent(memberName, message);
         }
      }

      #endregion

      private void btnSend_Click(object sender, EventArgs e)
      {
         channel.SendMessage(this.userName,
                             txtSendMessage.Text.Trim());
         txtSendMessage.Clear();
         txtSendMessage.Select();
         txtSendMessage.Focus();
      }

      private void ChatClient_FormClosing(object sender,
                                          FormClosingEventArgs e)
      {
         try
         {
            if (channel != null)
            {
               channel.Leave(this.userName);
               channel.Close();
            }
            if (factory != null)
            {
               factory.Close();
            }
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.ToString());
         }
      }
   }
}

Again, the client is also very simple. The important thing the config file. Here are the details.

?xml version="1.0" encoding="utf-8" ?
configuration
   system.serviceModel
      client
         endpoint name="ChatEndPoint"
                  address="net.p2p://chatMesh/ChatServer"
                  binding="netPeerTcpBinding"
                  bindingConfiguration="PeerTcpConfig"
                  contract="ChatClient.IChatService"endpoint

      client

      bindings
         netPeerTcpBinding
            binding name="PeerTcpConfig" port="0"
               security mode="None"security
                  resolver mode="Custom"
                     custom address="net.tcp://10.34.34.241/ChatServer"
                            binding="netTcpBinding"
                        bindingConfiguration="TcpConfig"custom
                  resolver
            binding
         netPeerTcpBinding
         netTcpBinding
            binding name="TcpConfig"
               security mode="None"security
            binding
         netTcpBinding
      bindings
   system.serviceModel
configuration

Here in the config file you have endpoint names "ChatEndpoint", which points to a chat mesh and uses netPeerTcpBinding. When you configure the binding, you use the custom resolver mode and give custom TCP address where your actual server is running. Giving the port number as zero will automatically detect the free port for communication. You use security mode as none. Thus, by using this configuration you can start the clients and send messages across the intranet.

Limitations

I did not find a way to send back a list of online users to the newly joined user. I'll appreciate the solution. Also, let me know if you find anything more interesting. I am also into WCF exploring stage and this might not be the perfect chat application. Any suggestions and corrections are welcome.

Reference

I have referred the Microsoft Technology Samples for getting familiar with WCF and working of different bindings.

Conclusion

You can conclude that WCF provides you a very simple but very powerful way of establishing communication between diverse systems, maintaining the performance benefits with similar OS.



About the Author

Jayant Kulkarni

Dear Friends, I'm from Pune and curently working with Symantec Corp. I'm having more than 7 years of exp in software field and have worked on areas like ASP.NET, C#, .NET remoting, web services, pocket pc applciations. I'm a brainbench certified software engineer in .NET framework, C#, ADO.NET and ASP.NET. If you like any of my articles or you want to suggest some changes and improve the way I code, write articles feel free to mail me at: jayantdotnet@gmail.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds