|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis 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. Try the demo online.NET 3.5 Framework must be installed.
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:
The application features include:
UpdatesFile transfer enabledI really recommend these two books in case you like WCF:
TechniqueThis 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. So, you have got to know:
Step by step, Building a WCF duplex serviceSteps in points:
Create service assembly
using System.Linq;
using System.Text;
System.ServiceModel.using System.ServiceModel;.using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace ServiceAssembly
{
public class ChatService
{
}
}
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.System.Runtime.Serialization, and so add this line: using System.Runtime.Serialization;. [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; }
}
}
Message will have information about the sender, content, and time: [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; }
}
}
MessageFile to enable sending files between clients. [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; }
}
}
[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 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. 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.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). [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;
}
}
IChat interface: 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
}
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 Host on WPF
< 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 System.Runtime.Serialization.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;
}
}
}
}
}
}
< 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 />
Create client on WPF
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. maxBufferPoolSize="67108864"
maxBufferSize="67108864" maxConnections="100"
maxReceivedMessageSize="67108864">
Client code commented, read from the top to bottom.. 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)
| ||||||||||||||||||||