Web Services Interoperability: tips and tricks for consuming heterogeneous Web Services from Visual Studio

In the projects I’ve been involved in lately, we consumed a bunch of Web Services built by third parties, using different Web Services tools and frameworks. We faced some issues in referencing them at first try, but we were able to overcome these issues relatively easily - special thanks to my colleague Matt Long from MCS US Central for help in diagnosing the issues!

So it looked worth blogging…

Disclaimer

The information contained in this article comes from real integration projects. It solved the issues we faced, however neither the author nor Microsoft can make any guarantee about it resolving similar issues in other contexts.

By the way, why is this not working out of the box?

Since the very early days of Visual Studio .Net and the .Net framework, Microsoft vision was to make the use of Web Services as transparent as possible in the programming model of .Net languages.

To implement this vision, Visual Studio relies on a description of the Web Services, the WSDL, to generate a proxy class – this is what happens when a developer adds a Web Service Reference to one of his projects. This proxy class is the programmatic object against which the developers are writing code, and then the .Net framework will handle the details of the SOAP calls at run time. This is explained in details in the Visual Studio documentation, if you want more information you may start here: http://msdn.microsoft.com/en-us/library/tydxdyw9.aspx

From a programming model perspective, accessing a Web service like any instance of any class is just great. However, one side effect is that Visual Studio needs to find all the required information in the WSDL, so it can generate the appropriate proxy. Bad news is, it turns out that some other Web Services tools and frameworks don’t provide all of that information. Good news is, if you add the missing information yourself, then everything will work fine!

A simple sample

Let’s start with a sample WSDL build from a real project experience. Let’s say this WSDL describes a service for creating a customer account in a remote system. Input parameters are the customer name and the country where the customer lives. It returns a result code, a result message, and an array containing the names and phone numbers for 3 contacts.

This is prototyped by the following series of interfaces:

namespace Contoso.Services

{

    /// <summary>

    /// Describes the service interface

    /// </summary>

    public interface ICustomerAccountService

    {

        /// <summary>

        /// Create a customer account using the name and country provided

        /// </summary>

        /// <param name="name">name of customer</param>

        /// <param name="country">customer country</param>

        /// <returns>a result code, a result message and an array of contacts</returns>

        ICreateCustomerAccountOutput Create(string name, string country);

    }

    /// <summary>

    /// Output parameters for customer creation

    /// </summary>

    public interface ICreateCustomerAccountOutput

    {

        bool ResultCode { get; }

        string Message { get; }

        ICustomerContact[] Contacts { get; }

    }

    /// <summary>

    /// Contact information returned after Customer creation

    /// </summary>

    public interface ICustomerContact

    {

        string Role { get; }

        string Name { get; }

        string Phone { get; }

    }

}

First WSDL implementation

From my experience, some Web Services tools and frameworks may generate a WSDL as simple as the following to describe this as a Web service:

<?xml version='1.0' encoding='UTF-8'?>

<!-- First sample WSDL -->

<definitions name="CustomerAccount_Definitions" targetNamespace="http://services.contoso.com/CustomerAccount" xmlns:typens="http://services.contoso.com/CustomerAccount" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">

    <message name="CustomerAccount_Create_Request">

        <part name="Name"/>

        <part name="Country"/>

    </message>

    <message name="CustomerAccount_Create_Response">

        <part name="ResultCode"/>

        <part name="Message"/>

        <part name="CustomerContact"/>

    </message>

    <portType name="customerPortType">

        <operation name="Create">

            <input message="typens:CustomerAccount_Create_Request"/>

            <output message="typens:CustomerAccount_Create_Response"/>

        </operation>

    </portType>

    <binding name="customerBinding" type="typens:customerPortType">

        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

        <operation name="Create">

            <soap:operation soapAction="urn:customerAction"/>

            <input>

                <soap:body namespace="http://services.contoso.com/CustomerAccount" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

            </input>

            <output>

                <soap:body namespace="http://services.contoso.com/CustomerAccount" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>

            </output>

        </operation>

    </binding>

    <service name="customerService">

        <port name="customerPort" binding="typens:customerBinding">

            <soap:address location="http://services.contoso.com:8080/CustomerAccount"/>

        </port>

    </service>

</definitions>

While this WSDL can be referenced in Visual Studio, it doesn’t contain enough information for the appropriate proxy class to be generated. Instead, the Create method will be degraded to:

void Create();

If you look at the warnings generated during compilation, you will notice 2 of them, both showing the following message:

Custom tool warning: The optional WSDL extension element 'body' from namespace 'http://schemas.xmlsoap.org/wsdl/soap/' was not handled.

Looking at the XPath, you would notice one applies to the input part above, and the other applies to the output.

So Visual Studio warns us that it only generated a void method!

Second WSDL implementation

Most obvious issue here is the lack of types, so after discussing with the editor of the Web service you may get typed WSDL, which has changes in the message sections like these ones (changes highlighted in yellow):

<?xml version='1.0' encoding='UTF-8'?>

    <message name="CustomerAccount_Create_Request">

        <part name="Name" type="xsd:string" />

        <part name="Country" type="xsd:string" />

    </message>

    <message name="CustomerAccount_Create_Response">

        <part name="ResultCode" type="xsd:bool" />

        <part name="Message" type="xsd:string" />

        <part name="CustomerContact" type="xsd:any" />

    </message>

This is better, but yet not what we want – we have no more warnings, but actually our Create method has been generated to:

string Create(out string Message , out string CustomerContact , string Name , string Country)

Basically everything has been converted to string – so what should we do to get a fully typed generation?

Third WSDL implementation

Here is a solution that fit the needs – I will then discuss the different items and the impact of their missing:

<?xml version='1.0' encoding='UTF-8'?>

<!-- Third sample WSDL -->

<definitions name="CustomerAccount_Definitions" targetNamespace="http://services.contoso.com/CustomerAccount" xmlns:typens="http://services.contoso.com/CustomerAccount" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://services.contoso.com/CustomerAccount">

    <!-- This section defines complex types -->

    <types>

        <xsd:schema targetNamespace="http://services.contoso.com/CustomerAccount" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://services.contoso.com/CustomerAccount" >

            <xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />

            <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" schemaLocation="http://schemas.xmlsoap.org/soap/encoding/" />

            <xsd:complexType name="respType">

                <xsd:sequence>

                    <xsd:element name="ResultCode " type="xsd:boolean" />

                    <xsd:element name="Message" type="xsd:string" />

                    <xsd:element name="Contacts" type="tns:ArrayOfCustomerContact" />

                </xsd:sequence>

            </xsd:complexType>

            <xsd:complexType name='ArrayOfCustomerContact'>

                <xsd:complexContent>

                    <xsd:restriction base='soapenc:Array'>

                        <xsd:attribute ref='soapenc:arrayType' wsdl:arrayType='tns:CustomerContact[3]'/>

                    </xsd:restriction>

                </xsd:complexContent>

            </xsd:complexType>

            <xsd:complexType name='CustomerContact'>

                <xsd:sequence>

                    <xsd:element name='Role' type='xsd:string'/>

                    <xsd:element name='Name' type='xsd:string'/>

                    <xsd:element name='Phone' type='xsd:string'/>

                </xsd:sequence>

            </xsd:complexType>

        </xsd:schema>

    </types>

    <message name="CustomerAccount_Create_Request">

        <part name="Name" type="xsd:string" />

        <part name="Country" type="xsd:string" />

    </message>

    <message name="CustomerAccount_Create_Response">

        <part name="CustomerContact" type="tns:respType" />

    </message>

    <!-- portType, binding and service sections left unchanged, removed for clarity -->

</definitions>

So here we get our Create method generated like this:

respType Create(string Name , string Country)

with respType generated like this (some code removed for clarity):

[System.Xml.Serialization.SoapTypeAttribute(Namespace="http://services.contoso.com/CustomerAccount")]

    public partial class respType

{

        public bool ResultCode {

            get { return this.resultCodeField; }

            set { this.resultCodeField = value; }

        }

       

        public string Message {

            get { return this.messageField; }

            set { this.messageField = value; }

        }

       

        public CustomerContact[] Contacts {

            get { return this.contactsField; }

            set { this.contactsField = value; }

        }

       

    }

   

    [System.Xml.Serialization.SoapTypeAttribute(Namespace="http://services.contoso.com/CustomerAccount")]

    public partial class CustomerContact

{

        public string Role {

            get { return this.roleField; }

            set { this.roleField = value; }

        }

       

        public string Name {

            get { return this.nameField; }

            set { this.nameField = value; }

        }

       

        public string Phone {

            get { return this.phoneField; }

            set { this.phoneField = value; }

        }

Now we have a WSDL that can be referenced using Visual Studio!

Common error messages

In trying to get the appropriate WSDL, we came across a few error messages; they are listed here with the action that enabled us to correct the situation.

Errors in imported schemas declaration

Namespace 'xxx' is not available to be referenced in this schema.

This message appears when the WSDL is opened in Visual Studio.

It means the xsd:import directive is missing from the xsd:schema section – adding the directive for the appropriate namespace should solve this issue.

Undefined complexType 'http://schemas.xmlsoap.org/soap/encoding/:Array' is used as a base for complex type restriction.

This message appears when the WSDL is opened in Visual Studio.

It means that Visual Studio doesn’t know where to retrieve the schema definition – VS comes with some schema definitions, but other Web Services tools and frameworks may use different schemas. So you need to help Visual Studio to locate those definitions. This can be down in 2 different ways: either you specify a schemaLocation attribute for the appropriate xsd:import directive, or you save it to the folder where Visual Studio stores its schemas, located under %ProgramFiles%\Microsoft Visual Studio 9.0\Xml\Schemas if you are using Visual Studio 2008 and are in the common configuration.

Errors in type definition

If you have errors in type definitions, Visual Studio may not generate the proxy class for you when you add the reference. Try using wsdl.exe from the command line.

You may get the following error message:

Custom tool error: Unable to import WebService/Schema. Unable to import binding 'customerBinding' from namespace 'http://services.contoso.com/CustomerAccount'. Unable to import operation 'Create'. The datatype ' http://schemas.xmlsoap.org/wsdl/:respType' is missing.

This is typically what happens when you try to declare a type outside of the schema section, as in the following fragment:

    <message name="CustomerAccount_Create_Response">

        <xsd:complexType name="respType">

            <xsd:sequence>

                <xsd:element name="ResultCode " type="xsd:boolean" />

                <xsd:element name="Message" type="xsd:string" />

            </xsd:sequence>

        </xsd:complexType>

        <part name="CustomerContact" type="respType" />

    </message>

Other approaches to solve the same issue

You may think, there are other approaches to solve the same issue – maybe they would be more efficient? Here are those we evaluated.

Do your own serialization

Starting from the second WSDL, one may be tempted to build his own serialization, and stay at the string level for the WSDL.

While this approach may work, we eliminated it for the following reasons: First, it might be complicated to match the serialization required by the Web service; second, we found it better to have a WSDL that precisely describes the contract between the service and its clients; third, it is easier to have developers from different communities communicate on WSDL, rather than on serialization details.

Build your own Web service and use its WSDL instead

With Visual Studio, it is easy to create a Web service that match the same interface; then VS will generate the WSDL automatically – so this WSDL should be ok for the other Web service shouldn’t it?

Well, we also eliminated this approach for reasons close to the previous one – this is a communication issue: the beauty of WSDL is that developers from various environments can discuss the contract; however providing a full blown contract to developers from the other environments and asking them to build a Web service that match this contract turned out to be more complex than starting from the simple WSDL and filling the gaps.

Conclusion

In the future, we’ll probably see a greater normalization on WSDL usage, that will enable seamless integration of Web services and their clients made from heterogeneous Web Services tools and frameworks.

For the present, it still may raise some issues. However, in our case, simply by modifying the WSDL of the Web services we were consuming, we were able to overcome the incompatibilities. Hopefully the information published here will help developers facing the same issues.