Friday, March 4, 2011

Consuming an ASP.NET Web Service from Salesforce Apex Callouts

Salesforces wsdl2apex can be a bit basic in what is supports (see Supported WSDL Features).

Steps for consuming an ASP.NET web service from Apex.

  1. Add the Web service host to the Remote Sites.
    Adminitraction Setup > Security Controls > Remote Site Settings
    Skipping this step will result in a "System.CalloutException: IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint ="...
  2. Export the WSDL from the ASP.NET web service
  3. Optionally, you might like to sort the WSDL. The idea is to ensure wsdl2apex produces the same elements in the same order for future code comparisons.
  4. Import the WSDL into Salesforce
    Develop > Apex Classes > Generate from WSDL*
  5. Define a Apex class name
  6. Test the new Apex class using Execute Anonymous in Eclipse. With a ASP.NET generated web service I found the class I defined in the previous step had a nested class corresponding to the binding name from the uploaded WSDL. The nested class had a method that corresponded to the web method (the operation in the WSDL).
  7. Optionally, search the generated class for calls to WebServiceCallout.invoke(...). This will currently cause test cases to abort silently (except for a log message) and you may not notice. Sprinkle the code with a liberal amount of Test.isRunningTest().

*Common WSDL parsing errors:

  1. If the Parse WSDL button produces Error: Failed to parse wsdl: Found more than one wsdl:binding. WSDL with multiple binding not supported Deleting the second <wsdl:binding> element what contains soap12 elements and the corresponding port definition under <wsdl:wervice>.
  2. wsdl:import
    Error: Failed to parse wsdl: Unknown element: import
    Locate the import elements in the current WSDL. E.g. . Pull that WSDL down and merge its contents into the parent WSDL.
  3. xsd:import
    Failed to parse wsdl: Found schema import from location http://example.com/WebService.svc?xsd=xsd0. External schema import not supported
    These will typically appear under <wsdl:types>. Copy the contents of the import under <wsdl:types> and remove the <xsd:schema>. See also: Force.com Discussion Forum: Referencing external schemas in a WSDL
  4. Error: Failed to parse wsdl: Failed to parse WSDL: Unable to find binding {http://tempuri.org/}BasicHttpBinding_IWebService. Found BasicHttpBinding_IWebService instead.
    The <wsdl:definitions> had the attribute xmlns:i0="http://tempuri.org/". i0: was being used as the namespace for the binding. Switching it to tns: resolved the issue.
  5. Error: Failed to parse wsdl: schema:targetNamespace can not be null
    In this case I needed to add a targetNamespace attribute and value to the element. E.g. <xsd:schema> becomes <xsd:schema targetNamespace="http://www.example.com/api/Imports">
  6. Apex Generation Failed:
    Unable to find schema for element; {http://schemas.microsoft.com/2003/10/Serialization/Arrays}ArrayOfstring
    and
    Unable to find schema for element; {http://schemas.microsoft.com/2003/10/Serialization/Arrays}ArrayOflong
    In this case I'd initially made a mistake when pulling in the xsd for these complex types. I corrected it by putting the complex type definitions within a <xsd:schema targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/Arrays">element in wsdl:type section.
  7. Error: wwwExampleComApi
    Error: Identifier name is reserved: long at 191:23

    This error was caused by the ArrayOflong type generating the following Apex:
    public class ArrayOflong {
        public Long[] long;
        private String[] long_type_info = new String[]{'long','http://www.w3.org/2001/XMLSchema','long','0','-1','false'};
        private String[] apex_schema_type_info = new String[]{'http://www.Example.com/api','false','false'};
        private String[] field_order_type_info = new String[]{'long'};
    }
    
    Possible fix:
    public class ArrayOflong {
        public Long[] long_x; // Note the _x suffix to avoid the conflict
        // The first parameter here is what will appear in the SOAP message as the element name
        private String[] long_type_info = new String[]{'long','http://www.w3.org/2001/XMLSchema','long','0','-1','false'};
        private String[] apex_schema_type_info = new String[]{'http://www.Example.com/api','false','false'};
        private String[] field_order_type_info = new String[]{'long_x'};
    }
    
  8. Error: Failed to parse wsdl: Unsupported Schema element found http://www.w3.org/2001/XMLSchema:attribute. At: 1866:57
    The WSDL had three "attribute" elements:
    • <xs:attribute name="FactoryType" type="xs:QName"/>
    • <xs:attribute name="Id" type="xs:ID"/>
    • <xs:attribute name="Ref" type="xs:IDREF"/>
    Commenting these out of the WSDL allowed into to be used with wsdl2apex. I'm not sure what adverse effects resulted from removing them.
  9. The use of <xsd:extension base="foo"> in a complex type won't result in the base class fields appearing in generated Apex class.
    Try copying the fields from Apex class that was being extended into the sub class.

See also: