West Wind Internet and Client Tools
wwSoap Help
Gravatar is a globally recognized avatar based on your email address. wwSoap Help
  Scott R
  All
  Jun 11, 2019 @ 10:04am

Rick,

I realize that on your help doc (https://webconnection.west-wind.com/docs/_06f04lmkh.htm) it says that the wwSOAP class is deprecated. However, I was hoping you could at least tell me what properties I need to set to achieve my desired result?

I've got an xml sample from the company we are trying to hit and I'm having problems getting the wwSOAP XML to match.

I need it to match:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v20="http://services.discretewireless.com/v2009_1/">
   <soapenv:Header/>
   <soapenv:Body>
      <v20:getAuthenticationTokenRequest>
         <v20:username>username</v20:username>
         <v20:password>password</v20:password>
      </v20:getAuthenticationTokenRequest>
   </soapenv:Body>
</soapenv:Envelope>

I'm using the following code:

DO wwSOAP
LOCAL oSoap, oWSDL

oSoap = CREATEOBJECT('wwSoap')
oWSDL = oSoap.parseServiceWSDL('http://services.discretewireless.com/v2009_1/AuthenticationManagerV1.wsdl')
*oSoap.cMethodNameSpace = 'http://services.discretewireless.com/v2009_1/'
*oSoap.cMethodNameSpaceURI = 'http://services.discretewireless.com/v2009_1/'

oSoap.cExtraEnvelopeAttributes = 'xmlns:v20="http://services.discretewireless.com/v2009_1/"'
oSoap.cMethodNameSpaceURI = 'v20'
oSoap.cMethodNameSpace = 'v20'

oSoap.addParameter('username', otsologin.tsologin, 'v20:') && I was testing to see if the 'v20:' would show up anywhere
oSoap.addParameter('password', otsologin.tsopwd)

m.req = oSoap.createSoapRequestXML('getAuthenticationTokenRequest')
*m.resp = oSoap.callWSDLMethod('getAuthenticationToken') &&, oWSDL)

and I'm am currently ending up with:

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
 soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"  xmlns:v20="http://services.discretewireless.com/v2009_1/">
<soap:Body>
<v20:getAuthenticationTokenRequest xmlns:v20="v20" >
	<username>username</username>
	<password>password</password>
</v20:getAuthenticationTokenRequest></soap:Body>
</soap:Envelope>

This is close but not right. Is there a property I'm missing or is this not even possible using the wwSOAP class?

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jun 11, 2019 @ 12:50pm

You need to specify the Uri properly. Use the namespace URL not V20.

oSoap.cExtraEnvelopeAttributes = 'xmlns:v20="http://services.discretewireless.com/v2009_1/"'
oSoap.cMethodNameSpaceURI = "http://services.discretewireless.com/v2009_1/"
oSoap.cMethodNameSpace = "v20"

The better choice probably is the West Wind Web Service Proxy Generator which can create service wrappers for you:

West Wind Web Service Proxy Generator

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 15, 2019 @ 11:10am

Rick,

Thanks. I've now gotten it formatted correctly but I can't get the SOAP object to call it right. Let me explain.

Basically, if I do:

oSoap = CREATEOBJECT('wwSoap')
oWSDL = oSoap.parseServiceWSDL('http://services.discretewireless.com/v2009_1/AuthenticationManagerV1.wsdl')

oSoap.cExtraEnvelopeAttributes = 'xmlns:v20="http://services.discretewireless.com/v2009_1/"'
oSoap.cMethodNameSpaceURI = "http://services.discretewireless.com/v2009_1/"
oSoap.cMethodNameSpace = 'v20'

oSoap.addParameter('username', otsologin.tsologin)
oSoap.addParameter('password', otsologin.tsopwd)

m.req = oSoap.createSoapRequestXML('getAuthenticationTokenRequest')

m.req looks correct. The problem I run into is, the real method on the WSDL file is getAuthenticationToken, NOT getAuthenticationTokenRequest.

When I do m.resp = oSoap.callWSDLMethod('getAuthenticationToken') I get an error message back that says:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Unexpected element {http://services.nextraq.com/v2009_1/}getAuthenticationToken found.   Expected {http://services.nextraq.com/v2009_1/}getAuthenticationTokenRequest.
</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>

if I try to call oSoap.getWSDLMethod('getAuthenticationTokenRequest'), oSoap.cerrorMsg says 'Invalid Method'

Talking to the company, they swear it has to come through as getAuthenticationTokenRequest even though the method is getAuthenticationToken

How do I make it so I can call oSoap.getWSDLMethod('getAuthenticationToken') but have it use the getAuthenticationTokenRequest XML?

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 16, 2019 @ 02:07am

Scott,

Services like these is exactly why wwSoap is no longer (and hasn't been) supported for a long time. Getting the method and namespace mapping to work with this tooling is extremely tricky if mappings aren't using default namespace mappings. Custom namepsace mappings are doable (most of the time) but the method request has to be massaged into the right format and you have to know what that format looks like in the first place to compare. It requires understanding what the message looks like and how the formatting has to be adjusted which in and of itself is difficult (and I can't remember either without looking this up).

In fact, at this point I don't even remember how to do this with wwSoap. wwSoap was designed in the early days of SOAP when the mappings tended to be simple 1-1 mappings of methods and properties instead of multi-schema mappings into the same method set.

This is why I highly recommend that you use the .NET SOAP client (as described here) or a tool wrapper around it like the West Wind Proxy Generator to handle the .NET bit. The reason is simply that the complexities of the SOAP protocol navigation is handled by a sophisticated and continually maintained Microsoft parser instead of a tool that was essentially designed around SOAP 1.0 and didn't have the resources to keep up with the complex changes that were introduced in SOAP 1.1 and 1.2.

So you can try to make this work with wwSoap, but you're pretty much on your own by looking through the wwSOAP source code to figure out how to format the SOAP envelope to send to the server, or look at the .NET based approach. FWIW, I do a fair bit of Web Service work for customers and in all of the work I've done in the last 10 years or so we've used this .NET approach using both raw .NET components interfaced via COM as shown in the article or using the Proxy wrapper where it works (basically most SOAP 1.x services). It's not always easy either, but it works much more reliably or at the very least can be made to work even in complex scenarios with a bit of .NET code to help, plus if necessary supports even more complex WS* services via usage of WCF. IOW there are many more options with this route that are just not possible with wwSoap.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 16, 2019 @ 07:12am

Rick,

Thanks for the info. I'll look into the .Net / Proxy Generator.

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 16, 2019 @ 08:11am

Rick,

I tried using the Proxy Generator and am wondering if you can point me in the right direction. I created the .dll and the .prg files and am having a problem implementing them. I am getting back a message about 'method not found.' I found in your troubleshotting (https://west-wind.com/wsdlgenerator/docs/_3680sgboj.htm) that it says 'To troubleshoot this problem go into Reflector and examine the exact method signature of the method you are trying to call.'

I look at the reflector and it shows:

On the fox side, how do I create that object type using the dot net bridge? I can see the class in the .cs file the Proxy Generator created, but I have no idea how to implement it...

This is the code from the .cs file for the type I believe I'm supposed to be passing to my method:

[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.6.1590.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://services.nextraq.com/v2009_1/")]
    public partial class getAuthenticationTokenRequest {
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=0)]
        public string username;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=1)]
        public string password;
    }

How do I properly use this class in the VFP side?

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 20, 2019 @ 04:50am

Sorry for the delay in replying - I'm travelling with terrible internet access...

You can take a look at this topic in the help file that explains how to call methods with object parameters:

https://west-wind.com/wsdlgenerator/docs/_2km016yg4.htm

There are more detailed topics in the docs as well that talk about specific parameters that need to be passed. The short version is you need to create the objects with loBridge.CreateInstance("namespace.classname") then set the properties and then pass the object(s) to the method that expects it.

loRequest = loBridge.CreateInstance("NexTraqAuthenticationManagerV1Port.getAuthenticationTokenRequest")
loRequest.username = "sekreeet"
loRequest.password = "superExtraSeeekrett"

loResponse = loProxy.getAuthenticationToken(loRequest)

? loResponse && object - check properties in Reflector

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 22, 2019 @ 07:29am

Rick,

No problem on the slow reply. You're normally really quick. Figured you were busy with something.

That worked! Thank you very much for your help. This SOAP project has been kicking our butts for a while and we were finally able to get an auth token.

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 22, 2019 @ 10:03am

Rick,

One more question for you. Not sure if you'll need to connect in to my box for this?

I'm trying to hit a different method now that requires most of the information to be in the header.

The header looks like:

[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.6.1590.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://services.nextraq.com/v2009_1/")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://services.nextraq.com/v2009_1/", IsNullable=false)]
    public partial class DWHeader : System.Web.Services.Protocols.SoapHeader {
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=0)]
        public SenderInfo SenderInfo;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=1)]
        public long GlobalTimeoutMS;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool GlobalTimeoutMSSpecified;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=2)]
        public string TrackingID;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlArrayAttribute(Order=3)]
        [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)]
        public TraceResult[] TraceResults;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=4)]
        public TraceSettings TraceSettings;
    }

I tried to do:

LOCAL oNexTraq
DO NexTraqTrackManagerV1PortProxy

SET STEP ON
oNexTraq = CREATEOBJECT("NexTraqTrackManagerV1PortProxy", "V4")

*** Call your methods and retrieve result
LOCAL oReq, oHeader, oCred
oHeader = oNexTraq.oBridge.createInstance('NexTraqTrackManagerV1PortProxy.DWHeader')
oHeader.Credentials.AuthToken = m.token

But VFP keeps saying that oHeader is null. How do I go about setting these properties?

Where it's multiple nested classes like this will I be able to do something like:

oHeader = oNexTraq.oBridge.createInstance('NexTraqTrackManagerV1PortProxy.DWHeader')

oSender = oNexTraq.oBridge.createInstance('NexTraqTrackManagerV1PortProxy.SenderInfo')
oCred = oNexTraq.oBridge.createInstance('NexTraqTrackManagerV1PortProxy.Credentials')

oCred.authToken = m.token

oSender.Credentials = oCred && Does this work?
oHeader.SenderInfo = oSender && Does this work?

I get that I first need to get the DWHeader thing working first but just wondering how this whole process works.

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 22, 2019 @ 10:43am

Soap Headers require special handling due to the fact they are value types that can't be easily passed to FoxPro.

Check this topic in the help file:

Using SOAP Headers with the Generated Proxy

Yes, for multiple nested classes you can create the classes and assign them to the appropriate parent object properties.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 22, 2019 @ 12:27pm

Rick,

Unfortunately I'm still unable to create the header object.

What I'm doing is:

oNexTraq = CREATEOBJECT("NexTraqTrackManagerV1PortProxy", "V4")

IF !oNexTraq.oBridge.createInstanceOnType(oNexTraq.oService, 'DWHeaderValue', 'NexTraqTrackManagerV1PortProxy.DWHeader')
	* Crap! Couldn't create the object
	SET STEP ON
ENDIF

And it's returning false everytime.

Again, if I look at the .cs file, the class is defined as:

[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "4.6.1590.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://services.nextraq.com/v2009_1/")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://services.nextraq.com/v2009_1/", IsNullable=false)]
    public partial class DWHeader : System.Web.Services.Protocols.SoapHeader {
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=0)]
        public SenderInfo SenderInfo;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=1)]
        public long GlobalTimeoutMS;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool GlobalTimeoutMSSpecified;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=2)]
        public string TrackingID;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlArrayAttribute(Order=3)]
        [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)]
        public TraceResult[] TraceResults;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute(Order=4)]
        public TraceSettings TraceSettings;
    }

Is there anything obvious that I'm doing wrong or do I need to have you connect into my computer and take a look at with me?

Thanks,

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 24, 2019 @ 04:06am

You should check the error message after the call to .CreateInstanceOnType().

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 24, 2019 @ 04:06am

You should check the error message after the call to .CreateInstanceOnType().

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 25, 2019 @ 06:26am

Rick,

The error message I get back is:

Type not loaded. Please load call LoadAssembly first.

I would understand this if I had written the .dll and was using the .net bridge to call it but it looks like the init of the class your proxy generator created is calling oBridge.loadAssembly.

****************************************
FUNCTION Init(lcDotNetVersion,llNoLoad)

IF !llNoLoad
	IF !this.LoadService(lcDotNetVersion)
		ERROR this.cErrorMsg
	ENDIF
ENDIF	

ENDFUNC
* Init

************************************************************************
*  LoadService
****************************************
***  Function: Loads an instance of the .NET Runtime if not loaded
***            and loads wwDotnetBridge .NET component into the runtime.
***    Assume: .NET is available
***            Failure will throw a FoxPro error
***      Pass: lcDotnetVersion
***               "V2","V4" or explicit .NET version
***               defaults to V2
***    Return: .T. or .F.
************************************************************************
FUNCTION LoadService(lcDotNetVersion)
LOCAL loBridge as wwDotNetBridge

IF ISNULL(this.oBridge)
	THIS.oBridge = CREATEOBJECT("wwDotNetBridge",lcDotNetVersion)
	IF !this.oBridge.Loadassembly(this.cAssemblyPath)
	   this.cErrorMsg = this.oBridge.cErrorMsg
	   RETURN .F.
	ENDIF   
ENDIF

IF ISNULL(this.oService)
	this.oService = this.oBridge.CreateInstance(this.cComClass)
	IF ISNULL(this.oService)
	   this.cErrorMsg = this.oBridge.cErrorMsg
	   RETURN .F.
	ENDIF

	*** For some reason accessing URL directly doesnt work - use indirect referencing
	*this.oService.Url = this.cServiceUrl
	this.oBridge.SetProperty(this.oService,"Url",this.cServiceUrl)
ENDIF

RETURN .T.
ENDFUNC
*   LoadService

So if I'm understanding correctly, load assembly should be done when I do:

oNexTraq = CREATEOBJECT("NexTraqTrackManagerV1PortProxy", "V4") && Load assembly done here

IF !oNexTraq.oBridge.createInstanceOnType(oNexTraq.oService, 'DWHeaderValue', 'NexTraqTrackManagerV1PortProxy.DWHeader')
	* Crap! Couldn't create the object
	SET STEP ON
        * oNexTraq.oBridge.cErrorMsg = 'Type not loaded. Please load call LoadAssembly first.'
ENDIF

Is there something I'm missing? I'm open to having you just connect into my computer and take a look at it with me if it's not something obvious that I'm missing that you can see right off.

Scott

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 25, 2019 @ 06:35am

Make sure the signature is correct. Case, and exact spelling...

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 25, 2019 @ 08:22am

Rick,

Thanks. I did have a type... I feel stupid saying this but now I'm getting a different error.

I am now making it to the .setPropertyEx() and I'm getting an error that shows up in a VFP window. I glanced at the help doc / FAQs and didn't see anything...

What I'm doing is:

oNexTraq = CREATEOBJECT("NexTraqTrackManagerV1PortProxy", "V4")

LOCAL oReq, oHeader, oCred, oSenderInfo
* Full header object heirachy is DWHeader.SenderInfo.Credentials.AuthToken
m.headerVar = 'CPHeader' && 'DWHeaderValue' && 'CPHeader'
m.senderVar = 'CPSenderInfo' && m.headerVar + '.SenderInfo'
m.credVar =  'CPCred' &&m.senderVar + '.Credentials'

IF !oNexTraq.oBridge.createInstanceOnType(oNexTraq.oService, m.credVar, 'NexTraqTrackManagerV1Port.Credentials')
	* Crap! Couldn't create the object
	oRet.ErrorMsg = oNexTraq.oBridge.cErrorMsg
	RETURN oRet
ENDIF 

oNexTraq.oBridge.setPropertyEx(oNexTraq.oService, m.credVar + '.AuthToken', oToken.token) && THIS LINE CAUSES THE ERROR

and the error message is

OLE IDispatch exception code 0 from wwDotNetBridge: Index was outside the bounds of the array...

I've double checked and the Credentials property is 'AuthToken' (caps, spelling, etc. is correct)

Is there something else I need to be doing?

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Rick Strahl
  Scott R
  Jul 25, 2019 @ 08:52am

Not sure... I can't see the whole service definition.

The index error usually means that the property is not being found.

Based on what you've posted here I can't see that that your service actually requires a SOAP header. The methods don't have an associated SOAP Header and I don't see the header definition on the actual service object. There's no CPCred property on the service object you showed earlier which would explain why you can't create an instance for it.

Assuming you actually need the header, first thing you should check is to make sure the header is set by printing out the object value and it should say (Object). Also make really sure the property names you are using are correct down the tree.

Looking at what you have it looks like loProxy.oService.CPCred.AuthToken which seems incorrect (but I'm not sure). CPCred doesn't sound like a typical property name for a header object, but then again that's up to the service creators. Just make sure that's what you see in the Reflector methods that you are calling.

SOAP headers are a pain in the butt in any SOAP client implementation because they are not directly part of the schema, so you have to know what the schema defines by digging into the definition.

Maybe post the generated C# proxy at the end of your message (if its not sensitive). That might make it easier for me to actually see what you're calling.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwSoap Help
  Scott R
  Rick Strahl
  Jul 25, 2019 @ 10:19am

Rick,

Thanks! Your comments put me on the right path. Got it working.

Thanks again!

Scott

© 1996-2024