Click here to Skip to main content
15,881,709 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hello all,

I'm a newbie on WCF. I'm able to build a self-hosted WCF service and 4 .NET applications connected to it using NetTcpBindings. Now I need one more client written in Python2.7 script to connect to this WCF service providing duplex communication.

What I have tried:

As far as I googled, WsDualHttpBinding with SOAP 1.2 should be used.

Here is my service interface

C#
namespace GPH_QuickMessageServiceLib
    {
        /// <summary>
        /// GPH Quick Message Service Operations
        /// </summary>
        [ServiceContract(
            Name = "GPH_QuickMessageService",
            Namespace = "GPH_QuickMessageServiceLib",
            SessionMode = SessionMode.Required,
            CallbackContract = typeof(IMessageServiceCallback))]

        public interface IMessageServiceInbound
        {
            [OperationContract]
            [WebInvoke]
            int JoinTheConversation(string userName);
            [OperationContract]
            [WebInvoke]
            int LeaveTheConversation(string userName);
        }

        public interface IMessageServiceCallback
        {
            [OperationContract(IsOneWay = true)]
            [WebInvoke]
            void NotifyUserJoinedTheConversation(string userName, List<string> SubscriberList);
            [OperationContract(IsOneWay = true)]
            [WebInvoke]
            void NotifyUserLeftTheConversation(string userName, List<string> SubscriberList);
        }
    }

Here is my service behavior

C#
namespace GPH_QuickMessageServiceLib
    {
        /// <summary>
        /// GPH Quick Message Service behaviour
        /// </summary>
        [ServiceBehavior(
            ConcurrencyMode = ConcurrencyMode.Single,
            InstanceContextMode = InstanceContextMode.PerCall)]

        public class GPH_QuickMessageService : IMessageServiceInbound
        {
            private static List<IMessageServiceCallback> _callbackList = new List<IMessageServiceCallback>();
            //  number of current users - 0 to begin with
            private static int _registeredUsers = 0;
            private static List<string> SubscriberList = new List<string>();
            private static Dictionary<string, IMessageServiceCallback> NotifyList = new Dictionary<string, IMessageServiceCallback>();  // Default Constructor

            // Default Constructor
            public GPH_QuickMessageService() { }
    
            public int JoinTheConversation(string userName)
            {
                // Subscribe the user to the conversation
                IMessageServiceCallback registeredUser = OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();
    
                if (!_callbackList.Contains(registeredUser))
                {
                    _callbackList.Add(registeredUser);
                    SubscriberList.Add(userName);//Note the callback list is just a list of channels.
                    NotifyList.Add(userName, registeredUser);//Bind the username to the callback channel ID
                }
    
                _callbackList.ForEach(
                    delegate (IMessageServiceCallback callback)
                    {
                        callback.NotifyUserJoinedTheConversation(userName, SubscriberList);
                        _registeredUsers++;
                    });
    
                return _registeredUsers;
            }
    
            public int LeaveTheConversation(string userName)
            {
                // Unsubscribe the user from the conversation.      
                IMessageServiceCallback registeredUser = OperationContext.Current.GetCallbackChannel<IMessageServiceCallback>();
    
                if (_callbackList.Contains(registeredUser))
                {
                    _callbackList.Remove(registeredUser);
                    NotifyList.Remove(userName);
                    SubscriberList.Remove(userName);
                    _registeredUsers--;
                }
    
                // Notify everyone that user has arrived.
                // Use an anonymous delegate and generics to do our dirty work.
                _callbackList.ForEach(
                    delegate (IMessageServiceCallback callback)
                    {
                        callback.NotifyUserLeftTheConversation(userName, SubscriberList);
                    });
    
                return _registeredUsers;
            }
        }
    }


Here is my App.config in service-host application

XML
<?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
      </startup>
      <system.serviceModel>
        <services>
          <service name="GPH_QuickMessageServiceLib.GPH_QuickMessageService"
            behaviorConfiguration = "QuickMessageServiceMEXBehavior">
            <endpoint address ="soap"
                      binding="wsDualHttpBinding"
                      contract="GPH_QuickMessageServiceLib.IMessageServiceInbound" />
            <endpoint address ="service"
                      binding="netTcpBinding"
                      contract="GPH_QuickMessageServiceLib.IMessageServiceInbound" />
            <!-- Enable the MEX endpoint -->
            <endpoint address="mex"
                      binding="mexTcpBinding"
                      contract="IMetadataExchange" />
    
            <!-- Need to add this so MEX knows the address of our service -->
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:2709/GPH_QuickMessageService/soap"/>
                <add baseAddress="net.tcp://localhost:8868/GPH_QuickMessageService"/>
              </baseAddresses>
            </host>
    
          </service>
        </services>
        <!-- A behavior definition for MEX -->
        <behaviors>
          <serviceBehaviors>
            <behavior name="QuickMessageServiceMEXBehavior" >
              <serviceMetadata httpGetEnabled="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>


And here is what I have in Python2.7 script (I'm using suds-jurko 0.6 with a hack to use SOAP 1.2 as .NET wsDualHttpBinding only supports SOAP 1.2)

Python
from suds.client import Client
    from suds.bindings import binding
    import logging
    
    # Just for debugging purposes.
    logging.basicConfig(level=logging.INFO)
    logging.getLogger('suds.client').setLevel(logging.DEBUG)
    
    # Telnic's SOAP server expects a SOAP 1.2 envelope, not a SOAP 1.1 envelope
    # and will complain if this hack isn't done.
    binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope')
    client = Client('http://localhost:2709/GPH_QuickMessageService/soap?wsdl',
        headers={'Content-Type': 'application/soap+xml'})
    
    # This will now work just fine.
    result = client.service.JoinTheConversation('RIDE')
    
    print client
    print 'result = %s' % result


As i guess, my python client already bound to server and get the list of available operations but it could not get the result from those operations. It always returns None

Python
C:\Python27\python.exe C:/Users/sev_user/PycharmProjects/WcfInteration/venv/Scripts/suds_client.py
    DEBUG:suds.client:sending to (http://localhost:2709/GPH_QuickMessageService/soap/soap)
    message:
    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope xmlns:ns0="GPH_QuickMessageServiceLib" xmlns:ns1="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
       <SOAP-ENV:Header/>
       <ns1:Body>
          <ns0:JoinTheConversation>
             <ns0:userName>RIDE</ns0:userName>
          </ns0:JoinTheConversation>
       </ns1:Body>
    </SOAP-ENV:Envelope>
    DEBUG:suds.client:headers = {'SOAPAction': '"GPH_QuickMessageServiceLib/GPH_QuickMessageService/JoinTheConversation"', 'Content-Type': 'application/soap+xml'}
    
    Suds ( https://fedorahosted.org/suds/ )  version: 0.6
    
    Service ( GPH_QuickMessageService ) tns="http://tempuri.org/"
       Prefixes (3)
          ns0 = "GPH_QuickMessageServiceLib"
          ns1 = "http://schemas.microsoft.com/2003/10/Serialization/"
          ns2 = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"
       Ports (2):
          (WSDualHttpBinding_GPH_QuickMessageService)
             Methods (7):
                JoinTheConversation(xs:string userName)
                LeaveTheConversation(xs:string userName)
                NotifyUserJoinedTheConversation()
                NotifyUserLeftTheConversation()
                NotifyUserOfMessage()
                ReceiveMessage(xs:string userName, ns2:ArrayOfstring addressList, xs:string userMessage)
                sum(xs:int a, xs:int b)
             Types (4):
                ns2:ArrayOfstring
                ns1:char
                ns1:duration
                ns1:guid
          (NetTcpBinding_GPH_QuickMessageService)
             Methods (7):
                JoinTheConversation(xs:string userName)
                LeaveTheConversation(xs:string userName)
                NotifyUserJoinedTheConversation()
                NotifyUserLeftTheConversation()
                NotifyUserOfMessage()
                ReceiveMessage(xs:string userName, ns2:ArrayOfstring addressList, xs:string userMessage)
                sum(xs:int a, xs:int b)
             Types (4):
                ns2:ArrayOfstring
                ns1:char
                ns1:duration
                ns1:guid
    
    
    result = None
    DEBUG:suds.client:HTTP succeeded:
    
    
    Process finished with exit code 0


I have tried several ways other than suds, such as ZSI, zeep, but always get result as 'None' or '0'. I have attached logger on these SOAP client process and always get either 'HTTP succeed' or '202 Accepted'. Couldn't figure out myself what should be wrong here.

Did any body face the same problem? Please give me some clue to resolve this.

Or any other idea to get duplex communication between Python2.7 and WCF is always appreciated.
Posted
Updated 10-Jul-21 20:24pm

1 solution

Some clue (will work on this later this month):
Nothing on the python-client side handles the service-method NotifyUserJoinedTheConversation().
Result contains the direct output of web-method which is expected of type int, but because of missing exception handling, nothing is returned.
In C#, this is done with a proxy class at the client. Still looking for an equivalent for Python.

Temporarily, you can start to avoid the callback in JoinTheConversation and see if it returns 1.

# This will now work just NOT fine.
 _registeredUsers is not returned because service.NotifyUserJoinedTheConversation() is not handled on the client.
    result = client.service.JoinTheConversation('RIDE')
#   expected result: None (the callback fails, exception happens before return)


# Example proxy class in C#, to attach to the service-method of the callback;
public class ServiceCallbackProxy : Some.Namespace.ServiceReference.IServiceCallback
 
Share this answer
 

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



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900