Handling Complex SOAP Data Types in XML Web Services

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Paul Cornell
Microsoft Corporation

January 2002

Applies to:
     Microsoft® Office XP

Summary: The Web Service References Tool for Office XP VBA is designed to handle simple SOAP data types. This article describes how to programmatically send and receive complex SOAP data types in XML Web services by using the Microsoft Soap Type Library and the Microsoft XML Parser (MSXML). (19 printed pages)

Contents

Introduction
Sending and Receiving Complex SOAP Data Types Using the MSXML Parser
Constructing and Sending SOAP Messages Using the Microsoft Soap Type Library Conclusion Appendix: Selected API Members Described in This Article

Introduction

The Web Service References Tool for Office XP Visual Basic® for Applications (VBA) handles the passing and receiving of simple SOAP data types. However, the Web Service References Tool is not designed to handle the passing or receiving of complex SOAP data types. If you attempt to reference an XML Web service method that accepts or returns complex SOAP data types, you will receive the message "<Method Name> contains complex data types and are not supported. If <Web Service Name> is selected, then this method will not be included in the VBA Class."

There are three scenarios in which XML Web service methods use complex SOAP data types and are not handled by the Web Service References Tool:

  • The XML Web service method receives a complex SOAP data type and returns a simple SOAP data type.
  • The XML Web service method receives a simple SOAP data type and returns a complex SOAP data type.
  • The XML Web service method receives a complex SOAP data type and returns a complex SOAP data type.

For example, using the SalesRankNPrice XML Web service at http://www.perfectxml.net/WebServices/SalesRankNPrice/BookService.asmx, we see that the following XML Web service methods are supported by the Web Service References Tool, because they all accept a String value and return a String value:

  • GetAmazonPrice (accepts a String value representing an International Standard Book Number (ISBN); returns a String value representing the Amazon sales price)
  • GetAmazonSalesRank (accepts a String value representing an ISBN; returns a String value representing the Amazon sales rank)
  • GetAmazonUKPrice (accepts a String value representing an ISBN; returns a String value representing the Amazon UK sales price)
  • GetAmazonUKSalesRank (accepts a String value representing an ISBN; returns a String value representing the Amazon UK sales rank)
  • GetBNPrice (accepts a String value representing an ISBN; returns a String value representing the Barnes & Noble sales price)
  • GetBNSalesRank (accepts a String value representing an ISBN; returns a String value representing the Barnes & Noble sales rank)

However, the following XML Web service methods are not supported by the Web Service References Tool, because they either send or receive complex SOAP data types:

  • GetAll (accepts a String value representing an ISBN; returns four String values representing the Amazon sales rank, the Amazon price, the Barnes & Noble sales rank, and the Barnes & Noble price)
  • GetAmazonAndBNPrice (accepts a String value representing an ISBN; returns two String values representing the Amazon price and the Barnes & Noble price)
  • GetAmazonAndBNSalesRank (accepts a String value representing an ISBN; returns two String values representing the Amazon sales rank and the Barnes & Noble sales rank)
  • GetAmazonSalesRankNPrice (accepts a String value representing an ISBN; returns two String values representing the Amazon sales rank and sales price)
  • GetBNSalesRankNPrice (accepts a String value representing an ISBN; returns two String values representing the Barnes & Noble sales rank and sales price)

To interact with an XML Web service method that accepts or returns a complex SOAP data type, you can use the Microsoft XML (MSXML) Parser. Alternatively, you can use the "low level" SOAP Application Programming Interface (API) in the Microsoft Soap Type Library (mssoap1.dll).

Both of these programmatic approaches are described in the following sections.

Sending and Receiving Complex SOAP Data Types Using the MSXML Parser

To send and receive complex SOAP data types in XML Web services, you can use the Microsoft XML (MSXML) Parser. Complex SOAP data types are sent and returned as an MSXML IXMLDOMNodeList collection, so using the MSXML Parser makes it straightforward to build or iterate through a collection of nodes. For more information on the MSXML Parser, see the MSDN Online XML Developer Center.

For example, the ZIP Code Resolver XML Web service at http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx exposes a CorrectedAddressXML method that accepts four String values:

  • accessCode — Use "0" or "9999" for testing purposes.
  • address — A street address (for example, "One Microsoft Way").
  • city — A city name (for example, "Redmond").
  • state — A two-letter state postal abbreviation (for example, "WA").

The method returns the USPS address as a complex SOAP data type as follows:

<Street>MICROSOFT<br /> 1 MICROSOFT WAY</Street> 
<City>REDMOND</City> 
<State>WA</State> 
<ShortZIP>98052</ShortZIP> 
<FullZIP>98052-8300</FullZIP> 

Using the code that the Web Service References Tool generates as a starting point, the following code builds a SOAP request, calls the CorrectedAddressXML XML Web service method, and uses MSXML to parse the SOAP response. It consists of four code files:

  • mod_ResolveAddress.bas — Code that calls the XML Web service.
  • InputAddress.cls — Code that encapsulates an input address.
  • CorrectedAddress.cls — Code that encapsulates a corrected address.
  • clsws_ZipCodeResolver.cls — Code that encapsulates the XML Web service interface.
' -------------------------------
' mod_ResolveAddress.bas file.
' -------------------------------
Public Sub TestTheResolveAddressFunction()

    ' Purpose: Test code to call the ResolveAddress function.
    ' You could also type in the Immediate Window...
    ' ? ResolveAddress("0", "One Microsoft Way", "Redmond", "WA")
    MsgBox ResolveAddress("0", "One Microsoft Way", "Redmond", "WA")
    
End Sub

Public Function ResolveAddress(ByVal AccessCode As String, _
        ByVal Address As String, ByVal City As String, _
        ByVal State As String) As String

    ' Purpose: Resolves an address using the eraServer.net
    ' Zip Code Resolver Service.
    ' You must have the following modules available to
    ' call this code:
    '   clsws_ZipCodeResolver.cls
    '   InputAddress.bas
    '   CorrectedAddress.bas
    
    Dim objResolver As clsws_ZipCodeResolver
    Dim objOldAddr As InputAddress
    Dim objNewAddr As CorrectedAddress
    Dim strResults As String
    
    Set objResolver = New clsws_ZipCodeResolver
    Set objOldAddr = New InputAddress
    Set objNewAddr = New CorrectedAddress
    
    ' Set properties of the InputAddress class
    ' to user-defined values.
    With objOldAddr
        .AccessCode = AccessCode
        .Address = Address
        .City = City
        .State = State
    End With
    
    ' Get the resolved address by calling the
    ' wsm_CorrectedAddressXml method in the
    ' clsws_ZipCodeResolver class, passing in the
    ' InputAddress object.
    Set objNewAddr = objResolver.wsm_CorrectedAddressXml _
        (objOldAddr)
        
    ' Parse the results.
    With objNewAddr
        strResults = .Street
        strResults = strResults & .City & ", "
        strResults = strResults & .State & " "
        strResults = strResults & .ShortZIP & " ("
        strResults = strResults & .FullZIP & ")"
    End With
    
    ' Return the parsed results to the calling function.
    ResolveAddress = strResults
    
End Function

' -------------------------------
' InputAddress.cls file.
' -------------------------------
' Purpose: Represents target address properties.
' Property names are self-explanatory.
' For testing purposes, the AddressCode property
' is always zero (0).

Private m_strAccessCode As String
Private m_strAddress As String
Private m_strCity As String
Private m_strState As String

Public Property Get AccessCode() As String

    AccessCode = m_strAccessCode
    
End Property

Public Property Let AccessCode(ByVal strNewValue As String)

    m_strAccessCode = strNewValue
    
End Property

Public Property Get Address() As String
    
    Address = m_strAddress
    
End Property

Public Property Let Address(ByVal strNewValue As String)

    m_strAddress = strNewValue
    
End Property

Public Property Get City() As String

    City = m_strCity
    
End Property

Public Property Let City(ByVal strNewValue As String)

    m_strCity = strNewValue
    
End Property

Public Property Get State() As String

    State = m_strState
    
End Property

Public Property Let State(ByVal strNewValue As String)

    m_strState = strNewValue
    
End Property

' -------------------------------
' CorrectedAddress.cls file.
' -------------------------------
' Purpose: Represents corrected address properties.
' Property names are self-explanatory.
' The ShortZIP property doesn't include the -NNNN part of
' the Zip code, only the NNNNN- part.
 
Private m_strStreet As String
Private m_strCity As String
Private m_strState As String
Private m_strShortZIP As String
Private m_strFullZIP As String

Public Property Get Street() As String

    Street = m_strStreet

End Property

Public Property Let Street(ByVal strNewValue As String)

    m_strStreet = strNewValue
    
End Property

Public Property Get City() As String

    City = m_strCity
    
End Property

Public Property Let City(ByVal strNewValue As String)

    m_strCity = strNewValue
    
End Property

Public Property Get State() As String

    State = m_strState
    
End Property

Public Property Let State(ByVal strNewValue As String)

    m_strState = strNewValue
    
End Property

Public Property Get ShortZIP() As String

    ShortZIP = m_strShortZIP
    
End Property

Public Property Let ShortZIP(ByVal strNewValue As String)

    m_strShortZIP = strNewValue
    
End Property

Public Property Get FullZIP() As String

    FullZIP = m_strFullZIP
    
End Property

Public Property Let FullZIP(ByVal strNewValue As String)

    m_strFullZIP = strNewValue
    
End Property

' -------------------------------
' clsws_ZipCodeResolver.cls file.
' -------------------------------
'*****************************************************************
' This class was created by the Web Service References Tool.
'
' Created: 11/9/2001 01:50:22 PM
'
' Description:
' This class is a Visual Basic for Applications class representation of 
' the Web service ZipCodeResolver as defined by
' http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?WSDL,
'
' This class only contains methods that use simple data types,
' as defined in the WSDL.
'
' To Use:
' Dimension a variable as new clsws_ZipCodeResolver1, and then write
' code to use the methods provided by the class.
'
' Example:
'   Dim ExampVar as New clsws_ZipCodeResolver1
'   Debug.Print ExampVar.wsm_FullZipCode("Sample Input")
'
' Changes to the code in this class may result in incorrect behavior.
'
'*****************************************************************

' Dimensioning private class variables.
Private sc_ZipCodeResolver As SoapClient
Private Const c_WSDL_URL As String = _
    "http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?WSDL"

Private Sub Class_Initialize()
'*************************************************************
' Subroutine will be called each time the class is instantiated.
' Creates sc_ZipCodeResolver as new SoapClient, and then
' initializes sc_ZipCodeResolver.mssoapinit with WSDL file found in
' http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?WSDL.
'*************************************************************

    Set sc_ZipCodeResolver = New SoapClient
    sc_ZipCodeResolver.mssoapinit c_WSDL_URL
 
End Sub
 
Private Sub Class_Terminate()
'*************************************************************
' Subroutine will be called each time the class is destructed.
' Sets sc_ZipCodeResolver to Nothing.
'*************************************************************
 
    'Error Trap
    On Error GoTo Class_TerminateTrap
 
    Set sc_ZipCodeResolver = Nothing
 
Exit Sub
Class_TerminateTrap:
    ZipCodeResolverErrorHandler "Class_terminate"
End Sub
Private Sub ZipCodeResolverErrorHandler(str_Function As String)
'*****************************************************************
' This subroutine is the class error handler. It can be called from any
' class subroutine or function when that subroutine or function
' encounters an error. Then it will raise the error along with the
' name of the calling subroutine or function
'
'*****************************************************************
 
    ' SOAP Error
    If sc_ZipCodeResolver.faultcode <> "" Then
        Err.Raise vbObjectError, str_Function, _
            sc_ZipCodeResolver.faultstring
    ' Non SOAP Error
    Else
        Err.Raise Err.Number, str_Function, Err.Description
    End If
 
End Sub
' ...
Public Function wsm_CorrectedAddressXml(InputAddress As InputAddress) _
        As CorrectedAddress
'*************************************************************
' Proxy function created by hand, referencing
' http://webservices.eraserver.net/zipcoderesolver/zipcoderesolver.asmx?WSDL
' This code cannot be generated by the Web Service References Tool
' because it returns a complex SOAP data type.
'*************************************************************
 
    ' Purpose: Resolves an address and Zip code.
    ' Accepts: The target address as type InputAddress.
    ' Returns: The corrected address as type CorrectedAddress.
    ' You must add the InputAddress.cls and CorrectedAddress.cls
    ' files to this project, as well as set a reference to 
    ' Microsoft XML, v4.0 (msxml4.dll) before calling this code.
    
    ' Set error trap.
    On Error GoTo wsm_CorrectedAddressXmlTrap
    
    ' Data will be returned by the Web service in a collection
    ' of XML data nodes as an object of type MSXML2.IXMLDOMNodeList.
    Dim objAddrList As MSXML2.IXMLDOMNodeList
    Dim objAddrNode As MSXML2.IXMLDOMNode
    Dim objCorrAddr As CorrectedAddress
    
    Set objCorrAddr = New CorrectedAddress
    
    ' Call the CorrectedAddressXml Web method with properties
    ' of the InputAddress class and return the results as an
    ' MSXML2.IXMLDOMNodeList object.
    Set objAddrList = sc_ZipCodeResolver.CorrectedAddressXml _
        (InputAddress.AccessCode, InputAddress.Address, _
        InputAddress.City, InputAddress.State)

    ' Parse the returned collection of XML data nodes.
    For Each objAddrNode In objAddrList
        ' Persist the values of selected XML data nodes
        ' as properties of the CorrectedAddress object.
        Select Case objAddrNode.baseName
            Case "Street"
                objCorrAddr.Street = objAddrNode.nodeTypedValue
            Case "City"
                objCorrAddr.City = objAddrNode.nodeTypedValue
            Case "State"
                objCorrAddr.State = objAddrNode.nodeTypedValue
            Case "ShortZIP"
                objCorrAddr.ShortZIP = objAddrNode.nodeTypedValue
            Case "FullZIP"
                objCorrAddr.FullZIP = objAddrNode.nodeTypedValue
        End Select
    Next objAddrNode
    
    ' Return the CorrectedAddress object to the calling function.
    Set wsm_CorrectedAddressXml = objCorrAddr
    
Exit Function
wsm_CorrectedAddressXmlTrap:
    ZipCodeResolverErrorHandler "wsm_CorrectedAddressXml"
End Function

Constructing and Sending SOAP Messages Using the Microsoft Soap Type Library

Alternatively, to send a complex data type to an XML Web service method using SOAP, you can use the "low level" SOAP Application Programming Interface (API) in the Microsoft Soap Type Library (mssoap1.dll) to:

  1. Use an HttpConnector object to connect to the XML Web service's endpoint URL and appropriate SOAP action.
  2. Use a SoapSerializer object to programmatically construct and send a SOAP message, consisting of a SOAP message envelope, followed by a SOAP message body containing the input data encapsulated as an XML document fragment.
  3. Use a SoapReader object along with the MSXML Parser (see the previous section) to programmatically receive and parse the returned SOAP message.

As an example, the BabelFish Translation Server XML Web service accepts the following complex SOAP data type:

<input>
    <text>Hello</text>
    <language>French</language>
</input>

And returns the following complex SOAP data type:

<output>
    <text>Bonjour</text>
</output>

Although the returned SOAP data type looks like a simple SOAP data type, because the <text> tag is nested within the <output> tag, this immediately classifies it as a complex SOAP data type.

To send a complex SOAP data type using the "low level" SOAP API, use code similar to the following. Note that in this case we cannot use code generated by the Web Service References Tool.

Public Sub TranslateBabel()

    ' Purpose: Translates text from one language to another.
    ' WSDL: http://services.xmltoday.com/vx_engine/wsdl_publish.vep/translate.wsdl
    ' More info: http://www.xmethods.net/detail.html?id=94
    
    Dim objClient As MSSOAPLib.SoapClient
    ' To package SOAP request.
    Dim objSerial As MSSOAPLib.SoapSerializer
    ' To read SOAP response.
    Dim objRead As MSSOAPLib.SoapReader
    ' To connect to Web service using SOAP.
    Dim objConn As MSSOAPLib.SoapConnector
    ' To parse the SOAP response.
    Dim objResults As MSXML2.IXMLDOMNodeList
    Dim objNode As MSXML2.IXMLDOMNode
    
    ' Set up the SOAP connector.
    Set objConn = New MSSOAPLib.HttpConnector
    ' Define the endpoint URL. This is the actual running code, 
    ' not the WSDL file path! You can find it in the WSDL's    
    ' <soap:address> tag's location attribute.
    objConn.Property("EndPointURL") = _
        "http://thor.velocigen.com:80/vx_engine/soap-trigger.pperl"
    ' Define the SOAP action. You can find it in the WSDL's
    ' <soap:operation> tag's soapAction attribute for the matching
    ' <operation> tag.
    objConn.Property("SoapAction") = "urn:vgx-translate"
    ' Begin the SOAP message.
    objConn.BeginMessage
    
    Set objSerial = New MSSOAPLib.SoapSerializer
    ' Initialize the serializer to the connector's input stream.
    objSerial.Init objConn.InputStream
    
    ' Build the SOAP message.
    With objSerial
        .startEnvelope              ' <SOAP-ENV:Envelope>
        .startBody                  ' <SOAP-ENV:Body>
        ' Use the Web method's name and schema target namespace URI.
        .startElement "getTranslation", "urn:vgx-translate" ' <getTranslation>
        .startElement "input"       ' <input>
        .startElement "text"        ' <text>
        .writeString "Hello"
        .endElement                 ' </text>
        .startElement "language"    ' <language>
        .writeString "French"
        .endElement                 ' </language>
        .endElement                 ' </input>
        .endElement                 ' </getTranslation>
        .endBody                    ' </SOAP-ENV:Body>
        .endEnvelope                ' </SOAP-ENV:Envelope>
    End With
    
    ' Send the SOAP message.
    objConn.EndMessage
    
    Set objRead = New MSSOAPLib.SoapReader
    
    ' Initialize the SOAP reader to the connector's output stream.
    objRead.Load objConn.OutputStream
    
    ' Return an IXMLDOMNodeList that represents the connector's
    ' output stream. Results will look like this:
    ' <?xml version="1.0"?>
    ' <SOAP-ENV:Envelope xmlns:SOAP-
    '     ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-
    '     ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
    '     xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    '     xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
    '     xmlns:xsd="http://www.w3.org/1999/XMLSchema">
    '     <SOAP-ENV:Body>
    '         <namesp14:getTranslationResponse
    '             xmlns:namesp14="urn:getTranslation">
    '             <output>
    '                 <text>Bonjour</text>
    '             </output>
    '         </namesp14:getTranslationResponse>
    '     </SOAP-ENV:Body>
    ' </SOAP-ENV:Envelope>
    Set objResults = objRead.RPCResult.childNodes
        
    ' Iterate through the returned nodes.
    For Each objNode In objResults
        Select Case objNode.nodeName
            Case "text"
                ' Prints "Bonjour".
                MsgBox objNode.nodeTypedValue
            Case Else
        End Select
    Next objNode 

End Sub

Conclusion

The Web Service References Tool is designed to handle the passing and receiving of simple SOAP data types. This article described how to send and receive complex SOAP data types in XML Web services. You can send and receive complex SOAP data types by using the Microsoft XML Parser and, alternatively, the "low level" API in the Microsoft Soap Type Library.

Appendix: Selected API Members Described in This Article

Selected HttpConnector Object Members (Microsoft Soap Type Library)

Member Purpose
BeginMessage method Starts a SOAP message.
EndMessage method Marks the end of a SOAP message and initiates the send.
InputStream property (returns an IStream object) The SOAP data to be sent out.
OutputStream property (returns an IStream object) The SOAP data received.
Property property Given a case-sensitive property string, sets or returns a SOAP connection property. The property string can be one of the following:
  • AuthPassword The password used for endpoint authentication.
  • AuthUser The user name used for endpoint authentication.
  • EndPointURL The endpoint URL.
  • ProxyPassword The password used for proxy authentication.
  • ProxyPort The port of the proxy server to use.
  • ProxyServer The IP address or host name of the proxy server.
  • ProxyUser The user name used for proxy authentication.
  • SoapAction The value used in the SoapAction HTTP header.
  • SSLClientCertificateName A string identifying the client certificate to use for the Secure Sockets Layer (SSL) protocol, if any. The syntax is: [CURRENT_USER | LOCAL_MACHINE\[store-name\]]cert-name with the defaults being CURRENT_USER\MY (the same store that Microsoft Internet Explorer uses).
  • Timeout The timeout for an HttpConnector object. This timeout is in milliseconds.
  • UseProxy A Boolean property that specifies whether to use a proxy server. By default, this property is set to False indicating that a proxy server should not be used. Set this property to True if you want to use a proxy server. If you set this property to True and don't specify the ProxyServer property, then the HttpConnector object uses the proxy server set in the default settings of Microsoft Internet Explorer. In this release, the HttpConnector object ignores the Bypass Proxy settings in Internet Explorer.
  • UseSSL A Boolean value (True or False) that specifies the use of SSL. If this property is set to True, the HttpConnector object uses SSL connection regardless of whether HTTP or HTTPS is specified in the WSDL. If this property is set to False, the HttpConnector object will use SSL connection only if HTTPS is specified in the WSDL.

Selected SoapReader Object Members (Microsoft Soap Type Library)

Member Purpose
Load method (returns a Boolean value) Loads an XML data source to read from. Returns True if the load is successful; False if the load failed.
RPCResult property (returns an IXMLDOMElement object) Returns the first node in the SOAP response message's SOAP-ENV:Body element.

Selected SoapSerializer Object Members (Microsoft Soap Type Library)

Member Purpose
endElement method Ends a SOAP element.
startBody method Starts a SOAP Body element.
startEnvelope method Starts a SOAP Envelope element.
startElement method Given an element name, starts an element in the SOAP body. Optionally, you can also specify a namespace URI and style, as well as an element prefix.
writeString method Given a String value, writes the string into the SOAP message.

Selected IXMLDOMNodeList Collection Members (MSXML)

Member Purpose
item property (returns an IXMLDOMNode object) Given an index number, returns an individual node.

Selected IXMLDOMNode Object Members (MSXML)

Member Purpose
childNodes property (returns an IXMLDOMNodeList collection) Returns all of the node's child nodes.
nodeName property (returns a String value) Returns the name of the node.
nodeTypedValue property (returns a String value) Returns the strongly typed value of the node.