A WCF Chat Service & Client

I attended a interview recently for which the pre-requisite was to develop a chat application using WCF as part of a code challenge. The requirement was quite simple, so instead of taking the usual route of creating a WCF service and adding its reference to a client application, I decided to hand code everything without any configuration or service references!

Of course the  assumption here is that the service and client applications are owned by a single company/team so that the contracts can be shared between the service and client.

The complete code is available in my Github account.

The Problem Statement
Goal - Create a Chat Server and Client application using 
.NET 4.0/4.5 with WCF as the communication mechanism. 
The Chat Server should be a standalone application and 
the Chat Client should be a standalone application with 
a basic UI for users to interact and exchange messages 
through the Chat server. Stability of the code is a 
must-have and it should be ensured that the 
Server/Client do not crash during operation to 
maintain quality user experience. 

The basic functionality to be supported is as follows: 

1. The chat server should open up a TCP port for
 listening to incoming client requests. 

2. The server should be able to handle at 
least 2 distinct clients’ communication in parallel. 

3. On successful connection with the server, 
the chat client should be able to send plain text 
messages to the server. 

4. The server should be able to respond to each 
message by a static message "Hello!" appended 
with the server's timestamp. 

5. Both chat clients should be able to communicate 
with the server without the messages of either being 
visible to the other one. 

The Approach

Since the service contract has to be shared between the service and client the best place to create the service contract is in a common library project. I have created a “contract” folder which has few more sub folders having contracts for service, response, request,  fault and a contract for callback service (more on this later).

image

The Duplex Message Exchange Pattern via Callbacks

IEAChatService.cs is the service contract defined as shown below

 [ServiceContract(
        SessionMode = SessionMode.Required, 
        CallbackContract= typeof(IChatCallback))]
 public interface IEAChatService

I have defined a CallbackContract for this service contract. This callback contract interface is defined in IChatCallback.cs and is implemented by the client application (EA.Client.EAChat.UI).

    [ServiceContract]
    public interface IChatCallback
    {
        [OperationContract(IsOneWay=true)]
        void RefreshUserList(List<User> onlineUsers);

        [OperationContract]
        void ResponseMessage(ResponsePacket response);
    }

This contract is called as a duplex service contract. Duplex service contract is a message exchange pattern where service as well as client can send messages to other independently. Since this interface is implemented by client (EA.Client.EAChat.UI), the service can directly call the client methods. In this case, whenever a new user enters the chat room the Register  method of the EAChatService.cs calls the callback method RefreshUserList on the client, so that client can refresh the user interface and show the name of the newly entered user in the list of users of the chat room. Below is the relevant piece of code.

       public void Register(User userHandle)
        {
            try
            {
						//---irrelevant code removed ---------
                       foreach (KeyValuePair<string, IChatCallback> kvp in registeredUser)
                        {
                            IChatCallback callback = kvp.Value;
                            callback.RefreshUserList(onlineUsers);
                        }
                    }

                }
               // ---irrelevant code removed ---------

            }
            catch (UserHandleInUseException uEx)
            {
                log.Error("User handle is alread in use. Error Detail: " + uEx.Message);
                throw;

            }
            
            
        }
The Service Side Code

EAChatService.cs inherits from ServiceBase.cs. ServiceBase class has some common properties used why the EAChatService class. For instance, the ServiceBase class has a protected getter property  which returns the channel to the client instance which called the operation currently. Below is the code snippet.

      protected IChatCallback Callback
       {
           get
           {
               return OperationContext.Current.GetCallbackChannel<IChatCallback>();
           }
       }

 

ServiceBase class also has a protected dictionary for registeredUsers which stores the userID and the corresponding Callback as a keyvalue pair in the dictionary object. Since multiple users may call the Register method at once, the critical section of adding users in the dictionary is synchronized using a lock. below is the complete code of register method.

       public void Register(User userHandle)
        {
            try
            {
                if (!registeredUser.ContainsKey(userHandle.UserId) &&
                    !registeredUser.ContainsValue(Callback))
                {
                    lock (syncObject)
                    {
                        registeredUser.Add(userHandle.UserId, Callback);
                        onlineUsers.Add(userHandle);

                        foreach (KeyValuePair<string, IChatCallback> kvp in registeredUser)
                        {
                            IChatCallback callback = kvp.Value;
                            callback.RefreshUserList(onlineUsers);
                        }
                    }

                }
                else
                {
                    throw new UserHandleInUseException(ServiceFaultCode.HANDLE_IN_USE, "User with this handle already exist. Please choose a different handle.");
                }
            }
            catch (UserHandleInUseException uEx)
            {
                log.Error("User handle is alread in use. Error Detail: " + uEx.Message);
                throw;

            }
            
            
        }
The Client Code

The client code is implemented in the ChatWindow cliass. The ChatWindow  class has a SynchronizationContext which is used to marshal messages from one thread to another thread. You must be wondering, why do we need to marshal messages from one thread to another. The reason is that the client UI runs on a separate thread and the duplex message from the WCF service is delivered to the client application via another thread. Now the message sent from WCF service as part of duplex call cannot be directly used by the client UI. To enable the message transmission between the UI thread and callback thread, we use the UI thread’s SynchronizationContext and call its Post method to pass the message. I am using Unity container to hold and resolve instances of SynchronizationContext object. Below is the implementation of RefreshUserList callback method which demonstrates the SynchronizationContext usage.

       public void RefreshUserList(List<User> onlineUsers)
        {
            ClearOnlineUserList();

            foreach (User user in onlineUsers)
            {

                SendOrPostCallback callback =
                delegate(object state)
                {
                    this.OnlineUserListBox.Items.Add(user.UserId);
                };

                _uiSyncContext = (SynchronizationContext)container.Resolve(typeof(SynchronizationContext));
                _uiSyncContext.Post(callback, user.UserId);
            }
        }
The WCF endpoint & Hosting

The WCF endpoint is configured programmatically in the EA.Host.ServiceHostEngine project where we have the logic of hosting the WCF service as well.

I have defined an Interface IHostScheme.cs which has the declaration of  Host method and this interface is implemented by NetTcpHost.cs. Since one of the assumptions is that this chat application works in intranet, I choseto use NetTcp binding for the communication. In future if we decide to make to this chat application work in extranet then we can create another class implementing the IHostScheme interface with appropriate binding implementation.

Lets look at the NetTcpScheme.cs class.

    public class NetTcpHost : IHostScheme
    {
        public ServiceHost Host(string portNumber, bool enableMetaData)
        {
            Type contractType = typeof(IEAChatService);
            Type serviceType = typeof(EAChatService);

            Uri baseAddress = GetBaseAddress(portNumber);

            ServiceHost serviceHost = new ServiceHost(serviceType, baseAddress);
            serviceHost.AddServiceEndpoint(contractType, new NetTcpBinding(), baseAddress.AbsoluteUri);

            ServiceMetadataBehavior metaDataBehavior = new ServiceMetadataBehavior();
            metaDataBehavior.HttpGetEnabled = false;
            serviceHost.Description.Behaviors.Add(metaDataBehavior);

            if (enableMetaData)
            {
                serviceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
              MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
            }
            

            return serviceHost;
        }

        private Uri GetBaseAddress(string portNumber)
        {
            string prefix = ConfigurationManager.AppSettings["nettcp_prefix"];
            string chatServicePath = ConfigurationManager.AppSettings["EAChatServicePath"];

            return new Uri(prefix + portNumber + chatServicePath);


        }
    }

The code is pretty much self explanatory. This class is responsible for creating a ServiceHost and a ServiceEndpoint. To create a ServiceHost, we need the type of Service contract and to create a service endpoint which will be hosted by the ServiceHost, we need the contract’s type, address (along with the port) where we will host the service and binding is set to NetTcpBinding because that’s one of the requirement in the problem statement that communication has to take place over tcp port.

I have made HttpGetEnabled property of ServiceMetadataBehavior to false, as I don’t se any benefit of exposing the wsdl over http for our application. However, MEX endpoint is made configurable, so that if in future we wish to create a new client and create a proxy using svcutil for communication, we could do so easily.

That’s it! There are other utility projects and relatively less interesting code in the solution, so I will skip the remaining details.

Feel free to use the code and modify it according to your requirements. The code is available  in my Github account.

If you have any queries, feel free to ask in the comments section.

Advertisements