Pages

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:

11 comments:

  1. hi

    i got unable to find schema for element.
    how can i solve this problem

    ReplyDelete
  2. Hello Daniel,

    I am getting the following error, let me know if you have any solution for this

    Error: Failed to parse wsdl: Unable to find schema for element; {http://www.w3.org/2001/XMLSchema}schema


    Thanks in advance!!

    --
    Arish

    ReplyDelete
    Replies
    1. Hi Arish,

      Most likely your WSDL contains a "schema" element. Have a look at the Supported WSDL Features of WSDL2Apex.

      Cheers,
      Daniel

      Delete
  3. Thanks for the post. Helped a lot in generating my first apex class from .net wsdl file. I have a query.

    Along with the wsdl file, I have been provided with a dll(Miscrosoft.ServiceBus.dll) file which has some bindings to be used. I haven't used this to generate the code. When I try to get the output, I get the following error:

    Cannot send message because the service endpoint hosted at the specified address uses a binding that cannot be accessed over HTTP.

    So, how do I use this dll file and bind it to the WSDL file to generate the apex class? The binding need to be included in the generated class also.

    Please let me know.

    Thanks.

    ReplyDelete
    Replies
    1. Hi "Man"ish,

      You won't be able to utilise a .NET DLL directly in Salesforce apex.

      If I had to guess from the error message you will probably need to use https rather than http.

      Try changing the endpoint_x in the wsdl2apex generated class.

      Failing that, see if you can get a sample SOAP message from a valid call to the web service and compare that to what Salesforce is generating.

      Cheers,
      Daniel

      Delete
  4. Hi,

    I am getting this error. Can someone tell why is this happening and how to fix this?

    Apex Generation Failed
    Unsupported WSDL. Found more than one part for message setCurrentScopeById

    ReplyDelete
    Replies
    1. Have a look at http://salesforce.stackexchange.com/questions/4000/importing-ups-street-address-wsdl-into-apex. The WSDL most likely contains multi-part messages. You may be able to manually manipulate the WSDL to still import it with wsdl2apex.

      Delete
  5. hey..
    i am getting an error
    Apex Generation Failed
    Unable to find schema for element; {http://www.w3.org/2001/XMLSchema}double

    so cud u plz give ant solution..
    although i have gone thru ol the supported wsdl constructs..yet not able to import even a single external wsdl file...
    here is my document plz have a look
    https://docs.google.com/file/d/0B_MSnsQCP3eeZFRiREhOdllzRVk/edit

    ReplyDelete
    Replies
    1. It's hard to day without seeing the schema. The schema link you provided is for a binary download, which I'm not game to try because of the potential security risk.

      http://salesforce.stackexchange.com would be a good place to post the question and the problem schema details.

      Delete
  6. Nice post. what will you advice for a requirement implemented using 'Outbound Messages' which ensures of retrying and delivering the message.
    However how to handle and show the error on SF GUI thats sent by external system, due to some data error (as data is not saved in external system) , As this failure is data error, outbound monitor message shall not retry to send.Any advice will help.

    ReplyDelete
    Replies
    1. When you are processing the outbound message you can get the Salesforce Session ID and ServerURL. You can use these to write data back to Salesforce. This could include any problem records that need manual follow up.

      Delete