Question

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. enter image description here

Was it helpful?

Solution

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.

OTHER TIPS

1) You can use IErrorHandle to catch this exception (may be useful). Often exceptions are thrown during callbacks (serialization/(un)KnownType(s) issues).

2) If you can't see problem in your server/service-side (in svclog) - the root cause of communication abort can be on the client. Look at your callback implementation on client. If exception is thrown in callback method (raising event handlers etc.) and it swallowed (or you missed it for some reason, maybe because it thrown not in UI thread or something else...debugger didn't notify you) - it will (or it might) abort communication (client proxy will be faulted).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top