Friday, January 23, 2009

Java web service interoperability from .NET CF 1.0

I don’t suppose anyone else is developing in .NET CF 1.0 now in 2009. Well those are the constraints for project I’m involved in and I prefer to think about this as a challenge :)

Anyway current topic is interoperability with Java web services. This causes a lot of problems in .NET CF 1.0, some of them weren’t addressed until .NET CF 3.5. In my case it started with inability to generate web service client proxy using standard wsdl.exe tool.

In this example we have a Java web service for querying cars by name. It returns car description with name and collection of car parts. Each car parts can have a set of string attributes. The model looks like this:

image

  Let’s say we have WSDL like this one:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="ws" targetNamespace="http://tempuri.org" 
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
                  xmlns:tns="http://tempuri.org" 
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
    <xsd:schema targetNamespace="http://tempuri.org" xmlns:pref="http://tempuri.org/to">
        <xsd:import namespace="http://tempuri.org/to" schemaLocation="myschema.xsd" />
        <xsd:element name="getCarDesc">
            <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="carQuery" type="pref:CarQuery" maxOccurs="1" minOccurs="1" />
          </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
        <xsd:element name="getCatDescResponse">
            <xsd:complexType>
                <xsd:sequence>
                    <xsd:element name="response" type="pref:Car" maxOccurs="1" minOccurs="0" />
                </xsd:sequence>
            </xsd:complexType>
        </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="carRequest">
    <wsdl:part element="tns:getCarDesc" name="parameters"/>
  </wsdl:message>
  <wsdl:message name="carResponse">
    <wsdl:part element="tns:getCarResponse" name="parameters"/>
  </wsdl:message>
  <wsdl:portType name="ws">
    <wsdl:operation name="getCar">
      <wsdl:input message="tns:carRequest"/>
      <wsdl:output message="tns:carResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="wsSOAP" type="tns:ws">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getCar">
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="ws">
    <wsdl:port binding="tns:wsSOAP" name="wsSOAP">
      <soap:address location="http://myhost/get-cat-web/ws" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

There is one service and it has one method, getCar. This method takes CarQuery object as a parameter and returns a Car object as response. The WSDL refers to external schema in myschema.xsd file, which contains type definitions.

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
        targetNamespace="http://tempuri.org/to" 
        xmlns:tns="http://tempuri.org/to" 
        elementFormDefault="qualified"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="1.0">

    <complexType name="CarPart">
        <sequence>
            <element name="attribute" type="string" maxOccurs="unbounded" minOccurs="1" />
        </sequence>
    </complexType>

    <complexType name="Car">
        <sequence>
          <element name="name" type="string" maxOccurs="1" minOccurs="1" />
          <element name="parts" type="tns:CarPart" maxOccurs="unbounded" minOccurs="0" />
        </sequence>
    </complexType>

    <complexType name="CarQuery">
        <sequence>
            <element name="name" type="string" maxOccurs="1" minOccurs="1" />
        </sequence>
    </complexType>
</schema>

This works perfectly in Java and is used by Java guys to generate a web service. When you try to feed this to wsdl.exe, the only thing it has to say is:

Error: Unable to import binding 'wsSOAP' from namespace 'http://tempuri.org'.
 - Unable to import operation 'getCar'.
 - The operation binding 'getCar' from namespace 'http://tempuri.org' had invalid syntax.
Missing soap:operation binding.

At least wsdl.exe is being quite specific about this one. I went to validate my WSDL at this site and it confirmed, that I lack soap:operation tag. So I added it and specified operation URI:

    <wsdl:operation name="getCar">
      <soap:operation soapAction="http://tempuri.org/ws/getCar" style="document"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>

The wsdl.exe finished successfully and generated proxy class along with used types. This however was not the end of my struggle. It turned out, that the generated code won’t compile int .NET CF 1.0 as there is no support for Form property of XmlElementAttribute class. The web service method was decorated with this attribute in following way:

[return: System.Xml.Serialization.XmlElementAttribute("response", 
    Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Car getCar(
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    CarQuery carQuery) 
{
    ...
}

It’s really strange that this tool would generate code that won’t compile (and yes, the result was the same when created using Add Web Reference in Smart Device project). I went to look for solution on the web and came up with this modification to WSDL (added elementFormDefault attribute):

...
<wsdl:types>
    <xsd:schema elementFormDefault="qualified" 
                targetNamespace="http://tempuri.org" xmlns:pref="http://tempuri.org/to">
...

This removed all usages of the Form property of XmlElementAttribute class. The code would now compile, but looked a little different than I expected. Take a look at the Car class:

public class Car {
    
    private string nameField;
    
    private string[][] partsField;
    
    /// <remarks/>
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField = value;
        }
    }
    
    /// <remarks/>
    [System.Xml.Serialization.XmlArrayItemAttribute("attribute", typeof(string), IsNullable=false)]
    public string[][] parts {
        get {
            return this.partsField;
        }
        set {
            this.partsField = value;
        }
    }
}

There’s no CarPart object collection. There was no CarPart class in generated code at all. Instead, the tool generated string[][] array for me. Well it’s not what I would like to have, but I could live with it. So I went on to code that test the web service and to my surprise the constructor of the proxy class threw FileNotFoundException as the application was unable to find a dynamically created assembly used by XML serializer. I tried to do the same with Visual Studio 2008 and here’s what I got:

InvalidOperationException was unhandled
Unable to generate a temporary class (result=1).
error CS0029: Cannot implicitly convert type 'string' to 'string[]'

Not only the wsdl.exe changed the object model, but also the XML Serializer was unable to deal with it. At this point I decided to manually modify the generated code and added the CarPart class. I also changed parts collection to CarPart[] type. And it finally worked. Yet I feel disappointed that I have to manually create proxy classes to work with Java web services.

A whole different thing is performance of web services on the smart device. SOAP and XML imply large message sizes and extensive use of reflection, which has a great impact on the application’s performance.

DotNetKicks Image

1 comment:

valerik said...

Thank you for taking time to explain this.

Step where I had to move soap:operation fixed my problem.

Share