The SOAP Toolkit allows for clients to use two interfaces to access data. The high-level interface reads in the WSDL file and, based on how you call the methods, just knows what to do. It also allows for low-level interaction where you craft the entire SOAP message and handle the response. You can also create custom type mappers to handle data that you want the high-level interface to be able to handle. As you may have guessed after reading the last section, a custom type mapper uses the low-level interface to make the high-level one easier to work with.
In this section, we will write four different functions that use the high-level interface, a customer type mapper, and the low level interface. All of this will be demonstrated in the VBCh10Test application.
The high-level interface allows for you to interact with any Web Service that sends back simple data types or arrays of simple types. Anything else will require you to manipulate the XML by hand somewhere. To use the high-level interface, simply declare an instance of the MSSOAPLib.SoapClient COM object. Then, load up the WSDL file and call the functions as though the SoapClient object supported them. Under the covers, it maps those calls to the WSDL file, makes the calls, and interprets the results for you. Listing 10.12 shows some code that calls HelloWorld and displays the response in a message box.
You can even get good results when you need to read back an array of integers. The GetRandomNumbers call works equally well. This one takes three arguments and returns a set of random numbers within the specified range. To get 10 random numbers between 0 and 10, you would call the Web Service using the code in Listing 10.14.
Listing 10.14 reads a local copy of the WSDL file. The SOAP Toolkit documentation recommends that the WSDL file always be located on the same machine as the client application. You can load the WSDL over the Internet or LAN, but doing so involves longer delays in getting the file. Why is this a safe thing to do? Odds are very good that if the Web Service changes, your client will fail, regardless of how up-to-date the WSDL is. If you get the WSDL from the Web Service computer, the function may work, but the results may be unusable. By storing the WSDL on the client machine, the changes are still breaking ones. As a result, you are better off keeping the WSDL file on the client. A deployed Web Service should not change its interface. If it does, clients are expected to break. This will either force the creators of the clients to update the code or abandon the Web Service for something more stable.
When you need to use simple types that the toolkit understands natively, you do not need to do a lot of work. The toolkit ups the ante when you need to read in a custom type. If you will only read the type using one call, you will not save much time by creating a custom type mapper. On the other hand, if you call the function a lot, a custom type mapper can come in handy. To create one, you need to make your own WSML file and COM object. The WSML file only needs to indicate the type and the COM object that knows how to translate the type into a COM object. The COM library VBCh10TestMappers handles the mapping, and a customized version of Ch10STKExSvr.wsml tells the toolkit how to use the COM library. Listing 10.15 shows the WSML file.
The class mapping the XML to COM objects does not need to keep its dispatch IDs constant. Because the SOAP Toolkit only attaches to the ISoapTypeMapper interface, the toolkit already knows what it is looking for and can handle using the functionality simply through the PROGID. Listing 10.16 shows the three functions that have any code for the client side: Init, varType and read.
To put this all together, the executable tells the mssoapinit method to look in the WSML file for any types it does not know how to handle. When the library sees a Person come over the wire, it will load up the specified type mapper and tell it to read the XML. The method returns a Variant that the client can then use to get at the actual binary data. Using the type mapper allows you to keep your code fairly simplistic. Listing 10.17 shows the client code that takes advantage of the WSML file.
So, how do you handle an array of complex data? You execute the function and then manipulate the XML result however you see fit. When the toolkit does not know how to map the data, you will always get back an instance of an IXMLDOMNodeList. Using it, you can see any data returned within the body result. You might think that the serialization we did might make this difficult. It doesn't. Instead, we have three objects that each contain three fields. Listing 10.18 shows how you would extract this information.
So, what would you do if you needed to access the Web Service the hard way? You would use the low-level interface.
The low-level interface allows you to control the creation of the SOAP message. The low-level interface comes in handy when a WSDL file does not exist or when you need to craft the message by hand due to incompatibilities in between toolkits. To create the message, you need to know all the details about what the server is expecting. In particular, you need the following information:
The SOAPAction value (only if required for routing)
Address to send the request to
Name of the message
Names of any parameters
Order the service requires the parameters to appear in
How to interpret any return values
After you have all this information, you can call the Web Service the hard way. Sometimes, you just have to use the low-level interface. We will look at calling HelloWorld by crafting the entire message through custom code.
To create the connection, you need to use two classes—SoapSerializer and SoapConnector. SoapConnector specifies the interface for something that can send and receive SOAP messages. In our case, we will instantiate an HttpConnector to send the request to an HTTP endpoint. We saw SoapSerializer in the server code. It's still responsible for knowing how to create the actual SOAP message. Finally, we will need to use SoapReader. SoapReader knows how to read a SOAP message, and we will use it to read the response. Listing 10.19 shows how to put all these classes together to send and receive a simple SOAP message.
If the data being returned is more complex, you will have more code that handles the SoapReader.RPCResult member to discover what XML came back. Likewise, a method that takes parameters or complex types will have more serialization code inside the construction of the method name. Whatever you need to do with SOAP, it should be possible using the low-level interface.