Following is the Duplex Service and its WebConfig file. In the service, the transmitting client sends it data via TransmitUserData method and the receiving client receives the data via PublishUserData from CallBackContract.
Now the issue is, if I run the clients for first time it works perfectly, but if I re-run the clients it give the following error at while Transmitting.
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it has been Aborted.
Duplex Service Contract
[ServiceContract(CallbackContract=typeof(IContactManagerCallBackService))]
public interface IContactManagerUserService
{
[OperationContract(IsOneWay = true)]
void SubscribeAsReceiver();
[OperationContract(IsOneWay = true)]
void SubscribeAsTransmitter();
[OperationContract]
void TransmitUserData(UserInfo info);
}
[ServiceContract]
public interface IContactManagerCallBackService
{
[OperationContract(IsOneWay = true)]
void PublishUserData(UserInfo info);
}
[DataContract]
public class UserInfo
{
[DataMember]
public bool PaidUser { get; set; }
[DataMember]
public string ProfileName{ get; set;}
[DataMember]
public string ComputerName { get; set; }
[DataMember]
public string IPAddress { get; set; }
[DataMember]
public string MACAddress { get; set; }
}
Service Implementation
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class ContactManagerUserService : IContactManagerUserService
{
SynchronizedCollection<IContactManagerCallBackService> receiverClients;
public ContactManagerUserService()
{
receiverClients = new SynchronizedCollection<IContactManagerCallBackService>();
}
public void SubscribeAsReceiver()
{
IContactManagerCallBackService client = OperationContext.Current.GetCallbackChannel<IContactManagerCallBackService>();
receiverClients.Add(client);
}
public void TransmitUserData(UserInfo info)
{
foreach (IContactManagerCallBackService receiverClient in receiverClients)
{
receiverClient.PublishUserData(info);
}
}
Web.config
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true" logMalformedMessages="true"
logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
</diagnostics>
<bindings>
<netTcpBinding>
<binding name="NewBinding0" maxConnections="1000"
portSharingEnabled="true">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="ContactManagerService.ContactManagerUserService">
<endpoint address="mex" binding="netTcpBinding" bindingConfiguration=""
name="dataEndPoint" contract="ContactManagerService.IContactManagerUserService" />
<endpoint binding="mexTcpBinding" bindingConfiguration="" name="mexHttp"
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost/ContactManagerUserService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Edit:
The code runs perfectly for the first time. Actually the problem is created by Transmitter, which we never subscribe or unsubscribe.
After the first receiver exits, its channel is still saved in the receiverClients collection in the service, so the next time a transmit occurs, the server will attempt to callback to a non-existent client. You must remove the receiver from the collection when it ends, perhaps with an unsubscribe contract operation. There are a number of ways to implement this. Here is one example. (These are the changes, only - please let me know if you would like the entire project.)
In IContactManagerUserService.cs:
// receivers must identify themselves
[OperationContract(IsOneWay = true)]
void SubscribeAsReceiver(string receiverID);
// new operation
[OperationContract(IsOneWay = true)]
void UnSubscribeAsReceiver(string receiverID);
In ContactManagerUserService.cs
// replace synchronized collection with thread-safe dictionary
using System.Collections.Concurrent;
ConcurrentDictionary<string, IContactManagerCallBackService> receiverClients;
public ContactManagerUserService()
{
receiverClients = new ConcurrentDictionary<string, IContactManagerCallBackService>();
}
public void SubscribeAsReceiver(string receiverID)
{
IContactManagerCallBackService client = OperationContext.Current.GetCallbackChannel<IContactManagerCallBackService>();
receiverClients.TryAdd(receiverID, client);
}
public void UnSubscribeAsReceiver(string receiverID)
{
IContactManagerCallBackService client;
receiverClients.TryRemove(receiverID, out client);
}
public void TransmitUserData(UserInfo info)
{
foreach (IContactManagerCallBackService receiverClient in receiverClients.Values)
{
receiverClient.PublishUserData(info);
}
}
In ReceiverClient.Program.cs (don't forget to update ServiceReferences since the contract has changed):
// one way to uniquely identify this receiver
string rcvID = Guid.NewGuid().ToString();
// .... //
receivingClient.SubscribeAsReceiver(rcvID);
// .... //
Console.ReadLine();
// Important - this line must be executed, or service will again throw error.
// So when debugging, don't just close window, press enter to execute this line.
// In more robust setting, this would probably go in finally{} block, or in Dispose()
receivingClient.UnSubscribeAsReceiver(rcvID);
Debugging Tip You may want to have a version of the service you can run locally, so you can step into it and debug it. Also, I have found logging messages to the service log to be very helpful when you can't run the service locally.
You can search WCF Publisher/Subscriber for more info.