The Generate Class from WSDL tool from Salesforce doesn't always fully create a working Apex Class
for you to connect to your Web Service with. Personally, I almost always have to fix the generated Apex Class (from the WSDL).
To deduce what variables you need for your classes, you need to see the actual XML response from your Web Service. I suggest using a program like SoapUI to see these request/responses.
Doing this will help you troubleshoot your error -- the error message suggests to me that you are receiving more parameters than you are accounting for hence throwing the error (are you sure you know what parameters you're supposed to get [among all cases -- ex. when you get a "failed" response from your Web Service]).
And in addition to helping troubleshooting this problem, you will need to write Test Code
for the Apex Class
you just created from your WSDL. To do this, you'll need to create Mock Responses Test Classes
where you simulate a response from your Web Service.
For example, I use a Web Service where I have one response for a login (where the WS gives me a SessionID
), and then I have another response where I get an Activity File from my WS. And then I also have third response when I try to get an Activity File with an invalid SessionID
. So, for this one Web Service, I need 3 Mock Responses Test Classes
.
Looking at the Partner WSDL, the soapAction for the login operation is the empty string.
<operation name="login">
<soap:operation soapAction=""/>
<!-- .... -->
</operation>
So you would think that it should be added to the request as such:
HttpRequest req = new HttpRequest();
// ...
req.setHeader('SOAPAction', '');
However, as you found, passing the empty string to the SOAPAction header in the HTTPRequest results in the following response:
System.HttpResponse[Status=Server Error, StatusCode=500]
It would appear that the Apex HttpRequest.setHeader(string, string) won't send an empty or white-space only header. Without this header Salesforce rejects it as an invalid SOAP request.
After a bit of experimenting, it turns out the Salesforce Partner API doesn't really care what SOAPAction you send to it, as long as it is defined. Try it out with the following code and vary the value sent.
string sessionId = UserInfo.getSessionId();
HttpRequest req = new HttpRequest();
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/Soap/u/31.0');
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
req.setHeader('SOAPAction', 'Wololo');
req.setBody('<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Header><SessionHeader xmlns="urn:partner.soap.sforce.com"><sessionId>'+sessionId+'</sessionId></SessionHeader></env:Header><env:Body><query xmlns="urn:partner.soap.sforce.com"><queryString>Select Id, Name from Account limit 1</queryString></query></env:Body></env:Envelope>');
//System.debug(req.getBody());
Http http = new Http();
HTTPResponse res = http.send(req)
Most values I've tried work, except for the empty string (''
) and whitespace only (' '
).
From the SOAPAction HTTP Header Field link in Keith's answer:
An HTTP client MUST use this header field when issuing a SOAP HTTP Request.
and
The presence and content of the SOAPAction header field can be used by servers such as firewalls to appropriately filter SOAP request messages in HTTP. The header field value of empty string ("") means that the intent of the SOAP message is provided by the HTTP Request-URI. No value means that there is no indication of the intent of the message.
So again, you need to define it. And in this case Salesforce doesn't use the value.
Best Answer
You can hand code your own manual XML POST callout using
HttpRequest
. It will be on you to build up the required XML and then parse the response.Alternatively, if you have a WSDL to work from, I created an alternative version of WSDL2Apex.
When you import the WSDL you are presented with a list of methods to import. At that stage you can opt to generate the Apex required to perform the HttpRequest.