In the dialog you need to specify a file location for the Web Service.
This file should be placed into a Web virtual directory, because the Web
Server will actually access this file and route it to the Web Connection
Web Service handler via the .wwSOAP script map extension. The actual template
generated looks like this: The Web Service consists of a small loader that's called by the Web
Connection Web Service engine, which in turn loads the class and calls
the method in question. Note that the class is created with the OLEPUBLIC
keyword. The Web Connection Web Service classes don't load this class
as a COM object however – the OLEPUBLIC is only used to create an SDL
file for consumption by MS SOAP as well as providing the ability to compile
your object into COM object that can be called from an MS SOAP hosted
Web Service. More on that later. The Wizard also generates a test method into the class as well so you
can check out the Web Service easily. Let's remove that test class and
instead add our GetStockQuoteSimple() function into the class as a method
like this: That's all there's to it! Voila, you've created your first Web Service! Before you go on make sure the Web Connection Server is running. Note
that Web Services in Web Connection are dynamically compiled both under
VFP 6 and 7. This means that you can make changes to the Web Service while
the Web Connection server is running and without stopping the Web service.
Let's make sure that the service actually works. Since this service
exists on the Internet on the West Wind Web Site I'll use that as an example.
wwSOAP includes a SOAP Method Tester form that you can use to quickly
check out a Web Service. Figure 2 shows how to set up the form to call
our newly created Web Service:
Figure 6 – The SOAP method tester allows you to quickly test
Web Services without writing any code.You can also view the SOAP Request
and Response to see what the SOAP messages look like.
Fill in the URL to the Web Service in this case the I used the service
running on the West Wind Web Site. If you're testing on your local machine
use localhost and the virtual directory that you copied the Web Service
to instead. Enter the method name and each of the parameters required,
in this case on the symbol and set the type. Note what happens if you
play with the types. Try passing an integer instead of a string. You get
an error, which is the error message thrown by the FoxPro Web Service
code: Variable 'LCHTML' is not found. wwSOAP embeds type information
into the SOAP messages and the Web Service on the other end interprets
those types. So when you passed an integer from the client it gets passed
to the server application which fails in the GetStockQuoteSimple() method
call because we didn't check for a non-string input parameter.
If you click on the SOAP Request button you can look at the request
packet traveling over the wire: This is a basic SOAP request envelope that consists of the following
sections:
The SOAP Envelope The SOAP Body The SOAP response is very similar: The layout of the SOAP Response is identical to the Request with the
exception that here the return value is returned in the body section. The SOAP Method Tester uses the wwSOAP client behind the scenes to perform
the SOAP method calls. Let's see what we have to do to call the service
with code: Pretty easy, eh? You basically specify the URL to call, add parameters
then call the method. CallMethod() goes out and does all of the hard work
of packaging up your parameters into the SOAP XML packet, sending the
request over the wire and decoding the result into a simple return value.
wwSOAP alsoincludes low level methods that let you do these steps independently
and properties like cRequestXML and cResponseXML let you view what goes
over the wire. The wwSOAP documentation help file includes examples on
how to do this.
Notice when you run this that the Web Service returns a character value.
That's not really a big problem – you can just run VAL() on the returned
quote, but it would be much nicer if the server actually did this for
us. Since wwSOAP and most SOAP applications understand embedded types
in the SOAP packet we can make a simple change in our Web Service code
to return a numeric value. Just change the last line in the GetStockQuoteSimple
method of the Web Service to: Save, then simply re-run the SOAP test code above and notice that now
the value returned is a numeric value! Notice how simple this process
is: You didn't have to recompile or stop the server. You simply change
the code and the new value is immediately returned to you.
Returning a single value like a stock is nice, but it's not all that
useful. If you wanted to retrieve information about a stock you'd probably
want to know a few things about the stock. Like the high and low and
change for the day, the actual time of the quote and a few other things.
You could set up multiple Web Service methods that return each of these
values and then make several SOAP calls to these methods, but this would
be vastly inefficient since it requires multiple round trips to the server.
SOAP calls have a fair amount of overhead in the packaging and unpackaging
of parameters and return values, and passing that data over the Web and
through the Web Server. There is a base amount of overhead that occurs for
every hit in addition to the time it takes to run the actual request, so
bundling up data into a single SOAP package is a big advantage.
For this reason SOAP supports embedding of object parameters and
return values. Since VFP can return objects from method calls and wwSOAP
supports objects you can create a method that returns an object as a
result value. There's a catch though as we'll see in a minute: The client
side must provide an object instance to receive the SOAP result.
To demonstrate lets add a new method to our Web Service called
GetStockQuote which will return an object that contains several properties
retrieved from a stock quote retrieved from the NASDAQ Web site. The
NASDAQ site provides quotes in XML format and this method retrieves values
from this XML packet: This code retrieves the XML based stock quote from NASDAQ and then
parses several of the XML properties into object properties for easier
access and returns the newly created object over SOAP.
If you now call this method with the SOAP Method Tester you can use:
Url:
http://www.west-wind.com/wconnect/soap/stockservice.wwsoap
Method: GetStockQuote
Parameter: lcSymbol - MSFT - string
The result that is returned is an XML fragment that looks like this: The SOAP Method tester simply displays this XML fragment, but when you
use code to call this method you can actually retrieve the object directly.
Here's the client code to do this: That's pretty cool, isn't it? Now you can simply have the server
return objects that you can serialize over the wire with SOAP and rebuild
these objects on the client side by deserializing them into existing
objects.
*** DO NOT REMOVE - CALL WRAPPER
PARAMETERS lcMethod, lcParmString, lvResult
PRIVATE _loServer
_loServer = CREATEOBJECT("StockService")
lvResult = Eval("_loServer."+ lcMethod + "("
+ lcParmString+ ")" )
RETURN lvResult
ENDFUNC
*** DO NOT REMOVE - CALL WRAPPER
*************************************************************
DEFINE CLASS StockService AS Session OLEPUBLIC
*************************************************************
Calling the Web Service
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<GetStockQuoteSimple>
<lcSymbol xsi:type="xsd:string">MSFT</lcSymbol>
</GetStockQuoteSimple>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The envelop contains the SOAP header that define the SOAP and related
namespaces such as xsi and xsd used for data types. The envelop can
also contain an optional <headers> section that lets you provide
custom headers to a request. Custom SOAP server implantations can
read info from the header and use it internally.
This is the meat of the SOAP packet. It contains the method call interface.
In the request this consists of the name of the method to call on
the server and all of the parameters passed. The parameters are each
described as sub-elements of the method and with wwSOAP are typed
with XML types.
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<GetStockQuoteSimpleResponse>
<return xsi:type="xsd:string">65.188</return>
</GetStockQuoteSimpleResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
* Function GetStockQuoteSimpleSOAP
LPARAMETERS lcSymbol
RETURN VAL( EXTRACT(lcHTML,"N=",CHR(13),CHR(10)) )
Expanding the Stock Web Service with Objects
************************************************************************
* StockService :: GetStockQuote
****************************************
*** Function: Returns a Stock Quote
*** Assume: Pulls data from
msn.moneycentral.com
*** Pass: lcSymbol -
MSFT, IBM etc.
<return xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xsi:type="record">
<ccompany>Microsoft Corporation</ccompany>
<csymbol>MSFT</csymbol>
<nhigh>66.13</nhigh>
<nlast>65.19</nlast>
<nlow>61.13</nlow>
<nnetchange>3.31</nnetchange>
<nopen>61.88</nopen>
<nperatio>38.12</nperatio>
<tupdated>2000-10-20T00:00:00</tupdated>
</return>
* Function GetStockQuoteSOAP
LPARAMETERS lcSymbol