How To Integrate an Apache SOAP 2.2 Client with a .NET XML Web Service (308466)



The information in this article applies to:

  • Microsoft Visual Studio .NET (2002), Professional Edition

This article was previously published under Q308466

SUMMARY

This step-by-step article describes how to integrate an Apache SOAP 2.2 client with an XML Web Service that is based on Active Server Pages (ASP) .NET. This article assumes that the ASP .NET server-side component exposes the Web method with the WebMethod attribute and does nothing more than provide a namespace for the Web Service itself.

back to the top

Requirements

The following items describe the recommended hardware, software, network infrastructure, skills and knowledge, and service packs that you need:
  • Java JDK version 1.3
  • Apache SOAP version 2.2
This article assumes that you are familiar with the following topics:
  • Apache SOAP 2.2 libraries setup
  • Java language and JavaBean technology
  • Web Service Description Language (WSDL) files
  • Simple API for XML (SAX) XML parsing model
For more information about how to set up the Apache SOAP 2.2 libraries, refer to the downloads for these products, as well as the REFERENCES at the end of this article.

When you use complex types in the SOAP interfaces, it is helpful to understand JavaBean technology as well. In addition, you need some familiarity with Web Service Description Language (WSDL) files when you create the various Java classes.

back to the top

The .NET XML Web Service

This article assumes that your .NET Web Service resembles the following:

Microsoft Visual C# .NET:
public class SimpleService : System.Web.Services.WebService
{
    public SimpleService() {
    }

    [WebMethod]
    public string echoString(string inputString) {
        if ( inputString == null ) {
            return "Input string is null";
        }
        return inputString;
    }
}
				
Microsoft Visual Basic .NET:
Public Class SimpleService
    Inherits System.Web.Services.WebService

    Public Sub New()
        MyBase.New()
    End Sub

    <WebMethod()> Public Function echoString( _
        ByVal inputString As String) As String

        echoString = inputString
    End Function

End Class
				
These declarations tell the .NET runtime to not expect any encoding information and to interpret the messages as document or literal types. That is, the element names mean something and the server is responsible for knowing what data types are passed in.

back to the top

The Apache SOAP Toolkit

The Apache SOAP Toolkit is designed as a remote procedure call (RPC) mechanism, not a document exchange mechanism. It expects the message exchange mechanism to be RPC encoded. As a result, you must follow these steps so that everything works correctly:
  1. Write a Java proxy for the .NET endpoint.
  2. Create a class that generates the SOAP message Body element.
  3. Create a class that parses the SOAP response.
back to the top

The Java Proxy

The IBM Web Services Toolkit can produce proxies; however, due to the amount of work you need to do after the proxy is generated, it is usually easier to write the proxy by hand. Beyond that, you need to write functions that mimic the signatures in the WSDL of the operations that are associated with the portType to which you are connecting. For example, the echoString function signature resembles the following:
public synchronized String echoString( String inputString ) 
      throws SOAPException
				
This allows users of the proxy to instantiate the proxy and call functions, while only worrying about handling SOAP faults. The proxy performs the following steps:
  1. Verifies that the URL has been set.
  2. Prepares the message.
  3. Sends the message.
  4. Parses the response.
Because of the way in which the Apache SOAP 2.2 classes encode messages, you must override the piece that builds the body as well as the pieces that interpret the response. You must change the way in which the response is handled because the changes to Apache go outside its original RPC design. The full code to call echoString resembles the following:
public synchronized String echoString( String inputString ) 
    throws SOAPException {
    String retval = "";
    if (url == null) {
        throw new SOAPException(Constants.FAULT_CODE_CLIENT,
        "A URL must be specified via " +
        "SoapBuildersExSoapProxy.setEndPoint(URL).");
    }

    // Instantiate the message and the envelope.
    // The message sends the envelope and gets
    // the response.
    Message message = new Message();
    Envelope env = new Envelope();
    DataHandler soapMsg = null;

    // Get this from the soapAction attribute on the
    // soap:operation element that is found within the SOAP
    // binding information in the WSDL.
    String SOAPActionURI = "http://tempuri.org/echoString";
    MessageBody theBody = new MessageBody();

    // Set the argument.
    theBody.echoString = inputString;

    // Replace the default body with our own.
    env.setBody( theBody );
    message.send( getEndPoint(), SOAPActionURI, env );
    try{
        // Because the Body.unmarshall handler is static,
        // you cannot replace the basic machinery easily.
        // Instead, you must obtain and parse the 
        // message on your own. 
        soapMsg = message.receive();
        XMLReader xr = XMLReaderFactory.createXMLReader(
            "org.apache.xerces.parsers.SAXParser"); 

        ClientHandler ch = new ClientHandler();
        ch.setElementToSearchFor("echoStringResult");
        // Set the ContentHandler. 
        xr.setContentHandler( ch ); 

        // Parse the file. 
        xr.parse( new InputSource( 
           new StringReader( soapMsg.getContent().toString() ) ) );

        // At this point, the result has been parsed and stored
        // within the ClientHandler instance.
        retval = ch.getResult();
    } catch ( Exception e ) {
        // You need to do something with the exception.
        // Here, we print out the exception to the console.
        System.out.println( "***Exception***:  " + e.toString() );
    }
    return retval;
}
				
This basic form works regardless of whether the actual argument is a simple type such as a string or a complex type such as an array. Creating the Body class is more difficult. You must do the serialization by hand.

back to the top

Override the Body Serialization

Apache SOAP uses a class called Body to serialize and deserialize SOAP messages. It does so with the marshall and unmarshall methods. The marshall method is an instance method and the unmarshall method is a static class method. Because of the structure of the Apache SOAP library and the way in which you are using it, you cannot inherit from Body and expect your own version of unmarshall to be called. Fortunately, you can replace marshall and change how you serialize the SOAP Body element.

To correctly serialize the echoString method, provide your own extended version of Body. Depending on how many methods you have, you can either create one version of a class that extends Body per method, or you can have the marshall method choose the correct code based on other information.

Assuming that you only serialize one method call in the derived class, the class must include the following:
  • A way to set the data that is being serialized.
  • Knowledge of how to write the XML so that it is formatted correctly for the ASP .NET endpoint.
Because the ASP .NET endpoint uses document/literal encoding, you only need to write out the following information:
  • Body element.
  • Method name and corresponding namespace.
  • Arguments that are passed into the method.
For the echoString example, the class resembles the following:
import java.io.*;
import org.apache.soap.util.*;
import org.apache.soap.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.rpc.SOAPContext;
public class MessageBody extends Body
{
    
  public String echoString;  
  public void marshall(String inScopeEncStyle, 
                       Writer sink, 
                       NSStack nsStack,
                       XMLJavaMappingRegistry xjmr, 
                       SOAPContext ctx)
    throws IllegalArgumentException, IOException
  {
    // Set the Body element
    String soapEnvNSPrefix = "SOAP-ENV";
    sink.write('<' + soapEnvNSPrefix + ':' 
        + Constants.ELEM_BODY + '>' + StringUtils.lineSeparator);

    // Write out the method name and related argument(s)
    sink.write("<echoString xmlns=\"http://tempuri.org/\">" +
        "<inputString>" + echoString +
        "</inputString></echoString>" );

    // Close the SOAP Body
    sink.write("</" + soapEnvNSPrefix + ':' + 
        Constants.ELEM_BODY + '>' + StringUtils.lineSeparator);

    nsStack.popScope();
  }

}
				
Now that you can send the message, you need to be able to read the response. To do this, you need a class that is called by the SAX parser.

back to the top

Parse the Response

When the message goes out and comes back, you can determine the entire SOAP response. This part is used within the code in the echoStringtry/catch block for the example proxy. This example ClientHandler class attempts to be a general purpose class that can obtain any single element response. Users of the class should be able to use the class as-is. If the value is a Boolean, date, or numeric type, you can do the conversion after you retrieve the result. More complex types need more complex implementations.

To retrieve simple values, you must do the following:
import org.xml.sax.helpers.*;
import org.xml.sax.*;
public class ClientHandler extends DefaultHandler { 
    private String result = "";
    private String elementToSearchFor = "";
    private boolean foundResult = false;

    public ClientHandler() {
    }

    public String getResult(){
        return result;
    }

    public void setElementToSearchFor( String elemName ) {
        elementToSearchFor = elemName;
    }

    public String getElementToSearchFor() {
        return elementToSearchFor;
    }

    // Override methods of the DefaultHandler class 
    // to gain notification of SAX events. 
    // 
    // See org.xml.sax.ContentHandler for all available events. 
    // 
    public void startElement( String namespaceURI, 
        String localName, 
        String qName, 
        Attributes attr ) throws SAXException { 
        if ( foundResult == false ) {
            foundResult = (localName.compareTo( 
                elementToSearchFor ) == 0);
        }
    } 

    public void characters( char[] ch, int start, int length ) 
        throws SAXException { 
        if ( foundResult ) {
            // Read all the data in
            result = String.valueOf( ch, start, length );
            foundResult = false;
        }
    } 
}

				
In the above class, you call getResult to obtain the single element result. Modify the code as necessary for complex types and arrays.

back to the top

Troubleshooting

Overriding the body serialization can be time-consuming and error-prone. Test your proxy to ensure that it can handle whatever the ASP .NET XML Web Service returns to you.

back to the top

REFERENCES

Java JDK Home Page
http://java.sun.com

Apache SOAP home page
http://xml.apache.org/soap/

Writing or using Custom Apache Serializers/Deserializers
http://xml.apache.org/soap/docs/guide/serializer.html

At Your Service: Interoperability Testing
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dn_voices_webservice/html/service08152001.asp

Original post to soapbuilders, including the source as an attachment
http://groups.yahoo.com/group/soapbuilders/message/5096

back to the top

Modification Type:MinorLast Reviewed:6/29/2004
Keywords:kbhowto kbHOWTOmaster KB308466 kbAudDeveloper