Interoperability With Patterns and Strategies for Document-Based Web Services: Part 2 in a Series

   
By Sameer Tyagi, January 27, 2005  

In a previous article, we explored different patterns and strategies that can be used to build document-driven web services using J2EE technologies, along with their advantages and disadvantages. An important part of developing a document-driven web service is describing it efficiently. So, we also looked at best practices from a WSDL design perspective; namely how to go about separating the data types, and the abstract, concrete parts of the WSDL. We also covered some of the strategies and the considerations that developers should keep in mind when implementing web service endpoints that process the web service messages asynchronously.

Readers responded with a lot of feedback. Most of the questions were about interoperability with Microsoft .NET and how some of the described strategies can be employed in real world applications that cross platform boundaries. Microsoft announced C# somewhere in July 2000, as a part of the much larger .NET platform. The .NET platform is a development framework that provides a new way to create Microsoft Windows-based applications and web services. Within .NET, a few different product groups can be identified. These include the development tools (Visual Studio.NET, the .NET framework) and new programming languages like C#, VB.NET , their class libraries and the Common Language Runtime (CLR); the server products (BizTalk Server, IIS) and services (Hailstorm, Passport.NET). More details about .NET can be found at the Microsoft website.The C# language is surprisingly similar to Java in terms of types, statements, expressions,exceptions and language constructs.

In this article, we examine some of the strategies discussed earlier, and demonstrate interoperability with C#. To run the example code discussed below, you must first build and deploy the web services. Details on how to do that, along with the sample code, including Ant build scripts can be found in the previous part. To execute and build the C# code, you need to install the .NET 1.1 framework and the CLR (and contrary to popular belief you don't actually need to install Visual Studio).

The strategies associated with the Document-Based Web Services pattern examined here are:

  • Using XML in the SOAP Body
  • Using a String in the SOAP Body
  • Using base64Encoded or raw bytes in the SOAP body
  • Using the xsd:any Element in WSDL
  • Using the xsd:anyType in WSDL

With the " Using XML in the SOAP Body" strategy, the WSDL uses externalized schemas to describe the documents exchanged but references the respective elements directly. This allows the schemas to be detected by compile time tools and bound into programming language structures. Code Sample 1 and 2 show the abstract and concrete part of the WSDL again. The abstract and concrete WSDLs are defined in different namespaces to avoid any conflicts and keep WSDL parsers' tools happy.

Code Sample 1: Abstract WSDL for the Document-Literal Example

<definitions xmlns:tns="urn:AbstractPOService"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:ns1="urn:PurchaseOrderDocument"
             xmlns:ns2="urn:Status" xmlns:ns3="urn:POProcessingFault"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="urn:AbstractPOService" name="AbstractPOService">
    <types>
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                targetNamespace="urn:AbstractPOService">
            <import schemaLocation="PurchaseOrder.xsd"
                    namespace="urn:PurchaseOrderDocument" id="ns1"/>
            <import schemaLocation="PurchaseOrderStatus.xsd"
                    namespace="urn:Status" id="ns2"/>
            <import schemaLocation="POProcessingProblem.xsd"
                    namespace="urn:POProcessingFault" id="ns3"/>
        </schema>
    </types>
    <message name="IPurchaseOrder_acceptPO">
        <part name="parameters" element="ns1:PurchaseOrderDocument"/>
    </message>
    <message name="IPurchaseOrder_acceptPOResponse">
        <part name="result" element="ns2:Status"/>
    </message>
    <message name="POProcessingProblem">
        <part name="POProcessingProblem" element="ns3:POProcessingFault"/>
    </message>
    <portType name="IPurchaseOrder">
        <operation name="acceptPO">
            <input message="tns:IPurchaseOrder_acceptPO"/>
            <output message="tns:IPurchaseOrder_acceptPOResponse"/>
            <fault name="POProcessingProblem" message="tns:POProcessingProblem"/>
        </operation>
    </portType>
</definitions>

Code Sample 2: Concrete WSDL for the Document-Literal Example

<definitions xmlns:tns="urn:ConcretePOService"
             xmlns:abs="urn:AbstractPOService"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="urn:ConcretePOService" name="POService">
     
                                             <import namespace="urn:AbstractPOService" location="Abstract.wsdl"/>                     
    <binding name="IPurchaseOrderBinding" type="abs:IPurchaseOrder">
        <soap:binding style="document"
              transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="acceptPO">
            <soap:operation/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
            <fault name="POProcessingProblem">
                <soap:fault name="POProcessingProblem" use="literal"/>
            </fault>
        </operation>
    </binding>
    <service name="POService">
        <port name="IPurchaseOrderPort" binding="tns:IPurchaseOrderBinding">
            <soap:address
                  location="http://127.0.0.1:8080/docliteralfromjava/jaxrpc"/>
        </port>
    </service>
</definitions>
                  

To build and deploy the service endpoint and JAX-RPC client, follow the instructions at this link or with the sample code for part 1. Just as the wscompile tool in Java WSDP is used to read the WSDL and generate the client side bindings in the Java programming language, the WSDL description can now be passed to the Microsoft .NET "WSDL tool" wsdl.exe tool in order to generate the client side proxies. The WSDL tool generates C# stub code by parsing the WSDL contract files and XSD schemas. The wsdl.exe creates a single source file based on the best type to use for objects specified in the service description. The documentation for this tool states that "In some cases the tool uses a least-common-denominator approach for casting objects to a type and sometimes the generated type in the proxy class might not be what the developer wants or expects". This behavior is a bit different from wscompile, which uses the mapping criteria defined in the JAX-RPC JSR 101.To use wsdl.exe use the following command.

wsdl /language:CS /protocol:SOAP http://localhost:8080/docliteralfromwsdl/jaxrpc?WSDL

This generates the client side stub or proxy called POService. The C# source file POService.cs contains the structures and serialization rules based on the schema and bindings defined in the WSDL. To use this, a Windows DLL needs to be built out of the generated proxy code using the C# compiler, passing it the referenced DLL's from the .NET framework.

csc /t:library /r:System.Web.Services.dll /r:System.Xml.dll POService.cs

The next step is to write a client (just like we did in Java) and invoke the acceptPO() method in C#. This is shown in Code Sample 3. It can be seen that the names of the methods in the generated C# proxy are identical to the method names used in the JAX-RPC web service and the JAX-RPC client. The C# client code is remarkably similar to the JAX-RPC stub client written earlier due to the syntactic and semantic similarities between the two programming languages. The client code can now be compiled using the C# compiler csc and executed. Under the covers, the C# programs are not really compiled into executable files, they are compiled into Microsoft Intermediate Language (MSIL) files, which the CLR then executes. This behavior is similar to the way Java applications are compiled into bytecode which is executed by the JVM.

csc /t:exe /r:POService.dll JAXClient.cs

Code Sample 3 : C# Client Code using XML in SOAP body

namespace PurchaseOrderConsumer{
//Sample client showing consumption of a Java based Document-Oriented
//Web Service from C# client
class JAXClient {
    // The main entry point for the application.
    [STAThread]
    static void Main(string[] args) {
// Instantiate the stub/proxy
       POService service = new POService();
// Set the endpoint URL to go though our SOAP trace utility
       if (args.Length == 1)
            service.Url = args[0];
       else
       service.Url = "http://localhost:9090/docliteralfromwsdl/jaxrpc";
       System.Console.WriteLine("Invoking JAX-RPC Client on " +
                                 service.Url);
// Create an Address
        Address addrs = new Address();
        addrs.street = "1 Main Street";
        addrs.city = "Boston";
        addrs.state = "MA";
        addrs.zipCode = "01829";
// Create a PurcahseOrder
        PurchaseOrder po = new PurchaseOrder();
        po.createDate = new DateTime(2003, 08, 30);
        po.billTo = addrs;
        po.shipTo = addrs;
        po.poID = "99AA";
// Create some Items
        LineItem[] orderitems = new LineItem[1];
        LineItem item = new LineItem();
        item.itemname = "Copy Machine";
        item.price = 100;
        item.quantity = 10;
        orderitems[0] = item;
// Add the Items to the PurchaseOrder
        po.items = orderitems;
// Send the request
        PurchaseOrderStatus status=service.acceptPO(po);
        Console.WriteLine("Payment was scheduled Orderid: " +  status.orderid );
        Console.WriteLine("Timestpamp: " + status.timestamp );
     }
  }
}

When the SOAP request and response messages passed over the wire are compared with both Java and C# clients,you will notice that the response by the service is identical in nature. This will always be the case, because a service is not tied to the implementation on the client side; it only cares about the SOAP message it receives over the wire, which could originate from any possible client, even one sending raw data over sockets.

With the " Using a String in the SOAP Body" strategy, the XML document is passed as a String between the service and the service consumer. This is the XML version of the helloWorld or getStockQuote service, only the parameter and return type Strings now contain XML data. The WSDL for this strategy deployed from the service is shown in Code Sample 4.

Code Sample 4: WSDL for the String Service

<definitions xmlns:tns="http://www.examples.com/wsdl/StringService"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:ns2="http://www.examples.com/types"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="http://www.examples.com/wsdl/StringService"
             name="XMLStringService">
    <types>
        <schema targetNamespace="http://www.examples.com/types"
                xmlns:tns="http://www.examples.com/types"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                xmlns="http://www.w3.org/2001/XMLSchema">
        <complexType name="sayXMLHello">
            <sequence>
                <element name="String_1" type="string" nillable="true"/>
            </sequence>
        </complexType>
        <complexType name="sayXMLHelloResponse">
            <sequence>
                <element name="result" type="string" nillable="true"/>
             </sequence>
        </complexType>
            <element name="sayXMLHello" type="tns:sayXMLHello"/>
            <element name="sayXMLHelloResponse" type="tns:sayXMLHelloResponse"/>
        </schema>
    </types>
    <message name="IStringService_sayXMLHello">
        <part name="parameters" element="ns2:sayXMLHello"/>
    </message>
    <message name="IStringService_sayXMLHelloResponse">
        <part name="result" element="ns2:sayXMLHelloResponse"/>
    </message>
    <portType name="IStringService">
         
                                         <operation name="sayXMLHello">                 <input message="tns:IStringService_sayXMLHello"/>                 <output message="tns:IStringService_sayXMLHelloResponse"/>         </operation>                         
    </portType>
    <binding name="IStringServiceBinding" type="tns:IStringService">
        <soap:binding style="document"
              transport="http://schemas.xmlsoap.org/soap/http"/>
            <operation name="sayXMLHello">
                <soap:operation/>
                <input>
                    <soap:body use="literal"/>
                </input>
                <output>
                    <soap:body use="literal"/>
                </output>
            </operation>
    </binding>
    <service name="XMLStringService">
        <port name="IStringServicePort" binding="tns:IStringServiceBinding">
            <soap:address location="http://localhost:9090/xmlstring/jaxrpc"/>
        </port>
    </service>
</definitions>
                

From a C# client perspective, consuming the service is simple and quite straightforward. All it needs to do is construct the XML as a String and pass it as a parameter. The C# client code for this strategy is show in Code Sample 5.

Code Sample 5: C# Client code consuming the String service

using System;
using System.Xml;
using System.Text;
using System.IO;

namespace PurchaseOrderConsumer{
//Sample client showing consumption of a Java based Document based 
//Web Service from C# client
class JAXClient {
    // The main entry point for the application.
    [STAThread]
    static void Main(string[] args) {
// Instantiate the stub/proxy
        XMLStringService stub = new XMLStringService();
// Set the endpoint URL to go though our SOAP trace utility
        if (args.Length == 1)
        stub.Url = args[0];
        else
            stub.Url = "http://localhost:9090/xmlstring/jaxrpc";
                sendString(XML, stub);
                }
    public static void sendString(String XML, XMLStringService stub) {
        sayXMLHello request = new sayXMLHello();
        request.String_1=XML;
        sayXMLHelloResponse response = stub.sayXMLHello(request);
        string message = response.result;
        Console.WriteLine("     =============== Response from Server ========================");
        // Print response on console
        Console.WriteLine(message);
    }
    private static String XML = "<payload>" +
        "<BidRequest BidId=\"123654\" DueDate=\"2004-05-01\" xmlns:n=\"http://bidservice.com\"+
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
        " xsi:noNamespaceSchemaLocation=\"BidRequest.xsd\">" +
        " <Company Name=\"Small Time Shipping\" id=\"10001\" url=\"www.BigTimeShipping.com\" phone=\"888-920-9817\">" +
        "  <Address Street=\"Small Ship Lane\" City=\"Oceanana\" State=\"MD\" Zip=\"20819\"/>" +
        "  <BiddingAgent Name=\"Joe Bid Agent\" Email=\"joe@shipping.com\" Phone=\"888-890-1789\"/>" +
        " </Company>" +
        " <Manifest>" +
        "  <PickupPortOfCall Port=\"Miami\" Date=\"2003-10-09\" Time=\"Morning\"/>" +
        "  <DeliveryPortOfCall Port=\"Athens\" Date=\"2003-12-01\" Time=\"Afternoon\"/>" +
        "  <Cargo>" +
        "   <ContainerSet Size=\"Small\" IsRefridgerated=\"true\" Quantity=\"10\"/>" +
        "   <ContainerSet Size=\"Medium\" IsRefridgerated=\"false\" Quantity=\"2\"/>" +
        "   <ContainerSet Size=\"Large\" IsRefridgerated=\"false\" Quantity=\"3\"/>" +
        "  </Cargo>" +
        " </Manifest>" +
        "</BidRequest>" +
        "</payload>";
   }//end class
}// end namespace

With the base64Encoded or raw bytes in the SOAP body strategy, the service expects XML document structure as a base64Encoded string or as a set of raw bytes in the body of the SOAP request. The WSDL for this service endpoint is shown Code Sample 6. The service endpoint essentially contains two operations; the first accepts a Base64 encoded String, and the second accepts raw bytes.

Code Sample 6: WSDL for the Base64 Service

<definitions xmlns:tns="http://www.examples.com/wsdl/StringService"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:ns2="http://www.examples.com/types"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="http://www.examples.com/wsdl/StringService"
             name="XMLStringService">
    <types>
        <schema targetNamespace="http://www.examples.com/types"
                xmlns:tns="http://www.examples.com/types"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                xmlns="http://www.w3.org/2001/XMLSchema">
            <complexType name="sayRawHello">
                <sequence>
                     
                                         <element name="arrayOfbyte_1"                               type="base64Binary"                              nillable="true"/>                   
                </sequence>
            </complexType>
            <complexType name="sayRawHelloResponse">
                <sequence>
                     
                                         <element name="result"                              type="base64Binary"                              nillable="true"/>                   
                </sequence>
            </complexType>
            <complexType name="sayXMLHello">
                <sequence>
                     
                                         <element name="String_1"                              type="string"                              nillable="true"/>                   
                </sequence>
            </complexType>
            <complexType name="sayXMLHelloResponse">
                <sequence>
                     
                                         <element name="result"                              type="string"                              nillable="true"/>                   
                </sequence>
            </complexType>
            <element name="sayRawHello" type="tns:sayRawHello"/>
            <element name="sayRawHelloResponse" type="tns:sayRawHelloResponse"/>
            <element name="sayXMLHello" type="tns:sayXMLHello"/>
            <element name="sayXMLHelloResponse" type="tns:sayXMLHelloResponse"/>
        </schema>
    </types>
    <message name="IStringService_sayRawHello">
        <part name="parameters" element="ns2:sayRawHello"/>
    </message>
    <message name="IStringService_sayRawHelloResponse">
        <part name="result" element="ns2:sayRawHelloResponse"/>
    </message>
    <message name="IStringService_sayXMLHello">
        <part name="parameters" element="ns2:sayXMLHello"/>
    </message>
    <message name="IStringService_sayXMLHelloResponse">
        <part name="result" element="ns2:sayXMLHelloResponse"/>
    </message>
    <portType name="IStringService">
        <operation name="sayRawHello">
            <input message="tns:IStringService_sayRawHello"/>
            <output message="tns:IStringService_sayRawHelloResponse"/>
        </operation>
        <operation name="sayXMLHello">
            <input message="tns:IStringService_sayXMLHello"/>
            <output message="tns:IStringService_sayXMLHelloResponse"/>
        </operation>
    </portType>
    <binding name="IStringServiceBinding" type="tns:IStringService">
        <soap:binding style="document"
                      transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="sayRawHello">
            <soap:operation/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
            </operation>
            <operation name="sayXMLHello">
                <soap:operation/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
    <service name="XMLStringService">
        <port name="IStringServicePort" binding="tns:IStringServiceBinding">
            <soap:address location="http://localhost:9090/base64/jaxrpc"/>
        </port>
    </service>
</definitions>
                

The .NET framework contains a few different classes that can be used for handling Base64 encoded data as well as raw bytes. The client code is quite similar to the Java implementation and is shown in Code Sample 7. We demonstrate both methods in the C# code. For the first case, when sending the XML as a Base64 encoded string, the System.Convert class is used because it provides useful methods to convert between different base data types. Of particular use is the ToBase64String method. For the second case the code reads the contents from an XML file and sends the raw bytes to the service just like the Java code.

Code Sample 7: C# Client code using Base64 and raw bytes

using System;
using System.Xml;
using System.Text;
using System.IO;

namespace PurchaseOrderConsumer{
//Sample client showing consumption of a Java based Document based
//Web Service from C# client
class JAXClient {
    // The main entry point for the application.
    [STAThread]
    static void Main(string[] args) {
// Instantiate the stub/proxy
        XMLStringService stub = new XMLStringService();
// Set the endpoint URL to go though our SOAP trace utility
        if (args.Length == 1)
            stub.Url = args[0];
        else
            stub.Url = "http://localhost:9090/base64/jaxrpc";
       sendStringFromFile("purchaseorder.xml", stub);
       sendRawBytesFromFile("purchaseorder.xml", stub);
        }

    public static void sendStringFromFile(String filename, XMLStringService stub) {
        Console.WriteLine("     =============== sending base64encoded data ========================");
        byte[] data= readBytesFromFileRaw(filename);
        string  base64String = Convert.ToBase64String(data, 0, data.Length);
// create the request object
        sayXMLHello request = new sayXMLHello();
        request.String_1=base64String;
        sayXMLHelloResponse response = stub.sayXMLHello(request);
        string message = response.result;
// decode message from base64 to string and print;
     byte[] result =  Convert.FromBase64String(message);
        printResult(result);
        }

   public static void sendRawBytesFromFile(String filename, XMLStringService stub) {
        Console.WriteLine("     =============== sending raw data ========================");
        byte[] data= readBytesFromFileRaw(filename);
// create the request object
        sayRawHello rawrequest = new sayRawHello();
        rawrequest.arrayOfbyte_1 = data;
        sayRawHelloResponse rawresponse = stub.sayRawHello(rawrequest);
        byte[] result = rawresponse.result;
        printResult(result);
        }

    /**
     * Reads the contents form a file with XML in it and returns a byte array
     */
    private static byte[] readBytesFromFileRaw(String filename) {
        // Read from file
      FileStream  file = new FileStream(filename, FileMode.Open, FileAccess.Read);
        // create a byte array
      byte[] binaryData = new Byte[file.Length];
        // read the file into the byte array
        long bytesRead = file.Read(binaryData, 0,(int)file.Length);
        file.Close();
     return binaryData;
    }

  private static void printResult(byte[] result){
        Console.WriteLine("======================= Response from Server =======================" );
                for (int i=0;i<result.Length ;i++ ){
                Console.Write((char)result[i]);
                  }
        Console.WriteLine("==================================================================" );
        }
   }//end class
}// end namespace

With the "xsd:any Element in WSDL" strategy, there is no schema that defines the documents being passed back and forth. Instead, the xsd:any wildcard is used to indicate that an XML document is being passed, but its structure or root element is not identified. The sample WSDL is shown in Code Sample 8.

Code Sample 8: WSDL Extract with the any Element

<definitions xmlns:tns="http://www.examples.com/wsdl/poservice"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:ns1="http://www.examples.com/types"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="http://www.examples.com/wsdl/poservice"
             name="POService">
<types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
            targetNamespace="http://www.examples.com/types">
        <element name="BusinessDocumentRequest">
            <complexType>
                <sequence>
                    <any maxOccurs="1"/>
                </sequence>
            </complexType>
        </element>
        <element name="BusinessDocumentReply">
            <complexType>
                <sequence>
                     
                                         <any maxOccurs="1"/>                   
                </sequence>
            </complexType>
        </element>
        <element name="BusinessDocumentFault">
            <complexType>
                <sequence>
                     
                                         <any maxOccurs="1"/>                   
                </sequence>
            </complexType>
        </element>
    </schema>
</types>
<message name="IPurchaseOrder_acceptPO">
    <part name="parameters" element="ns1:BusinessDocumentRequest"/>
</message>
<message name="IPurchaseOrder_acceptPOResponse">
    <part name="result" element="ns1:BusinessDocumentReply"/>
</message>
<message name="POProcessingProblem">
    <part name="POProcessingProblem" element="ns1:BusinessDocumentFault"/>
</message>
<portType name="IPurchaseOrder">
    <operation name="acceptPO">
        <input message="tns:IPurchaseOrder_acceptPO"/>
        <output message="tns:IPurchaseOrder_acceptPOResponse"/>
        <fault name="POProcessingProblem" message="tns:POProcessingProblem"/>
    </operation>
</portType>
<binding name="IPurchaseOrderBinding" type="tns:IPurchaseOrder">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="acceptPO">
    <soap:operation/>
        <input>
            <soap:body use="literal"/>
        </input>
        <output>
            <soap:body use="literal"/>
        </output>
        <fault name="POProcessingProblem">
            <soap:fault name="POProcessingProblem" use="literal"/>
        </fault>
    </operation>
</binding>
<service name="POService">
    <port name="IPurchaseOrderPort" binding="tns:IPurchaseOrderBinding">
        <soap:address location="http://127.0.0.1:8080/docliteralfromjava/jaxrpc"/>
    </port>
</service>
</definitions>
                

On the Java side of things, the message elements get mapped to corresponding JavaBeans, however instead of the usual properties, the xsd:any gets mapped to a property called _any of type javax.xml.soap.SOAPElement (if the maxOccurs is greater than 1, it maps to an array of javax.xml.soap.SOAPElement). This behavior and mapping is standardized by the JAX-RPC specifications.

On the C# side of things the behavior is similar. C# has a corresponding class called System.Xml.XmlElement which is analogous to the Java org.w3c.dom.Element type. Note that the Java SOAPElement is actually a subclass of Element). Table 1 compares the different constructs between Java and C#.

Table 1: Java DOM to C# Mapping
 
Java (org.w3c.dom) Package
.NET (System.Xml) Namespace
Node XmlNode
Document XmlDocument
DocumentFragment XmlDocumentFragment
DocumentType XmlDocumentType
EntityReference XmlEntityReference
Element XmlElement
Attr XmlAttribute
ProcessingInstruction XmlProcessingInstruction
Comment XmlComment
Text XmlText
CDataSection XmlCDataSection
Entity XmlEntity
Notation XmlNotation
N/A XmlDeclaration
N/A XmlSignificantWhitespace
N/A XmlWhitespace

 

The C# Client for consuming the web service using xsd:any, deployed in J2EE 1.4, is shown in Code Sample 9. The client behavior is similar to the Java client, in that it loads an XML document from a file and sends it to the service. The output is printed out on the screen.

Code Sample 9: C# Client code using XMLElement directly

using System;
using System.Xml;

namespace PurchaseOrderConsumer{
//Sample client showing consumption of a Java based Document-Oriented
//Web Service from C# client
class JAXClient {
    // The main entry point for the application.
    [STAThread]
    static void Main(string[] args) {
// Instantiate the stub/proxy
        POService service = new POService();
// Set the endpoint URL to go though our SOAP trace utility
        if (args.Length == 1)
            service.Url = args[0];
        else
            service.Url = "http://localhost:9090/anysoapelement/jaxrpc";
// Load the request payload
     System.Console.WriteLine("Invoking JAX-RPC Client on " + service.Url);
        XmlDocument doc = new XmlDocument();
        doc.Load("purchaseorder.xml");
        XmlElement po = doc.DocumentElement;
// Send the request
        XmlElement status=service.acceptPO(po);
// Print the request
        XmlTextWriter writer = new XmlTextWriter(Console.Out);
        writer.Formatting = Formatting.Indented;
        status.WriteTo(writer);
// Save the request
        XmlTextWriter filewriter = new XmlTextWriter("response.xml", null);
        filewriter.Formatting = Formatting.Indented;
        status.WriteTo(filewriter);
        filewriter.Close();
    }
  }
}

With the "xsd:anyType in WSDL" strategy, the WSDL defines an element as type xsd:anyType, which actually references a document fragment. The sample WSDL is shown in Code Sample 10.This is somewhat similar to the previous example, in that the elements are still mapped to a SOAPElement in Java; however in C#, the proxy maps it to an System.object . This is analogous to the java.lang.Object and is the base class of all classes in the .NET Framework.

Code Sample 10: WSDL using xsd:anyType

<definitions xmlns:tns="http://www.examples.com/wsdl/poservice"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:ns1="http://www.examples.com/types"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetnamespace="http://www.examples.com/wsdl/poservice"
             name="POService">
<types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
            targetnamespace="http://www.examples.com/types">
         
                                         <element name="BusinessDocumentRequest"  type="anyType"/>         <element name="BusinessDocumentReply"   type="anyType"/>         <element name="BusinessDocumentFault"   type="anyType"/>                   
    </schema>
</types>
<message name="IPurchaseOrder_acceptPO">
    <part name="parameters" element="ns1:BusinessDocumentRequest"/>
</message>
<message name="IPurchaseOrder_acceptPOResponse">
    <part name="result" element="ns1:BusinessDocumentReply"/>
</message>
<message name="POProcessingProblem">
    <part name="POProcessingProblem" element="ns1:BusinessDocumentFault"/>
</message>
<porttype name="IPurchaseOrder">
    <operation name="acceptPO">
        <input message="tns:IPurchaseOrder_acceptPO"/>
            <output message="tns:IPurchaseOrder_acceptPOResponse"/>
                <fault name="POProcessingProblem  message="tns:POProcessingProblem"/>
    </operation>
</porttype>
<binding name="IPurchaseOrderBinding" type="tns:IPurchaseOrder">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="acceptPO">
        <soap:operation/>
        <input>
            <soap:body use="literal"/>
        </input>
        <output>
            <soap:body use="literal"/>
        </output>
        <fault name="POProcessingProblem">
            <soap:fault name="POProcessingProblem" use="literal"/>
        </fault>
    </operation>
</binding>
<service name="POService">
    <port name="IPurchaseOrderPort" binding="tns:IPurchaseOrderBinding">
        <soap:address location="http://127.0.0.1:8008/0anytypesoapelementa/jaxrpc"/>
    </port>
</service>
</definitions>
                

The C# client code for the anyType example is similar to the previous example but with a subtle difference. The generated proxy no longer accepts an XMLElement but works with an object instead. This behavior is a little different from the Java example where in both cases the generated type was a SOAPElement . The client shown in Code Sample 11 loads an XML document from a file and accesses the XML contained therein as an object.

Code Sample 11: C# Client code using for the xsd:anyType Web Service

using System;
using System.Xml;

namespace PurchaseOrderConsumer{
//Sample client showing consumption of a Java based Document based
//Web Service from C# client
class JAXClient {
    // The main entry point for the application.
    [STAThread]
    static void Main(string[] args) {
// Instantiate the stub/proxy
   POService service = new POService();
// Set the endpoint URL to go though our SOAP trace utility
  if (args.Length == 1)
    service.Url = args[0];
  else
    service.Url = "http://localhost:9090/anytypesoapelement/jaxrpc";
// Load the request payload
  System.Console.WriteLine("Invoking JAX-RPC Client on " + service.Url);
  XmlDocument doc = new XmlDocument();
  doc.Load("BusinessDocumentRequest.xml");
// get to the XML
  object obj = (object) doc.OuterXml;
// Send the request
   object status=service.acceptPO(obj);
// Print the response
   System.Xml.XmlNode[] response=(System.Xml.XmlNode[])status;
   XmlTextWriter writer = new XmlTextWriter(Console.Out);
   writer.Formatting = Formatting.Indented;
   response[1].WriteTo(writer);
    }
  }
}

Summary

In this document we demonstrated seamless interoperability with C# for document-based web services developed and deployed in the J2EE 1.4 platform. This is mainly possible because of the WS-I Basic Profile 1.0 support in JAX-RPC 1.1. JAX-RPC is a part of J2EE 1.4, this is important because it means that not only must J2EE 1.4 containers provide support for this API in the client, web and application server containers but that they must also satisfy all the interoperability requirements from WS-I Basic Profile outlined in JAX-RPC. Developers can now concentrate more on the application and the business problem; as demonstrated in these white papers; and less time worrying about the specifics of “on the wire interoperability”.

The source for the examples is included with this article in the references section below. More examples, solutions, and best practices can be found in the 'Java Blueprints Solution Catalog' project on Java.net. The catalog consists of an evolving set of guidelines and best practices for the J2EE 1.4 platform. Readers are encouraged to use these examples as well as participate in the project on Java.net.

About the Author

Sameer Tyagi is a Senior Staff Engineer at Sun Microsystems. He remains focused on architecture, design, and implementation of large-scale enterprise applications with Java technology. His publications include industry periodicals and books on Java and J2EE technologies. Sameer can be reached at s.t@sun.com.

References
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.