Working with the Microsoft SOAP Toolkit
The Simple Object Access Protocol continues to gather more and
more keen supporters. As more developers embrace SOAP, the greater
the need for common libraries and tools – this is especially
important, as different SOAP implementations must be able to
co–exist in harmony. The Microsoft SOAP Toolkit bridges the gap
between current development strategies and the .NET approach. The
Toolkit breathes life into your legacy COM objects, allowing them to
be used as part of your .NET solutions.
Introduction
In my previous articles, I demonstrated how we could create SOAP
clients and listeners from scratch. Whilst this was a good way to
learn about SOAP, it's a fairly labor–intensive process. The SOAP
Toolkit alleviates some of pressure – notably there's a wizard that
creates a SOAP listener from a COM object. Client–side support is
also simplified by the provision of an object that can manage SOAP
requests and responses.
The Toolkit maintains the ethos behind SOAP – interoperability.
Whilst the Toolkit offers client–side support, it doesn't insist
that you use it – you're free to implement your own SOAP
Request/Response handlers on the client if you so desire. This
means, for example, that non–Windows 2000 clients that support HTTP
are able to take advantage of the server–side Toolkit
implementation. After all, the client isn't interested in how
the server processes a SOAP request, only that it does what
is asked of it.
Getting Started
I will assume that you have downloaded and installed the Toolkit
in default directory ( C:\Program Files\SOAP
Toolkit ). Familiarity with the SOAP architecture, and why
it's useful to us is also beneficial. The Toolkit is available for
download: http://msdn.microsoft.com/xml/general/toolkit_intro.asp?WROXEMPTOKEN=365433ZwwvvVQ81hbeQTnInzTN.
The current version of the Toolkit requires that you have Visual
Studio 6 SP3 installed.
There will be a database involved, currency.mdb – it's part of the download
associated with this article. Access to the database will be via a
DSN – you'll need to create one called ASPToday and point it to currency.mdb on your system.
I've built and tested my Toolkit applications using Windows 2000
Server. Microsoft has tested the Toolkit using Windows 2000 Server
and Professional. The Toolkit has been through basic testing with
Windows NT 4.0 SP6a and Internet Explorer 5.0. At the moment, there
is no Windows 9x release – although if you look through the
newsgroups mentioned at the end of this article, there are a couple
of third parties who have ported a version to Windows 9x.
The SOAP Toolkit Explained
The Toolkit comprises of three major parts:
- A Remote Object Proxy Engine (ROPE) – this is supplied as a
DLL. ROPE offers methods capable of packaging a SOAP Request, and
also "unscrambling" a SOAP Response.
- An SDL Wizard – this is an EXE that offers a step–by–step
means of gathering the information that is required to create an
SDL file and an ASP. SDL stands for Service Description Language.
SDL is used to allow SOAP clients the opportunity to find out
which methods are available from a given SOAP provider. The
Toolkit includes an early draft of the SDL specification. We'll
see an example SDL file later in this article.
- A SOAP client and the associated SOAP server – the client is
implemented as a Visual Basic application; the server is
implemented using ASP.
A Worked Example
To demonstrate the Toolkit, we'll need to understand what's
required on the server and what's required on the client. As soon as
we've covered the server and client requirements, we'll take a look
at the SDL Wizard.
Server–side Requirements
To demonstrate the server–side aspects of the Toolkit I will use
a simple COM object created using Visual Basic 6. The COM object is
called ASPToday.dll and offers three methods:
- GetCurrencyList – creates an XML fragment that contains a list
of countries and currencies
- GetCurrency – return a currency rate based on the specified
currency identifier
- SetCurrency – update the currency rate for a specified
currency identifier
You'll need to register the COM object ASPToday.dll . To register ASPToday.dll , click on the Start menu, choose Run – enter RegSvr32
C:\ASPToday.dll then click on OK . To unregister ASPToday.dll , use the command RegSvr32 /u C:\ASPToday.dll . In both cases
I've assumed that the DLL is in the root of your C: – you may need
to change this to suit your system. ROPE.DLL will also need to be registered on
the server.
The SDL Wizard
The SDL Wizard accepts as input the path to an existing COM
object (a DLL or EXE). We're able to select which methods we would
like to expose to our SOAP clients/consumers – thus we're not
committed to exposing every single method that the COM object
provides. The wizard also allows us to select the type of SOAP
listener we would like – there are two choices: ISAPI and ASP. The
ISAPI listener benefits from slightly better performance than the
ASP listener, however the latter benefits from greater degrees of
customization – after all, the ASP listener is VBScript. There's
more about the ASP listener later in this article.
The SDL Wizard creates two files based on the COM object:
- An XML file that provides the SDL for the methods that we
selected. The SDL file contains not only the methods, but their
data types also – we'll see how this can be of use to us when we
look at the client–side implementation.
- An ASP file that contains 'wrapper' functions for the methods
that we selected.
Client–side Requirements
ROPE.DLL (part of the Toolkit) also
needs to be installed and registered on the client system – you can
do this by using RegSvr32, as detailed above.
To demonstrate the Toolkit, I'll be creating a web page that
performs simple currency conversion – the page will send and receive
SOAP requests and responses.
Now that we know what we're trying to achieve, let's go through
the steps required to get it all to work.
Using the SDL Wizard
The SDL Wizard is a server–side task. Running SDLWizard.exe starts the wizard. There are
six steps; the first is a Welcome screen, the last is a Finish
screen. We'll take a look at the remaining four steps.
Selecting the source for wizard's output
The wizard needs to know whether you are asking it to create
source files from an existing COM object or from an existing SDL
file. We'll be creating source files for ASPToday.dll , so click on the Select COM object button then locate your
copy of ASPToday.dll .
As we'll see shortly, creating an SDL file by hand is a daunting
task, so it's likely that you'll be creating source for an existing
COM object. However it's feasible that another tool has created some
SDL for you – in which case the wizard is here to create the ASP
wrapper functions for you.
If all went well, the screenshot below should look familiar:
After you've selected your copy of ASPToday.dll , click on the Next button to
move to the next step.
Selecting the services you would like to
expose
The wizard uses the phrase services synonymously with
methods . We're now being asked to specify which methods from
ASPToday.dll that we would like to
make available to our SOAP clients. The wizard doesn't force us to
expose "all or nothing" – we can select just those methods we need
to expose. In our case, we're going to expose them all – click on
the ASPToday entry to select all the methods, as per the screenshot
below:
After you've made sure there's a tick beside the three methods
that we wish to expose to our SOAP clients, click on the Next button
to continue.
SOAP listener information
The wizard now requires the URI for the SOAP listener. I've got
my web server (IIS) configured such that localhost points to a directory called soaptk . Inside soaptk there is a directory called asptoday – this is where I am storing the
SOAP listener.
Specify the location for the new source
files
The new source files the wizard
talks about are in fact the SDL file and the ASP file that contains
the VBScript wrapper functions. In order to get this example to work
"out of the box", the location that these files are stored in should
be in the same place as the SOAP listener – the screenshot below
reflects this:
Clicking on Next completes the process. In the ASPToday directory there should be two new
files – Currency.xml and Currency.asp .
Here's the SDL for ASPToday.dll (
Currency.xml ): <?xml version='1.0' ?>
<!–– Generated 04/10/2000 21:33:39 by Microsoft SOAP Toolkit Wizard, Version 205.0.3 ––>
<serviceDescription name='ASPToday'
xmlns='urn:schemas–xmlsoap–org:sdl.2000–01–25'
xmlns:dt='http://www.w3.org/1999/XMLSchema'
xmlns:Currency='Currency'
>
<import namespace='Currency' location='#Currency'/>
<soap xmlns='urn:schemas–xmlsoap–org:soap–sdl–2000–01–25'>
<interface name='Currency'>
<requestResponse name='GetCurrencyList'>
<request ref='Currency:GetCurrencyList'/>
<response ref='Currency:GetCurrencyListResponse'/>
</requestResponse>
<requestResponse name='GetCurrency'>
<request ref='Currency:GetCurrency'/>
<response ref='Currency:GetCurrencyResponse'/>
<parameterorder>CurrID</parameterorder>
</requestResponse>
<requestResponse name='SetCurrency'>
<request ref='Currency:SetCurrency'/>
<response ref='Currency:SetCurrencyResponse'/>
<parameterorder>CurrID fRate</parameterorder>
</requestResponse>
</interface>
<service>
<addresses>
<address uri='http://localhost/asptoday/Currency.asp'/>
</addresses>
<implements name='Currency'/>
</service>
</soap>
<Currency:schema id='Currency' targetNamespace='Currency' xmlns='http://www.w3.org/1999/XMLSchema'>
<element name='GetCurrencyList'>
</element>
<element name='GetCurrencyListResponse'>
<type>
<element name='return' type='dt:string'/>
</type>
</element>
<element name='GetCurrency'>
<type>
<element name='CurrID' type='dt:string'/>
</type>
</element>
<element name='GetCurrencyResponse'>
<type>
<element name='return' type='dt:double'/>
</type>
</element>
<element name='SetCurrency'>
<type>
<element name='CurrID' type='dt:short'/>
<element name='fRate' type='dt:double'/>
</type>
</element>
<element name='SetCurrencyResponse'>
<type>
<element name='return' type='dt:boolean'/>
</type>
</element>
</Currency:schema>
</serviceDescription>
As you can see, it's not the kind of file we would like to create
by hand!
The SDL file comprises of three sections:
- <interface> – this element
records the methods that the listener supports.
- <service> – this element
specifies the location of the ASP SOAP Listener that is able to
process SOAP requests.
- <schema> – this element is
prefixed with a namespace to uniquely identify it as being part of
the Currency interface. This means that there can be more than one
<schema> element – one for
each interface. The <schema>
element identifies the parameters and data–types that each method
expects when called via a SOAP request. It also identifies the
return parameters and data–types. For example, we can see that the
SetCurrency SOAP Request should be
packaged up with two parameters – CurrID and fRate . CurrID is of type short and fRate is a double . After the SetCurrency method has executed, the SOAP
Response has one parameter – return
. It has a type of boolean .
Currency.asp , the ASP VBScript
wrapper functions are shown below: <%@ Language=VBScript %>
<% Option Explicit
Response.Expires = 0
'––––––––––––––––––––––––––––––––––––––––––––
' SOAP ASP Interface file Currency.asp
' Generated 04/10/2000 21:33:39
' By Microsoft SOAP Toolkit Wizard, Version 205.0.3
'––––––––––––––––––––––––––––––––––––––––––––
Const SOAP_SDLURI = "http://localhost/asptoday/Currency.xml" 'URI of service description file
%>
<!––#include file="listener.asp"––>
<%
Public Function GetCurrencyList ()
Dim objGetCurrencyList
Set objGetCurrencyList = Server.CreateObject("ASPToday.Currency")
GetCurrencyList = objGetCurrencyList.GetCurrencyList()
'Insert additional code here
Set objGetCurrencyList = NOTHING
End Function
Public Function GetCurrency (ByVal CurrID)
Dim objGetCurrency
Set objGetCurrency = Server.CreateObject("ASPToday.Currency")
GetCurrency = objGetCurrency.GetCurrency(CurrID)
'Insert additional code here
Set objGetCurrency = NOTHING
End Function
Public Function SetCurrency (ByVal CurrID, ByVal fRate)
Dim objSetCurrency
Set objSetCurrency = Server.CreateObject("ASPToday.Currency")
SetCurrency = objSetCurrency.SetCurrency(CurrID, fRate)
'Insert additional code here
Set objSetCurrency = NOTHING
End Function
%>
There's not much to these functions, they offer an ASP front–end
to the methods that our COM object provides. It's possible to
include additional code in the ASP functions – thus we're able to
amend the input parameters prior to the COM object methods being
called. Likewise, we can amend the outputs after the methods have
been called. This is perhaps useful if the source code for that
essential COM object has gone missing!
The Toolkit is supplied with a generic listener.asp – reading through the code
above, it becomes clear that we need to copy this file in to the
ASPToday folder. You'll find a copy of
listener.asp in the C:\Program Files\SOAP_Toolkit\ASP_Listener
directory – assuming that's where you installed the Toolkit.
Inside the Remote Object Proxy Engine
ROPE is primarily used client–side, however listener.asp actually uses it too – ROPE.DLL provides methods that can process
SOAP requests and SOAP responses.
ROPE Objects
ROPE.DLL exposes seven objects:
- Proxy is considered by Microsoft to be the primary
client–side interface for accessing SOAP endpoints. We'll see an
example of the Proxy object later in this article.
- SOAPPackager is able to package SOAP requests; it's
also able to "unpack" SOAP responses.
- WireTransfer is responsible for sending the SOAP
Request over "the wire". Essentially, it provides all the HTTP GET
and POST interactions that are required.
- ServiceDescriptors , SDMethodInfo ,
SDParameterInfo , and SDEndPointInfo provide a means
of iterating over the SDL file examining the methods and
parameters on the way. An examination of these objects is beyond
the scope of this article.
ROPE provides two mechanisms for a client to send a SOAP request.
The first method involves using the Proxy object; the second
involves a combination of the SOAPPackager and WireTransfer
objects.
I'll be demonstrating these objects using an example web page
with some VBScript; the screenshot below depicts the web page:
Using the Proxy object
The proxy object allows us to treat our SOAP methods as if they
were in a COM object that is installed on the local machine, i.e. we
can bind to the SOAP method at run–time.
The code behind the Update Rate
button demonstrates the Proxy object in action: function update(sCurrency, sRate)
dim objProxy
Set objProxy = CreateObject("ROPE.Proxy")
objProxy.LoadServicesDescription icURI, "http://localhost/asptoday/currency.xml", ""
objProxy.SetCurrency sCurrency, sRate
Set objProxy = Nothing
end function
Unfortunately, ROPE.DLL is not
"marked as safe", therefore Internet Explorer will take the action
specified by the "Initialize and script ActiveX controls not marked
as safe" security option. Depending upon your settings, you may
experience difficulty getting the example to work. To amend the
security option in Internet Explorer, choose the Tools menu, then Internet Options . Locate the ActiveX controls and plug–ins item – then
change the Initialize and Script ActiveX
controls not marked as safe option to enable or prompt . Alternatively, you can change the
overall security settings to Low . If
you experience further problems it may be because of the XML Parser
type you are running. To solve this and ensure the parser is in
Replace mode, run xmlinst.exe which is found at C:\WINNT\System32 .
Using the SOAPPackager and WireTransfer
Objects
The proxy object gave us an easy means of executing our SOAP
methods – however, what if the SOAP methods no longer exist? The
proxy object has no means of helping us. The SOAPPackager object,
however, can help us – it offers a method IsMethodAvailable that allows us to check
for the existence of a method before we create a SOAP Request.
The code behind the Convert button
demonstrates the greater power offered by the SOAPPackager and
WireTransfer objects: function convert(sCurrency, sAmount)
dim objSP
dim objWT
Set objSP = CreateObject("ROPE.SoapPackager")
Set objWT = CreateObject("ROPE.WireTransfer")
objSP.LoadServicesDescription icURI, "http://localhost/asptoday/currency.xml", ""
' check to see that we can execute the method
if objSP.IsMethodAvailable("GetCurrency") then
' Prepare the SOAP Request for the getCurrency method...
sRequestStruct = objSP.GetMethodStruct("GetCurrency", icINPUT)
objSP.SetPayloadData icREQUEST, "", "GetCurrency", sRequestStruct
objSP.SetParameter icREQUEST, "CurrID", sCurrency
sRequestPayload = objSP.GetPayload(icREQUEST)
' Add the usual SOAP Headers to SOAP Request
objWT.AddStdSOAPHeaders "http://localhost/asptoday/currency.asp", "GetCurrency", Len(sRequestPayload)
' Send the SOAP Request over "the wire" using HTTP...
sResponsePayload = objWT.PostDataToURI("http://localhost/asptoday/currency.asp", sRequestPayload)
' Format the payload to be a SOAP Response
objSP.SetPayload icRESPONSE, sResponsePayload
' Extract the 'return' element
sRate = objSP.GetParameter(icRESPONSE, "return")
' perform the calculation
txt_converted_amount.value = sRate * sAmount
end if
Set objSP = Nothing
Set objWT = Nothing
end function
The call to objSP.GetMethodStruct
retrieves an XML fragment that matches the GetCurrency method: <GetCurrency>
<CurrID></CurrID>
</GetCurrency>
The XML fragment is built from the entry in the SDL file: <element name='GetCurrency'>
<type>
<element name='CurrID' type='dt:string'/>
</type>
</element>
At this point, CurrID is empty. The
call to objSP.SetParameter sets the
CurrID parameter accordingly: objSP.SetParameter icREQUEST, "CurrID", sCurrency
However, it's not until we examine the whole SOAP Request payload
that we can confirm that CurrID was
set. Calling objSP.GetPayload returns
the SOAP message (formatted as a request or a response – in this
case we're dealing with a request): sRequestPayload = objSP.GetPayload(icREQUEST)
The final SOAP Request payload looks like this: <?xml version="1.0"?>
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP:Body>
<GetCurrency>
<CurrID>1</CurrID>
</GetCurrency>
</SOAP:Body>
</SOAP:Envelope>
All that's required now, is the addition of SOAP headers, and our
request is ready to post to the listener: ' Add the usual SOAP Headers to SOAP Request
objWT.AddStdSOAPHeaders "http://localhost/asptoday/currency.asp", "GetCurrency", Len(sRequestPayload)
' Send the SOAP Request over "the wire" using HTTP...
sResponsePayload = objWT.PostDataToURI("http://localhost/asptoday/currency.asp", sRequestPayload)
PostDataToURI performs an
asynchronous post to the SOAP listener – i.e. it will block until a
timeout or a response is received. Assuming a response is received,
PostDataToURI returns a formatted SOAP
Response.
We can then call GetParameter to
extract the XML element return – this holds the actual exchange rate
value. ' Format the payload to be a SOAP Response
objSP.SetPayload icRESPONSE, sResponsePayload
' Extract the 'return' element
sRate = objSP.GetParameter(icRESPONSE, "return")
' perform the calculation
txt_converted_amount.value = sRate * sAmount
Type Safety
As developers we're used to the benefits that type–safety can
offer us. The SDL doesn't just specify the methods that a SOAP
consumer may use, but also specifies the data–types that method
uses. The data–type is specified by the addition of the data–type
attribute to an SDL schema element: <element name='SetCurrency'>
<type>
<element name='CurrID' type='dt:short'/>
<element name='fRate' type='dt:double'/>
</type>
</element>
Whilst our web page example can't take advantage of type safety,
your favorite typed language can. If you get the types of your
parameters wrong, server–side ROPE will respond with a SOAP fault –
indicating a type mismatch.
Passing XML as Parameters
The GetParameter method in the
current version of ROPE will remove XML element tags. This is
peculiar because the SOAP Request Payload actually contains the XML
element tags. The current solution to this problem is to enclose any
XML in a <![CDATA[…]] section.
There's an example of how to do this in the ASPToday.dll source code.
Using <!CDATA[ returns a single
string that contains an <id> ,
<country> and a <currency_name> . Thus we have lost
the notion of type–safety. It's possible to modify the SDL file to
allow the data types to be specified. There's a good discussion
about the merits of this technique and the <![CDATA[ technique at http://msdn.microsoft.com/xml/articles/soapguide_ado.asp?WROXEMPTOKEN=365433ZwwvvVQ81hbeQTnInzTN.
Summary
This article has proven that there is a place for the SOAP
Toolkit in your development portfolio. The .NET approach offers C#
as the language for choice for web services, rewriting your existing
COM objects in C# is time–consuming and would introduce new bugs.
Thus, the Toolkit is a great way of extending the life of the COM
objects you can't afford to be without. There is, fortunately,
considerable overlap between the SOAP Toolkit and the .NET approach
– both use the SDL.
Finally, if you're keen to stay abreast with what's happening in
the world of SOAP there are two newsgroups that I can recommend.
Both are located on the msnews.microsoft.com server:
microsoft.public.xml.soap and
microsoft.public.mdsn.soaptoolkit |