Hi all.
I have followed Tomek Janczuk's demonstration on silverlight tv to create a chat program that uses WCF Duplex Polling web service. The client subscribes to the server, and then the server initiates notifications to all connected clients to publish events.
The Idea is simple, on the client, there is a button that allows the client to connect. A text box where the client can write a message and publish it, and a bigger text box that presents all the notifications received from the server.
I connected 3 clients (in different browsers - IE, Firefox and Chrome) and it all works nicely. They send messages and receive them smoothly. The problem starts when I close one of the browsers. As soon as one client is out, the other clients get stuck. They stop getting notifications.
I am guessing that the loop in the server that goes through all the clients and sends them the notifications is stuck on the client that is now missing. I tried catching the exception and removing it from the clients list (see code) but it still does not help.
any ideas?
The server code is as follows:
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;
using System.Runtime.Remoting.Channels;
namespace ChatDemo.Web
{
[ServiceContract]
public interface IChatNotification
{
// this will be used as a callback method, therefore it must be one way
[OperationContract(IsOneWay=true)]
void Notify(string message);
[OperationContract(IsOneWay = true)]
void Subscribed();
}
// define this as a callback contract - to allow push
[ServiceContract(Namespace="", CallbackContract=typeof(IChatNotification))]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class ChatService
{
SynchronizedCollection<IChatNotification> clients = new SynchronizedCollection<IChatNotification>();
[OperationContract(IsOneWay=true)]
public void Subscribe()
{
IChatNotification cli = OperationContext.Current.GetCallbackChannel<IChatNotification>();
this.clients.Add(cli);
// inform the client it is now subscribed
cli.Subscribed();
Publish("New Client Connected: " + cli.GetHashCode());
}
[OperationContract(IsOneWay = true)]
public void Publish(string message)
{
SynchronizedCollection<IChatNotification> toRemove = new SynchronizedCollection<IChatNotification>();
foreach (IChatNotification channel in this.clients)
{
try
{
channel.Notify(message);
}
catch
{
toRemove.Add(channel);
}
}
// now remove all the dead channels
foreach (IChatNotification chnl in toRemove)
{
this.clients.Remove(chnl);
}
}
}
}
The client code is as follows:
void client_NotifyReceived(object sender, ChatServiceProxy.NotifyReceivedEventArgs e)
{
this.Messages.Text += string.Format("{0}\n\n", e.Error != null ? e.Error.ToString() : e.message);
}
private void MyMessage_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.client.PublishAsync(this.MyMessage.Text);
this.MyMessage.Text = "";
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.client = new ChatServiceProxy.ChatServiceClient(new PollingDuplexHttpBinding { DuplexMode = PollingDuplexMode.MultipleMessagesPerPoll }, new EndpointAddress("../ChatService.svc"));
// listen for server events
this.client.NotifyReceived += new EventHandler<ChatServiceProxy.NotifyReceivedEventArgs>(client_NotifyReceived);
this.client.SubscribedReceived += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_SubscribedReceived);
// subscribe for the server events
this.client.SubscribeAsync();
}
void client_SubscribedReceived(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
try
{
Messages.Text += "Connected!\n\n";
gsConnect.Color = Colors.Green;
}
catch
{
Messages.Text += "Failed to Connect!\n\n";
}
}
And the web config is as follows:
<system.serviceModel>
<extensions>
<bindingExtensions>
<add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</bindingExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<pollingDuplex>
<binding name="myPollingDuplex" duplexMode="MultipleMessagesPerPoll"/>
</pollingDuplex>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service name="ChatDemo.Web.ChatService">
<endpoint address="" binding="pollingDuplex" bindingConfiguration="myPollingDuplex" contract="ChatDemo.Web.ChatService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>