Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

A WCF-WPF Chat Application

4.90/5 (120 votes)
15 Apr 2008CPOL11 min read 24   31.9K  
An Internet chat application with file transfer.

*Edit:

A much more enhanced version with screen casting on my github using WPF Dynamic Modules.

WCF WPF Chat

 

Another (better performance) version using standard Windows Forms with added features.

Screen Cast

Introduction

This application is built on the WCF .NET Framework 3.0. It uses duplex communication and TCP binding (for some reasons that are discussed later in this article). It concentrates on handling and controlling a WCF service that has to make reliable sessions with many clients and keep those connections alive as long as possible.

The application UI is built on WPF .NET Framework 3.0, and here I have to say that I am not good in WPF, and I haven't used animations; I just wanted it to look better, so I chose WPF.

Chat CLient

Chat Client

Try the demo online

.NET 3.5 Framework must be installed.

  • Download the demo (or alternate location)
  • Run it, go to my blog to get an always-updated service IP
  • Choose a name and an avatar, and hit Connect

Before we start, I have to mention two great applications that I have found:

As Sacha explains here, it is a great application (and Sacha is really smart -I love this man-).

The WCF Chat mechanism developed by Nikola and Sacha is a great technique, but it is complex somehow; I wasn't able to understand Sacha's article the first time I read it, that's why I decided to make this simple and step by step.

Note that this article uses client-server pattern and not peer to peer.

In this article, you are going to learn how to:

  • Create a service and configure it to use TCP binding.
  • Host a service and manually control it.
  • Define service contracts, duplex, and data contracts.
  • Implement a reliable session between the service and the client, and keep it alive for a long time.
  • Increase max connections, let's say to 100.
  • Handle the communication state in the client application.
  • Asynchronously call service operations.
  • Enable the service to be accessed online.

The application features include:

  • Connect to the service and disconnect, offline from inside a network or online from the internet.
  • Choose nickname and avatar.
  • Participate in public chat, or whisper in private chat.
  • Know if someone is writing a message.
  • Know if the service stopped or disconnected.
  • Reconnect after faults.
  • File transfer (updated).

Updates

File transfer enabled

Send File

Open File

File Sent

I really recommend these two books in case you like WCF:

Technique

This service is a singleton service; every client starting a session does not instantiate a new instance of the service. This is to enable a single service to deal with many clients. This means that the first client calling the service instantiates a new instance of the service, and each subsequent call is just calling the service operations. Service operations are methods or functions that the service implements; a service represents its operations in an interface, and represents the callback operations in another interface, asking a client to implement those callback operations, to be able to call the client again whenever it may want.

WCF_Service_Client

So, you have got to know:

  • Contracts: Contracts in Windows Communication Foundation provide the interoperability they need to communicate with the client. It is through contracts that clients and services agree as to the types of operations and structures they will use during the period that they are communicating back and forth. Without contracts, no work would be accomplished because no agreement would happen (Wrox Professional WCF Programming). In our case, we define the service operations that we want the service to implement, in an interface, and define the operations that the service wants the client to implement in another interface called the callback interface, this is where they both agree through contracts.
  • EndPoints: The service must define at least one EndPoint and apply binding to it.
  • Bindings: WCF has some predefined bindings that are really useful and can fit many cases. In our case, we are going to use netTcpBinding which allows duplex communications (the red EndPoint in the image above), and mexTcpBinding to support publishing service metadata (the blue EndPoint in the image above).
  • Addresses: Services have to define an address for every EndPoint (we will go through base addresses later), so it can be called from, and here we have three addresses, one on TCP protocol to call the service, one on TCP protocol to publish metadata, and one on HTTP protocol to enable HTTP GET for metadata. And you (the developer) is the one who defines the service address (it doesn't relate to any other names, or paths, you just define a new address like http://localhost/blablabla/myService.svc and it will work).

Step by step, Building a WCF duplex service

Steps in points:

  • Create service assembly:
    • Data contracts
    • Service contract
    • Callback contract
    • Concurrency handling
    • Service implementation
  • Create Host on WPF:
    • ServiceHost class
    • Binding configuration in config file
    • Binding configuration programmatically
    • BaseAddresses
    • Enable metadata configuration in config file
    • Enable metadata configuration programmatically
    • Reliable Sessions
    • Max Connections
  • Create Client on WPF:
    • Generate proxy
    • Add avatars as embedded resources
    • Proxy duplex channel state handling
    • Asynchronously calling service operations
    • Implement callback interface
  • Other stuff:
    • Enable online access (port forward and firewalls)
    • Automatically locating service IP

Create service assembly

Create Service

C#
using System.Linq;
using System.Text;

Create Service

C#
using System;
using System.Collections.Generic;
using System.ServiceModel;

namespace ServiceAssembly
{
    public class ChatService
    {
    }
}
C#
[DataContract]
public class Client
{
    private string _name;
    private int _avatarID;
    private DateTime _time;

    [DataMember]
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    [DataMember]
    public int AvatarID
    {
        get { return _avatarID; }
        set { _avatarID = value; }
    }
    [DataMember]
    public DateTime Time
    {
        get { return _time; }
        set { _time = value; }
    }
}
C#
[DataContract]
public class Message
{
    private string _sender;
    private string _content;
    private DateTime _time;

    [DataMember]
    public string Sender
    {
        get { return _sender; }
        set { _sender = value; }
    }
    [DataMember]
    public string Content
    {
        get { return _content; }
        set { _content = value; }
    }
    [DataMember]
    public DateTime Time
    {
        get { return _time; }
        set { _time = value; }
    }
}
C#
[DataContract]
public class FileMessage
{
    private string sender;
    private string fileName;
    private byte[] data;
    private DateTime time;

    [DataMember]
    public string Sender
    {
        get { return sender; }
        set { sender = value; }
    }

    [DataMember]
    public string FileName
    {
        get { return fileName; }
        set { fileName = value; }
    }

    [DataMember]
    public byte[] Data
    {
        get { return data; }
        set { data = value; }
    }

    [DataMember]
    public DateTime Time
    {
        get { return time; }
        set { time = value; }
    }
}
C#
[ServiceContract(CallbackContract = typeof(IChatCallback),
                         SessionMode = SessionMode.Required)]
public interface IChat
{
    [OperationContract(IsInitiating = true)]
    bool Connect(Client client);

    [OperationContract(IsOneWay = true)]
    void Say(Message msg);

    [OperationContract(IsOneWay = true)]
    void Whisper(Message msg, Client receiver);

    [OperationContract(IsOneWay = true)]
    void IsWriting(Client client);

    [OperationContract(IsOneWay = false)]
    bool SendFile(FileMessage fileMsg, Client receiver);

    [OperationContract(IsOneWay = true, IsTerminating = true)]
    void Disconnect(Client client);
}

For designing a service contract, we need to know how the client is going to interact with the service. Here, we specify that a client has to start a session with the service, and terminate this session by calling some operations that can really start and terminate a session (this is why we set SessionMode = SessionMode.Required). Well, how can a service start a session or terminate it? The answer is, just by setting two properties (IsInitiating or IsTerminating) in the OperationContract attribute, and the WCF runtime will understand this. Operations can be one-way (void); this has an advantage that a client can call the operation and proceed its process without waiting for a reply from the service. In our contract, all operations are one-way except for the Connect operation; it returns a boolean to know if the client has been successfully joined, or the client name has been found and already exists. The last thing is to reference the Callback interface, which is the interface the client will implement: CallbackContract = typeof(IChatCallback).

C#
public interface IChatCallback
{
    [OperationContract(IsOneWay = true)]
    void RefreshClients(List< Client> clients);

    [OperationContract(IsOneWay = true)]
    void Receive(Message msg);

    [OperationContract(IsOneWay = true)]
    void ReceiveWhisper(Message msg, Client receiver);

    [OperationContract(IsOneWay = true)]
    void IsWritingCallback(Client client);

    [OperationContract(IsOneWay = true)]
    void ReceiverFile(FileMessage fileMsg, Client receiver);

    [OperationContract(IsOneWay = true)]
    void UserJoin(Client client);

    [OperationContract(IsOneWay = true)]
    void UserLeave(Client client);
}

The service is responsible for defining these parameters and calling those operations.

Well, this brings us to talk about handling concurrency on the service. A WCF service can handle concurrency in three different ways. The Single and Reentrant options use a synchronized pattern to handle incoming calls from clients, but this can make a deadlock if a client tries to call a service and waits for a reply, the service processes the client request and needs to call the client back again, but the client is still waiting for a reply from the service, and thus causing a deadlock. The Reentrant option will make the WCF release the lock, but we will use the Multiple option which allows to make calls on another thread (this is good, but needs us to synchronize our code as we said before).

C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
    ConcurrencyMode = ConcurrencyMode.Multiple,
    UseSynchronizationContext = false)]
public class ChatService
{
    Dictionary< Client, IChatCallback> clients =
             new Dictionary< Client, IChatCallback>();

    List< Client> clientList = new List< Client>();

    public INewServiceCallback CurrentCallback
    {
        get
        {
         return OperationContext.Current.
                GetCallbackChannel< IChatCallback>();
        }
    }

    object syncObj = new object();

    private bool SearchClientsByName(string name)
    {
        foreach (Client c in clients.Keys)
        {
            if (c.Name == name)
            {
                return true;
            }
        }
        return false;
    }
}
C#
public class ChatService : IChat
{
    ...

    #region IChat Members

    public bool Connect(Client client)
    {
        if (!clients.ContainsValue(CurrentCallback) &&
            !SearchClientsByName(client.Name))
        {
            lock (syncObj)
            {
                clients.Add(client, CurrentCallback);
                clientList.Add(client);

                foreach (Client key in clients.Keys)
                {
                    IChatCallback callback = clients[key];
                    try
                    {
                        callback.RefreshClients(clientList);
                        callback.UserJoin(client);
                    }
                    catch
                    {
                        clients.Remove(key);
                        return false;
                    }

                }

            }
            return true;
        }
        return false;
    }

    public void Say(Message msg)
    {
        lock (syncObj)
        {
            foreach (IChatCallback callback in clients.Values)
            {
                callback.Receive(msg);
            }
        }
    }

    public void Whisper(Message msg, Client receiver)
    {
        foreach (Client rec in clients.Keys)
        {
            if (rec.Name == receiver.Name)
            {
                IChatCallback callback = clients[rec];
                callback.ReceiveWhisper(msg, rec);

                foreach (Client sender in clients.Keys)
                {
                    if (sender.Name == msg.Sender)
                    {
                        IChatCallback senderCallback = clients[sender];
                        senderCallback.ReceiveWhisper(msg, rec);
                        return;
                    }
                }
            }
        }
    }

    public void IsWriting(Client client)
    {
        lock (syncObj)
        {
            foreach (IChatCallback callback in clients.Values)
            {
                callback.IsWritingCallback(client);
            }
        }
    }

    public bool SendFile(FileMessage fileMsg, Client receiver)
    {
        foreach (Client rcvr in clients.Keys)
        {
            if (rcvr.Name == receiver.Name)
            {
                Message msg = new Message();
                msg.Sender = fileMsg.Sender;
                msg.Content = "I'M SENDING FILE.. " + fileMsg.FileName;

                IChatCallback rcvrCallback = clients[rcvr];
                rcvrCallback.ReceiveWhisper(msg, receiver);
                rcvrCallback.ReceiverFile(fileMsg, receiver);

                foreach (Client sender in clients.Keys)
                {
                    if (sender.Name == fileMsg.Sender)
                    {
                        IChatCallback sndrCallback = clients[sender];
                        sndrCallback.ReceiveWhisper(msg, receiver);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public void Disconnect(Client client)
    {
        foreach (Client c in clients.Keys)
        {
            if (client.Name == c.Name)
            {
                lock (syncObj)
                {
                    this.clients.Remove(c);
                    this.clientList.Remove(c);
                    foreach (IChatCallback callback in clients.Values)
                    {
                        callback.RefreshClients(this.clientList);
                        callback.UserLeave(client);
                    }
                }
                return;
            }
        }
    }

    #endregion
}
C#
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace ServiceAssembly
{

    [DataContract]
    public class Client
    {
        private string _name;
        private int _avatarID;
        private DateTime _time;

        [DataMember]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        [DataMember]
        public int AvatarID
        {
            get { return _avatarID; }
            set { _avatarID = value; }
        }
        [DataMember]
        public DateTime Time
        {
            get { return _time; }
            set { _time = value; }
        }
    }

    [DataContract]
    public class Message
    {
        private string _sender;
        private string _content;
        private DateTime _time;

        [DataMember]
        public string Sender
        {
            get { return _sender; }
            set { _sender = value; }
        }
        [DataMember]
        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }
        [DataMember]
        public DateTime Time
        {
            get { return _time; }
            set { _time = value; }
        }
    }



    [ServiceContract(CallbackContract = typeof(IChatCallback), 
                     SessionMode = SessionMode.Required)]
    public interface IChat
    {
        [OperationContract(IsInitiating = true)]
        bool Connect(Client client);

        [OperationContract(IsOneWay = true)]
        void Say(Message msg);

        [OperationContract(IsOneWay = true)]
        void Whisper(Message msg, Client receiver);

        [OperationContract(IsOneWay = true)]
        void IsWriting(Client client);

        [OperationContract(IsOneWay = true, 
                           IsTerminating = true)]
        void Disconnect(Client client);
    }

    public interface IChatCallback
    {
        [OperationContract(IsOneWay = true)]
        void RefreshClients(List< Client> clients);

        [OperationContract(IsOneWay = true)]
        void Receive(Message msg);

        [OperationContract(IsOneWay = true)]
        void ReceiveWhisper(Message msg, Client receiver);

        [OperationContract(IsOneWay = true)]
        void IsWritingCallback(Client client);

        [OperationContract(IsOneWay = true)]
        void UserJoin(Client client);

        [OperationContract(IsOneWay = true)]
        void UserLeave(Client client);
    }


    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                     ConcurrencyMode = ConcurrencyMode.Multiple, 
                     UseSynchronizationContext = false)]
    public class ChatService : IChat
    {
        Dictionary< Client, IChatCallback> clients = 
          new Dictionary< List< Client> 
          clientList = new List< Client>();

        public IChatCallback CurrentCallback
        {
            get
            {
                return OperationContext.Current.
                       GetCallbackChannel< IChatCallback>();

            }
        }

        object syncObj = new object();

        private bool SearchClientsByName(string name)
        {
            foreach (Client c in clients.Keys)
            {
                if (c.Name == name)
                {
                    return true;
                }
            }
            return false;
        }


        #region IChat Members

        public bool Connect(Client client)
        {
            if (!clients.ContainsValue(CurrentCallback) && 
                !SearchClientsByName(client.Name))
            {
                lock (syncObj)
                {
                    clients.Add(client, CurrentCallback);
                    clientList.Add(client);

                    foreach (Client key in clients.Keys)
                    {
                        IChatCallback callback = clients[key];
                        try
                        {
                            callback.RefreshClients(clientList);
                            callback.UserJoin(client);
                        }
                        catch
                        {
                            clients.Remove(key);
                            return false;
                        }

                    }

                }
                return true;
            }
            return false;
        }

        public void Say(Message msg)
        {
            lock (syncObj)
            {
                foreach (IChatCallback callback in clients.Values)
                {
                    callback.Receive(msg);
                }
            }
        }

        public void Whisper(Message msg, Client receiver)
        {
            foreach (Client rec in clients.Keys)
            {
                if (rec.Name == receiver.Name)
                {
                    IChatCallback callback = clients[rec];
                    callback.ReceiveWhisper(msg, rec);

                    foreach (Client sender in clients.Keys)
                    {
                        if (sender.Name == msg.Sender)
                        {
                            IChatCallback senderCallback = clients[sender];
                            senderCallback.ReceiveWhisper(msg, rec);
                            return;
                        }
                    }
                }
            }
        }

        public void IsWriting(Client client)
        {
            lock (syncObj)
            {
                foreach (IChatCallback callback in clients.Values)
                {
                    callback.IsWritingCallback(client);
                }
            }
        }

        public bool SendFile(FileMessage fileMsg, Client receiver)
        {
            foreach (Client rcvr in clients.Keys)
            {
                if (rcvr.Name == receiver.Name)
                {
                    Message msg = new Message();
                    msg.Sender = fileMsg.Sender;
                    msg.Content = "I'M SENDING FILE.. " + fileMsg.FileName;

                    IChatCallback rcvrCallback = clients[rcvr];
                    rcvrCallback.ReceiveWhisper(msg, receiver);
                    rcvrCallback.ReceiverFile(fileMsg, receiver);

                    foreach (Client sender in clients.Keys)
                    {
                        if (sender.Name == fileMsg.Sender)
                        {
                            IChatCallback sndrCallback = clients[sender];
                            sndrCallback.ReceiveWhisper(msg, receiver);
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        
        public void Disconnect(Client client)
        {
            foreach (Client c in clients.Keys)
            {
                if (client.Name == c.Name)
                {
                    lock (syncObj)
                    {
                        this.clients.Remove(c);
                        this.clientList.Remove(c);
                        foreach (IChatCallback callback in clients.Values)
                        {
                            callback.RefreshClients(this.clientList);
                            callback.UserLeave(client);
                        }
                    }
                    return;
                }
            }
        }

        #endregion
    }
}
  • Create a new folder in the C: directory and name it WCFWPFRoot.
  • Fire up Visual Studio, File > New > Project, choose C# Class Library.
  • Set the solution name to WCFWPFApp and the project name to ServiceAssembly; the location is the WCFWPFRoot folder.
  • Rename Class1.cs to ChatService.cs.
  • Remove:
  • Right click the project name, choose Add Reference.. , and add a reference for System.ServiceModel.
  • Add this line to your code: using System.ServiceModel;.
  • ServiceChat.cs should look like this:
  • Now, we will start to add some interfaces and other classes beside this class (ChatService) in the ServiceAssembly namespace. The first class we will add is a Data Contract; a data contract means an agreement to transfer this type of data.
  • To add a Data Contract, add a reference to System.Runtime.Serialization, and so add this line: using System.Runtime.Serialization;.
  • Our data will be of two types, the first one represents a client and consists of name, avatar ID, and time; so we design the client to be like this:
  • The second Data Contract is for the message. We want clients to send messages to each other; so Message will have information about the sender, content, and time:
  • The next Data Contract is the MessageFile to enable sending files between clients.
  • After we design our Data Contracts that will be exchanged through the service and clients, we have to design the service operations in an interface:
  • Designing a callback interface is very easy; just want an interface to define some operations to be called on the client side.
  • Time for the service implementation (of IChat interface). Our service contains a generic dictionary of key types of clients and value types of IChatCallback; so this generic collection will hold the online clients as keys and callback objects for each client as values. The service also contains a generic list to hold the online clients (to quickly pass it to users), a public property that represents the current callback object, a private method used by the service to search for a client in the clients list, and an object to synchronize our work - this object is useful to lock the current thread from receiving calls from clients and wait for the current operation to be completed. We will need this because in the service context, you might be sending some information for each callback object using a foreach loop, and suddenly one of these callback objects' client might get disconnected, and the operation won't complete because the collection has been modified and a client has been removed from it.
  • Implement the IChat interface:
  • Finally, this is the complete service assembly:
  • Set debug mode to Release and build the project. ServiceAssembly.dll will be added to the Release folder under bin folder.

Create Host on WPF

XML
< window title="Chat Service Host" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
x:class="WPFHost.Window1" height="300" width="300" />
    < grid />

        < grid.background />
            < lineargradientbrush />
                < gradientstop color="LightSlateGray" offset="0" />
                < gradientstop color="White" offset="0.5" />
                < gradientstop color="LightSlateGray" offset="0.9" />
            < /lineargradientbrush />
        < /grid.background />
        
        < label name="label1" height="28" width="67" 
        margin="10,93,0,0" verticalalignment="Top" 
        horizontalalignment="Left">Local IP:</label />
        < label name="label2" height="28" width="67"
         margin="10,0,0,85" verticalalignment="Bottom" 
         horizontalalignment="Left">Listen Port:</label />
        < textbox height="23" margin="76,98,108,0" 
        verticalalignment="Top" x:name="textBoxIP" text="localhost" />
        < textbox height="23" margin="76,0,108,88" 
        verticalalignment="Bottom" x:name="textBoxPort" 
        text="7997" />
        < button height="23" width="82" 
        margin="0,0,15,88" verticalalignment="Bottom" 
        horizontalalignment="Right" x:name="buttonStop"
         click="buttonStop_Click">Stop</button />
        < button height="23" width="82" 
        margin="0,96,15,0" verticalalignment="Top"
         horizontalalignment="Right" x:name="buttonStart"
          click="buttonStart_Click">Start</button />
        < label height="28" margin="10,0,15,45" 
        verticalalignment="Bottom" x:name="labelStatus">Status</label />
        < label height="37" margin="10,18,15,0"
         verticalalignment="Top" x:name="labelTitle"
          fontfamily="Jokerman" fontsize="20"
           foreground="White">Chat Service</label />
    < /grid />
< /window />

This is our host application that should instantiate a ServiceHost object. A ServiceHost object is what will actually host your service, enable you to apply bindings, add EndPoints, start the service or stop it. So, we will start defining a ServiceHost programmatically, and then use the configuration file.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
using ServiceAssembly;
using System.ServiceModel.Description;
using System.Xml;

namespace WPFHost
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        ServiceHost host;

        private void buttonStart_Click(object sender, 
                                       RoutedEventArgs e)
        {
            buttonStart.IsEnabled = false;

            //Define base addresses so all 
            //endPoints can go under it

            Uri tcpAdrs = new Uri("net.tcp://" + 
                textBoxIP.Text.ToString() + ":" + 
                textBoxPort.Text.ToString() + "/WPFHost/");

            Uri httpAdrs = new Uri("http://" + 
                textBoxIP.Text.ToString() + ":" +
                (int.Parse(textBoxPort.Text.ToString()) + 1).ToString() + 
                "/WPFHost/");

            Uri[] baseAdresses = { tcpAdrs, httpAdrs };

            host = new ServiceHost(
                   typeof(ServiceAssembly.ChatService), baseAdresses);


            NetTcpBinding tcpBinding = 
               new NetTcpBinding(SecurityMode.None, true);
            //Updated: to enable file transefer of 64 MB
            tcpBinding.MaxBufferPoolSize = (int)67108864;
            tcpBinding.MaxBufferSize = 67108864;
            tcpBinding.MaxReceivedMessageSize = (int)67108864;
            tcpBinding.TransferMode = TransferMode.Buffered;
            tcpBinding.ReaderQuotas.MaxArrayLength = 67108864;
            tcpBinding.ReaderQuotas.MaxBytesPerRead = 67108864;
            tcpBinding.ReaderQuotas.MaxStringContentLength = 67108864;
            

            tcpBinding.MaxConnections = 100;
            //To maxmize MaxConnections you have 
            //to assign another port for mex endpoint

            //and configure ServiceThrottling as well
            ServiceThrottlingBehavior throttle;
            throttle = 
             host.Description.Behaviors.Find< ServiceThrottlingBehavior>();
            if (throttle == null)
            {
                throttle = new ServiceThrottlingBehavior();
                throttle.MaxConcurrentCalls = 100;
                throttle.MaxConcurrentSessions = 100;
                host.Description.Behaviors.Add(throttle);
            }


            //Enable reliable session and keep 
            //the connection alive for 20 hours.
            tcpBinding.ReceiveTimeout = new TimeSpan(20, 0, 0);
            tcpBinding.ReliableSession.Enabled = true;
            tcpBinding.ReliableSession.InactivityTimeout = 
                                       new TimeSpan(20, 0, 10);

            host.AddServiceEndpoint(typeof(ServiceAssembly.IChat), 
                                    tcpBinding, "tcp");

            //Define Metadata endPoint, So we can 
            //publish information about the service
            ServiceMetadataBehavior mBehave = 
                           new ServiceMetadataBehavior();
            host.Description.Behaviors.Add(mBehave);

            host.AddServiceEndpoint(typeof(IMetadataExchange),
                MetadataExchangeBindings.CreateMexTcpBinding(),
                "net.tcp://" + textBoxIP.Text.ToString() + ":" + 
                (int.Parse(textBoxPort.Text.ToString()) - 1).ToString() + 
                "/WPFHost/mex");


            try
            {
                host.Open();
            }
            catch (Exception ex)
            {
                labelStatus.Content = ex.Message.ToString();
            }
            finally
            {
                if (host.State == CommunicationState.Opened)
                {
                    labelStatus.Content = "Opened";
                    buttonStop.IsEnabled = true;
                }
            }
        }

        private void buttonStop_Click(object sender, RoutedEventArgs e)
        {
            if (host != null)
            {
                try
                {
                    host.Close();
                }
                catch (Exception ex)
                {
                    labelStatus.Content = ex.Message.ToString();
                }
                finally
                {
                    if (host.State == CommunicationState.Closed)
                    {
                        labelStatus.Content = "Closed";
                        buttonStart.IsEnabled = true;
                        buttonStop.IsEnabled = false;
                    }
                }
            }
        }
    }
}
XML
< configuration />
    < system.servicemodel />
        < services />
            < service name="WCFService.Service" 
              behaviorconfiguration="behaviorConfig" />
                < host />
                    < baseaddresses />
                        < add baseaddress="net.tcp://localhost:7997/WPFHost/" />
                        < add baseaddress="http://localhost:7998/WPFHost/" />
                    < /baseaddresses />
                < /host />
                < endpoint contract="ServiceAssembly.IChat" binding="netTcpBinding" 
                address="tcp" bindingconfiguration="tcpBinding" />

                < endpoint contract="IMetadataExchange" binding="mexTcpBinding" 
                address="net.tcp://localhost:7996/WcfWinFormsHost/mex" />
            < /service />
        < /services />
        < behaviors />
            < servicebehaviors />
                < behavior name="behaviorConfig" />
                    < servicemetadata httpgetenabled="true" />
                    < servicedebug includeexceptiondetailinfaults="true" />
                    < servicethrottling maxconcurrentcalls="100" 
                      maxconcurrentsessions="100" />
                < /behavior />
            < /servicebehaviors />
        < /behaviors />
        < bindings />
            < nettcpbinding />
                < binding name="tcpBinding" maxbuffersize="67108864" 
                maxreceivedmessagesize="67108864" maxbufferpoolsize="67108864"
                 transfermode="Buffered" closetimeout="00:00:10" 
                 opentimeout="00:00:10" receivetimeout="00:20:00" 
                 sendtimeout="00:01:00" maxconnections="100" />
                    < security mode="None" />
                    < /security />
                    < readerquotas maxarraylength="67108864" 
                      maxbytesperread="67108864" 
                      maxstringcontentlength="67108864" />
                    < reliablesession enabled="true" 
                      inactivitytimeout="00:20:00" />
                < /binding />
            < /nettcpbinding />
        < /bindings />
    < /system.servicemodel />
< /configuration />
  • File > Add > New Project.., choose WPF Application, and set its name to WPFHost.
  • Add a reference to System.ServiceModel.
  • Add reference and browse for the service DLL file. C: > WCFWPFRoot > WCFWPFApp > ServiceAssembly > bin > Release > ServiceAssembly.dll, in our case.
  • Replace the XAML code in window1.xaml with this:
  • Programmatically: Add a reference to System.Runtime.Serialization.
  • Replace the code in window1.xaml.cs with this code:
  • Using the configuration file: Add New Item and choose the configuration file.
  • Add this code:

Create client on WPF

Add Service Reference

As you see, we give it the address of the mex (our metadata) EndPoint, and it will know and configure a new config file with the address of the TCP EndPoint. Click Advanced to enable asynchronous operations and generic lists.

Configure Proxy

XML
maxBufferPoolSize="67108864"
maxBufferSize="67108864" maxConnections="100"
maxReceivedMessageSize="67108864">

Client code commented, read from the top to bottom..

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Reflection;
using System.ServiceModel;
using WPFClient.SVC;
using System.Collections;
using System.Windows.Threading;
using Microsoft.Win32;

namespace WPFClient
{
    /// < summary>
    /// Interaction logic for Window1.xaml
    /// < /summary>
    public partial class Window1 : Window, SVC.IChatCallback
    {
        //SVC holds references to the proxy and cotracts..
        SVC.ChatClient proxy = null;
        SVC.Client receiver = null;
        SVC.Client localClient = null;

        //Client will create this folder when loading
        string rcvFilesPath = @"C:/WCF_Received_Files/";

        //When the communication object 
        //turns to fault state it will
        //require another thread to invoke a fault event
        private delegate void FaultedInvoker();

        //This will hold each online client with 
        //a listBoxItem to quickly handle adding
        //and removing clients when they join or leave
        Dictionary< ListBoxItem, SVC.Client> OnlineClients = 
                      new Dictionary< ListBoxItem, Client>();


        public Window1()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Window1_Loaded);
            chatListBoxNames.SelectionChanged += new 
              SelectionChangedEventHandler(
              chatListBoxNames_SelectionChanged);
            chatTxtBoxType.KeyDown += 
              new KeyEventHandler(chatTxtBoxType_KeyDown);
            chatTxtBoxType.KeyUp += 
              new KeyEventHandler(chatTxtBoxType_KeyUp);
        }


        //Service might be disconnected or stopped for any reason,
        //so we have to handle the state of the communication object,
        //the communication object will fire 
        //an event for each transitioning
        //from a state to another, notice that when a connection state goes
        //from opening to opened or from opened to closing state.. it can't go
        //back so, if it is closed or faulted you have to set the proxy = null;
        //to be able to create a proxy again and open a connection
        //..
        //I have made a method called HandleProxy() to handle the state
        //of the connection, so in each event like opened, closed or faulted
        //we will call this method, and it will switch on the connection state
        //and apply a suitable reaction.
        //..
        //Because this events will need to be invoked on another thread
        //you can do like so in WPF applications (I've got this idea from
        //Sacha Barber's greate article on WCF WPF Application)

        void InnerDuplexChannel_Closed(object sender, EventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                                new FaultedInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        void InnerDuplexChannel_Opened(object sender, EventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                                new FaultedInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        void InnerDuplexChannel_Faulted(object sender, EventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                                new FaultedInvoker(HandleProxy));
                return;
            }
            HandleProxy();
        }

        #region Private Methods

        /// < summary>
        /// This is the most method I like, it helps us alot
        /// We may can't know when a connection is lost in 
        /// of network failure or service stopped.
        /// And also to maintain performance client doesnt know
        /// that the connection will be lost when hitting the 
        /// disconnect button, but when a session is terminated
        /// this method will be called, and it will handle everything.
        /// < /summary>
        private void HandleProxy()
        {
            if (proxy != null)
            {
                switch (this.proxy.State)
                {
                    case CommunicationState.Closed:
                        proxy = null;
                        chatListBoxMsgs.Items.Clear();
                        chatListBoxNames.Items.Clear();
                        loginLabelStatus.Content = "Disconnected";
                        ShowChat(false);
                        ShowLogin(true);
                        loginButtonConnect.IsEnabled = true;
                        break;
                    case CommunicationState.Closing:
                        break;
                    case CommunicationState.Created:
                        break;
                    case CommunicationState.Faulted:
                        proxy.Abort();
                        proxy = null;
                        chatListBoxMsgs.Items.Clear();
                        chatListBoxNames.Items.Clear();
                        ShowChat(false);
                        ShowLogin(true);
                        loginLabelStatus.Content = "Disconnected";
                        loginButtonConnect.IsEnabled = true;
                        break;
                    case CommunicationState.Opened:
                        ShowLogin(false);
                        ShowChat(true);

                        chatLabelCurrentStatus.Content = "online";
                        chatLabelCurrentUName.Content = this.localClient.Name;

                        Dictionary< int, Image> images = GetImages();
                        Image img = images[loginComboBoxImgs.SelectedIndex];
                        chatCurrentImage.Source = img.Source;
                        break;
                    case CommunicationState.Opening:
                        break;
                    default:
                        break;
                }
            }

        }
        
        /// < summary>
        /// This is the second important method, which creates 
        /// the proxy, subscribe to connection state events
        /// and open a connection with the service
        /// < /summary>
        private void Connect()
        {
            if (proxy == null)
            {
                try
                {
                    this.localClient = new SVC.Client();
                    this.localClient.Name = loginTxtBoxUName.Text.ToString();
                    this.localClient.AvatarID = loginComboBoxImgs.SelectedIndex;
                    InstanceContext context = new InstanceContext(this);
                    proxy = new SVC.ChatClient(context);

                    //As the address in the configuration file is set to localhost
                    //we want to change it so we can call a service in internal 
                    //network, or over internet
                    string servicePath = proxy.Endpoint.ListenUri.AbsolutePath;
                    string serviceListenPort = 
                      proxy.Endpoint.Address.Uri.Port.ToString();
                    proxy.Endpoint.Address = new EndpointAddress("net.tcp://" 
                       + loginTxtBoxIP.Text.ToString() + ":" + 
                       serviceListenPort + servicePath);


                    proxy.Open();

                    proxy.InnerDuplexChannel.Faulted += 
                      new EventHandler(InnerDuplexChannel_Faulted);
                    proxy.InnerDuplexChannel.Opened += 
                      new EventHandler(InnerDuplexChannel_Opened);
                    proxy.InnerDuplexChannel.Closed += 
                      new EventHandler(InnerDuplexChannel_Closed);
                    proxy.ConnectAsync(this.localClient);
                    proxy.ConnectCompleted += new EventHandler< 
                          ConnectCompletedEventArgs>(proxy_ConnectCompleted);
                }
                catch (Exception ex)
                {
                    loginTxtBoxUName.Text = ex.Message.ToString();
                    loginLabelStatus.Content = "Offline";
                    loginButtonConnect.IsEnabled = true;
                }
            }
            else
            {
                HandleProxy();
            }
        }

        private void Send()
        {
            if (proxy != null && chatTxtBoxType.Text != "")
            {
                if (proxy.State == CommunicationState.Faulted)
                {
                    HandleProxy();
                }
                else
                {
                    //Create message, assign its properties
                    SVC.Message msg = new WPFClient.SVC.Message();
                    msg.Sender = this.localClient.Name;
                    msg.Content = chatTxtBoxType.Text.ToString();

                    //If whisper mode is checked and an item is
                    //selected in the list box of clients, it will
                    //arrange a client object called receiver
                    //to whisper
                    if ((bool)chatCheckBoxWhisper.IsChecked)
                    {
                        if (this.receiver != null)
                        {
                            proxy.WhisperAsync(msg, this.receiver);
                            chatTxtBoxType.Text = "";
                            chatTxtBoxType.Focus();
                        }
                    }

                    else
                    {
                        proxy.SayAsync(msg);
                        chatTxtBoxType.Text = "";
                        chatTxtBoxType.Focus();
                    }
                    //Tell the service to tell back 
                    //all clients that this client
                    //has just finished typing..
                    proxy.IsWritingAsync(null);
                }
            }
        }

        
        /// < summary>
        /// This method to enable us scrolling the list box of messages
        /// when a new message comes from the service..
        /// < /summary>
        private ScrollViewer FindVisualChild(DependencyObject obj)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is ScrollViewer)
                {
                    return (ScrollViewer)child;
                }
                else
                {
                    ScrollViewer childOfChild = FindVisualChild(child);
                    if (childOfChild != null)
                    {
                        return childOfChild;
                    }
                }
            }
            return null;
        }
 
        /// < summary>
        /// This is an important method which is called whenever
        /// a message comes from the service, a client joins or
        /// leaves, to return a ready item to be added in the
        /// list box (either the one for messages or the one for
        /// clients).
        /// < /summary>
        private ListBoxItem MakeItem(int imgID, string text)
        {
            ListBoxItem item = new ListBoxItem();
            Dictionary< int, Image> images = GetImages();
            Image img = images[imgID];
            img.Height = 70;
            img.Width = 60;
            item.Content = img;

            TextBlock txtblock = new TextBlock();
            txtblock.Text = text;
            txtblock.VerticalAlignment = VerticalAlignment.Center;

            StackPanel panel = new StackPanel();
            panel.Orientation = Orientation.Horizontal;
            panel.Children.Add(item);
            panel.Children.Add(txtblock);

            ListBoxItem bigItem = new ListBoxItem();
            bigItem.Content = panel;

            return bigItem;
        }

        /// < summary>
        /// This method is not used, I just put it here to help
        /// you in case you want to make a rich text box and enable
        /// emoticons for example.
        /// Just add a richTextBox control and set
        /// richTextBox.Document = MakeDocument(imgid, text);
        /// < /summary>
        private FlowDocument MakeDocument(int imgID, string text)
        {
            Dictionary< int, Image> images = GetImages();
            Image img = images[imgID];
            img.Height = 70;
            img.Width = 60;


            Block imgBlock = new BlockUIContainer(img);
            Block txtBlock = new Paragraph(new Run(text));


            FlowDocument doc = new FlowDocument();
            doc.Blocks.Add(imgBlock);

            doc.Blocks.Add(txtBlock);

            doc.FlowDirection = FlowDirection.LeftToRight;
            return doc;
        }

        /// < summary>
        /// A method to retreive avatars as stream objects
        /// and get an objects of type Image from the stream,
        /// to return a dictionary of images and an ID for each
        /// image.
        /// < /summary>
        private Dictionary< int, Image> GetImages()
        {
            List< Stream> picsStrm = new List< Stream>();

            Assembly asmb = Assembly.GetExecutingAssembly();
            string[] picNames = asmb.GetManifestResourceNames();

            foreach (string s in picNames)
            {
                if (s.EndsWith(".png"))
                {
                    Stream strm = asmb.GetManifestResourceStream(s);
                    if (strm != null)
                    {
                        picsStrm.Add(strm);
                    }
                }
            }

            Dictionary< int, Image> images = new Dictionary< int, Image>();

            int i = 0;

            foreach (Stream strm in picsStrm)
            {

                PngBitmapDecoder decoder = new PngBitmapDecoder(strm,
                    BitmapCreateOptions.PreservePixelFormat, 
                    BitmapCacheOption.Default);
                BitmapSource bitmap = decoder.Frames[0] as BitmapSource;
                Image img = new Image();
                img.Source = bitmap;
                img.Stretch = Stretch.UniformToFill;

                images.Add(i, img);
                i++;

                strm.Close();
            }
            return images;
        }

        /// < summary>
        /// Show or hide login controls depends on the parameter
        /// < /summary>
        /// < param name="show">< /param>
        private void ShowLogin(bool show)
        {
            if (show)
            {
                loginButtonConnect.Visibility = Visibility.Visible;
                loginComboBoxImgs.Visibility = Visibility.Visible;
                loginLabelIP.Visibility = Visibility.Visible;
                loginLabelStatus.Visibility = Visibility.Visible;
                loginLabelTitle.Visibility = Visibility.Visible;
                loginLabelUName.Visibility = Visibility.Visible;
                loginPolyLine.Visibility = Visibility.Visible;
                loginTxtBoxIP.Visibility = Visibility.Visible;
                loginTxtBoxUName.Visibility = Visibility.Visible;
            }
            else
            {
                loginButtonConnect.Visibility = Visibility.Collapsed;
                loginComboBoxImgs.Visibility = Visibility.Collapsed;
                loginLabelIP.Visibility = Visibility.Collapsed;
                loginLabelStatus.Visibility = Visibility.Collapsed;
                loginLabelTitle.Visibility = Visibility.Collapsed;
                loginLabelUName.Visibility = Visibility.Collapsed;
                loginPolyLine.Visibility = Visibility.Collapsed;
                loginTxtBoxIP.Visibility = Visibility.Collapsed;
                loginTxtBoxUName.Visibility = Visibility.Collapsed;
            }
        }


        /// < summary>
        /// Show or hide chat controls depends on the parameter
        /// < /summary>
        /// < param name="show">< /param>
        private void ShowChat(bool show)
        {
            if (show)
            {
                chatButtonDisconnect.Visibility = Visibility.Visible;
                chatButtonSend.Visibility = Visibility.Visible;
                chatCheckBoxWhisper.Visibility = Visibility.Visible;
                chatCurrentImage.Visibility = Visibility.Visible;
                chatLabelCurrentStatus.Visibility = Visibility.Visible;
                chatLabelCurrentUName.Visibility = Visibility.Visible;
                chatListBoxMsgs.Visibility = Visibility.Visible;
                chatListBoxNames.Visibility = Visibility.Visible;
                chatTxtBoxType.Visibility = Visibility.Visible;
                chatLabelWritingMsg.Visibility = Visibility.Visible;
                chatLabelSendFileStatus.Visibility = Visibility.Visible;
                chatButtonOpenReceived.Visibility = Visibility.Visible;
                chatButtonSendFile.Visibility = Visibility.Visible;
            }
            else
            {
                chatButtonDisconnect.Visibility = Visibility.Collapsed;
                chatButtonSend.Visibility = Visibility.Collapsed;
                chatCheckBoxWhisper.Visibility = Visibility.Collapsed;
                chatCurrentImage.Visibility = Visibility.Collapsed;
                chatLabelCurrentStatus.Visibility = Visibility.Collapsed;
                chatLabelCurrentUName.Visibility = Visibility.Collapsed;
                chatListBoxMsgs.Visibility = Visibility.Collapsed;
                chatListBoxNames.Visibility = Visibility.Collapsed;
                chatTxtBoxType.Visibility = Visibility.Collapsed;
                chatLabelWritingMsg.Visibility = Visibility.Collapsed;
                chatLabelSendFileStatus.Visibility = Visibility.Collapsed;
                chatButtonOpenReceived.Visibility = Visibility.Collapsed;
                chatButtonSendFile.Visibility = Visibility.Collapsed;
            }
        }

        #endregion


        #region UI_Events

        void Window1_Loaded(object sender, RoutedEventArgs e)
        {
            //Create a folder named WCF_Received_Files in C directory
            DirectoryInfo dir = new DirectoryInfo(rcvFilesPath);
            dir.Create();

            Dictionary< int, Image> images = GetImages();
            //Populate images in the login comboBoc control
            foreach (Image img in images.Values)
            {
                ListBoxItem item = new ListBoxItem();
                item.Width = 90;
                item.Height = 90;
                item.Content = img;

                loginComboBoxImgs.Items.Add(item);
            }
            loginComboBoxImgs.SelectedIndex = 0;

            ShowChat(false);
            ShowLogin(true);

        }

        private void chatButtonOpenReceived_Click(object sender, 
                                                  RoutedEventArgs e)
        {
            //Open WCF_Received_Files folder in windows explorer
            System.Diagnostics.Process.Start(rcvFilesPath);
        }

        private void chatButtonSendFile_Click(object sender, 
                                              RoutedEventArgs e)
        {
            if (this.receiver != null)
            {
                Stream strm = null;
                try
                {
                    OpenFileDialog fileDialog = new OpenFileDialog();
                    fileDialog.Multiselect = false;

                    if (fileDialog.ShowDialog() == DialogResult.HasValue)
                    {
                        return;
                    }

                    strm = fileDialog.OpenFile();
                    if (strm != null)
                    {
                        byte[] buffer = new byte[(int)strm.Length];

                        int i = strm.Read(buffer, 0, buffer.Length);

                        if (i > 0)
                        {
                            SVC.FileMessage fMsg = new FileMessage();
                            fMsg.FileName = fileDialog.SafeFileName;
                            fMsg.Sender = this.localClient.Name;
                            fMsg.Data = buffer;
                            proxy.SendFileAsync(fMsg, this.receiver);
                            proxy.SendFileCompleted += new 
                                  EventHandler< SendFileCompletedEventArgs>
                            (proxy_SendFileCompleted);
                            chatLabelSendFileStatus.Content = "Sending...";
                        }

                    }
                }
                catch (Exception ex)
                {
                    chatTxtBoxType.Text = ex.Message.ToString();
                }
                finally
                {
                    if (strm != null)
                    {
                        strm.Close();
                    }
                }
            }

        }

        void proxy_SendFileCompleted(object sender, 
                   SendFileCompletedEventArgs e)
        {
            chatLabelSendFileStatus.Content = "File Sent";
        }


        protected override void OnClosing(
                  System.ComponentModel.CancelEventArgs e)
        {
            if (proxy != null)
            {
                if (proxy.State == CommunicationState.Opened)
                {
                    proxy.Disconnect(this.localClient);
                    //dont set proxy.Close(); because 
                    //isTerminating = true on Disconnect()
                    //and this by default will call 
                    //HandleProxy() to take care of this.
                }
                else
                {
                    HandleProxy();
                }
            }
        }

        private void buttonConnect_Click(object sender, 
                                   RoutedEventArgs e)
        {
            loginButtonConnect.IsEnabled = false;
            loginLabelStatus.Content = "Connecting..";
            proxy = null;
            Connect();

        }

        void proxy_ConnectCompleted(object sender, 
                   ConnectCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                loginLabelStatus.Foreground = 
                        new SolidColorBrush(Colors.Red);
                loginTxtBoxUName.Text = e.Error.Message.ToString();
                loginButtonConnect.IsEnabled = true;
            }
            else if (e.Result)
            {
                HandleProxy();
            }
            else if (!e.Result)
            {
                loginLabelStatus.Content = "Name found";
                loginButtonConnect.IsEnabled = true;
            }


        }

        private void chatButtonSend_Click(object sender, 
                                          RoutedEventArgs e)
        {
            Send();
        }

        private void chatButtonDisconnect_Click(object sender, 
                                          RoutedEventArgs e)
        {
            if (proxy != null)
            {
                if (proxy.State == CommunicationState.Faulted)
                {
                    HandleProxy();
                }
                else
                {
                    proxy.DisconnectAsync(this.localClient);
                }
            }
        }


        void chatTxtBoxType_KeyUp(object sender, KeyEventArgs e)
        {
            if (proxy != null)
            {
                if (proxy.State == CommunicationState.Faulted)
                {
                    HandleProxy();
                }
                else
                {
                    if (chatTxtBoxType.Text.Length < 1)
                    {
                        proxy.IsWritingAsync(null);
                    }
                }
            }
        }

        void chatTxtBoxType_KeyDown(object sender, KeyEventArgs e)
        {
            if (proxy != null)
            {
                if (proxy.State == CommunicationState.Faulted)
                {
                    HandleProxy();
                }
                else
                {
                    if (e.Key == Key.Enter)
                    {
                        Send();
                    }
                    else if (chatTxtBoxType.Text.Length < 1)
                    {
                        proxy.IsWritingAsync(this.localClient);
                    }
                }
            }
        }

        void chatListBoxNames_SelectionChanged(object sender, 
                              SelectionChangedEventArgs e)
        {
            //If user select an online client, make a client object
            //to be the receiver if the user wants to whisper him.
            ListBoxItem item = 
                chatListBoxNames.SelectedItem as ListBoxItem;
            if (item != null)
            {
                this.receiver = this.OnlineClients[item];
            }
        }


        #endregion

        #region IChatCallback Members

        public void RefreshClients(List< WPFClient.SVC.Client> clients)
        {
            chatListBoxNames.Items.Clear();
            OnlineClients.Clear();
            foreach (SVC.Client c in clients)
            {
                ListBoxItem item = MakeItem(c.AvatarID, c.Name);
                chatListBoxNames.Items.Add(item);
                OnlineClients.Add(item, c);
            }
        }

        public void Receive(WPFClient.SVC.Message msg)
        {
            foreach (SVC.Client c in this.OnlineClients.Values)
            {
                if (c.Name == msg.Sender)
                {
                    ListBoxItem item = MakeItem(c.AvatarID, 
                        msg.Sender + " : " + msg.Content);
                    chatListBoxMsgs.Items.Add(item);
                }
            }
            ScrollViewer sv = FindVisualChild(chatListBoxMsgs);
            sv.LineDown();
        }

        public void ReceiveWhisper(WPFClient.SVC.Message msg, 
                                   WPFClient.SVC.Client receiver)
        {
            foreach (SVC.Client c in this.OnlineClients.Values)
            {
                if (c.Name == msg.Sender)
                {
                    ListBoxItem item = MakeItem(c.AvatarID,
                        msg.Sender + " whispers " + 
                        receiver.Name + " : " + msg.Content);
                    chatListBoxMsgs.Items.Add(item);
                }
            }
            ScrollViewer sv = FindVisualChild(chatListBoxMsgs);
            sv.LineDown();
        }

        public void IsWritingCallback(WPFClient.SVC.Client client)
        {
            if (client == null)
            {
                chatLabelWritingMsg.Content = "";
            }
            else
            {
                chatLabelWritingMsg.Content += client.Name + 
                      " is writing a message.., ";
            }
        }

        public void ReceiverFile(WPFClient.SVC.FileMessage fileMsg, 
                                 WPFClient.SVC.Client receiver)
        {
            try
            {
                FileStream fileStrm = new FileStream(rcvFilesPath + 
                           fileMsg.FileName, FileMode.Create, 
                           FileAccess.ReadWrite);
                fileStrm.Write(fileMsg.Data, 0, fileMsg.Data.Length);
                chatLabelSendFileStatus.Content = 
                           "Received file, " + fileMsg.FileName;
            }
            catch (Exception ex)
            {
                chatLabelSendFileStatus.Content = ex.Message.ToString();
            }
        }

        public void UserJoin(WPFClient.SVC.Client client)
        {
            ListBoxItem item = MakeItem(client.AvatarID,
                "------------ " + client.Name + " joined chat ------------");
            chatListBoxMsgs.Items.Add(item);
            ScrollViewer sv = FindVisualChild(chatListBoxMsgs);
            sv.LineDown();
        }

        public void UserLeave(WPFClient.SVC.Client client)
        {
            ListBoxItem item = MakeItem(client.AvatarID,
                "------------ " + client.Name + " left chat ------------");
            chatListBoxMsgs.Items.Add(item);
            ScrollViewer sv = FindVisualChild(chatListBoxMsgs);
            sv.LineDown();
        }

        #endregion
    }
}
  • File > Add > New Project.., choose WPF Application, and set its name to WPFClient.
  • Add a reference to System.ServiceModel.
  • Set the WPFHost project to be the startup project, start it, hit the Start button to run the service.
  • Set the WPFClient project to be the startup project, and add a service reference, as in this picture:
  • Now, hit OK and you will notice that a new app.config file has been added. Open it and modify the binding to enable file transfers of 64 MB, and increase max connections to 100.
  • Switch to window1.xaml.cs and replace its code with this.

Other stuff

Enable online access over the Internet

Port Forward

  • If your server (the machine that will run the service application) is inside a network, you have to forward the port that the service listens on (in our case, it is 7997). To do this, login in to your router configuration, and port forward 7997 (Help).
  • You may also want to open this port manually, or create a rule for it in your firewall. I use Kaspersky Internet Security
  • .

    Open Port

Automatically locate service IP

You can automatically locate your service by saving an always-updated IP in a text file, uploading it online, and letting your client applications read the IP from the uploaded text file..

C#
WebRequest request = WebRequest.Create("www.yourserver.com/textfile.txt");
WebResponse response = request.GetResponse();
Stream strm = response.GetResponseStream();
StreamReader reader = new StreamReader(strm);
string serviceIP = reader.ReadToEnd();

Feedback

These distributed applications are built on a learning experience. I wanted to share the code to learn more and let others learn too, so if you got problems, errors, ideas, please let me know. If you liked it, please vote. Thanks.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)