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:
- Use an HttpConnector object to connect to the XML Web service's endpoint URL and appropriate SOAP action.
- 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.
- 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:
|
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. |