[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ]

Adding custom functions to the SDL

If you look closely at the ASP functions that the Wizard generates you can see that you don't necessarily need to call the COM components. You could for example run just plain ASP code in these functions. In fact, this is an easy way to create 'remote scripting' code that returns results to the client.

You can also add custom functions to this ASP document, but if you do you need to add the functions you create to the SDL document manually. For example, to add a HelloWorldAspOnly method to the SDL file you'd add the following:

 

            <requestResponse name='helloworldasponly'>
                <request ref='SoapServer:helloworldasponly'/>
                <response ref='SoapServer:helloworldasponlyResponse'/>
                <parameterorder>lcName</parameterorder>
            </requestResponse>
 
into the Interface section and
        <element name='helloworldasponly'>
            <type>
                <element name='lcName' type='dt:string'/>
            </type>
        </element>
        <element name='helloworldasponlyResponse'>
            <type>
                <element name='return' type='dt:string'/>
            </type>
        </element>
 
into the SoapServer section.
You can now add a new function to the ASP page:
Public Function helloworldasponly (ByVal lcName)
 
helloworldasponly = "Hello from ASP, " & lcName & ". Time is: " & now
 
End Function

 

Voila – you've added a new method to your Web service and without even recompiling any code. Think of this as an easy way to do remote scripting.


SOAP and Variants

Speaking of scripting, notice that the server I built also included a generic method called Evaluate. This method takes a string input parameter and a variant output result. Variants are handled specially by SOAP – basically you can return variant data from your functions as any type. You can return strings, dates, Boolean and numbers. For example try this:

    LPARAMETER lcEvalCommand
    LOCAL oProxy as Rope.Proxy, oWire as Rope.WireTransfer
     
    oWire=CREATEOBJECT("Rope.WireTransfer")
    lcXML = oWire.GetPageByURI("http://localhost/soap/soapserver.xml")
     
     
    oProxy = CREATEOBJECT("Rope.Proxy")
    ? oProxy.LoadServicesDescription(2, lcXML) && .T./1
     
    lvResult = oProxy.evaluate (lcEvalCommand)
    ? VARTYPE(lvResult)
    ? lvResult
     
    Then run the following in the command window:
    soapproxy("sys(0)")
    soapproxy("DateTime()")
    soapproxy("Month(DateTime())")

What you'll see is that SOAP will return the correct values, but it returns them all as strings rather than their proper types. You'd have to do the type conversions yourself.

Note that this is a very powerful (but potentially dangerous) concept – you can create generic methods that execute code on the server. You could for example add a method called Execute to the COM server that runs a block of VFP code and returns a result by using the COMPILE command to actually execute that block of code (or you can use VFP7's new ExecScript() function for this). However, without access restrictions this becomes a very dangerous proposition – if you can run code generically you can generically delete your hard disks file too (ExecScript("erase \winnt\system32\*.*") anyone?)…

Soap and XML results


(note this may change by the time you read this XML support is promised in updates)

The current release of the SOAP toolkit does not directly support XML parameters and results. The sample server includes an XML result method getXML, which passes a single lcName parameter and returns that parameter wrapped into an XML block:


    <?xml version="1.0"?>
    <docroot>
       <name>Rick</name>
    </docroot>

To call this method against the Web Serivce use:


    lvResult = oProxy.getxml("lcName")

You'll get a 'Bad SOAP return' error. This 'generic' error message is about the only error you get from the dynamic method interface, and it's not terribly useful.

If you run this request through the SoapManual.prg (change the method name and parameter in the PRG) file you'll see that the SOAP Response actually returns the XML string to the client. However, because the XML is not delimited in a special way the returned XML is actually an invalid XML document, resulting in the SOAP call to fail:

    <?xml version="1.0"?>
    <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <SOAP:Body>
    <getxmlResponse>
    <return><?xml version="1.0"?>
    <docroot>
       <name>Rick</name>
    </docroot></return>
    </getxmlResponse>
    </SOAP:Body>
    </SOAP:Envelope>

The above is invalid XML, so the parser that ROPE uses fails to do anything with the XML document. There's a crude workaround you can use for this:

  • Add a <![CDATA[ ]]> tag around the output generated in your code

  • Add a <![CDATA[ ]]> tag around the ASP function that returns the XML

 

I like the latter approach the least of the two evils:


    Public Function getxml (ByVal lcName)
       Dim objgetxml
       Set objgetxml = Server.CreateObject("soapdemo.SoapServer")
     
       getxml = "<![CDATA[" + objgetxml.getxml(lcName) + "]]>"
       'Insert additional code here
      
       Set objgetxml = NOTHING
    End Function

Now the XML response is valid:


    <?xml version="1.0"?>
    <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <SOAP:Body>
    <getxmlResponse>
    <return><![CDATA[<?xml version="1.0"?>
    <docroot>
       <name>Rick</name>
    </docroot>
    ]]></return>
    </getxmlResponse>
    </SOAP:Body>
    </SOAP:Envelope>

Unfortunately, this only solves part of the problem. If your XML result includes a CDATA section of its own, the above won't work still resulting in an invalid XML document. Due to XML's limitations on what can be contained inside of a CDATA section you can't embed complex XML inside of the data.

The problem is more farreaching than this as well – if you return a result string that contains extended characters you may also run into trouble because of the way that ROPE packages the XML manually (not using the parser). For example, try this (in SoapProxy.prg):

    lvResult = oProxy.helloworld("Rζ¦ick")

You'll get an error that nothing was posted.

If you try this with the SoapManual.prg you'll find that the data is not getting posted to the server

and the result times out after 10 seconds (ROPE's default timeout).

SOAP Problems

As you can see there are few problems with the SOAP toolkit. Here are a list of things that I ran into when playing with it:

 

Parameter/Return type support limitations

  • No direct XML parameter support
    This supposedly got fixed in the July release of the toolkit, but I couldn't find any documentation on it.

  • Extended character problems
    It appears the ROPE code creates the SOAP package XML manually and doesn't properly encode the XML so extended characters will create invalid XML messages that won't properly load into the XMLDOM. This in turn results in call failures

  • No support for objects
    Objects are a tricky thing to pass around in a distributed environment, but it would be nice if the toolkit could at least persist objects in some way. In custom XML applications you would normally persist objects to XML, but with the above XML limitations this is not possible at least not reliably.

Wire Protocol Problems

  • Lack of SSL support
    You cannot access Web Services over SSL using the ROPE client. Just about any B2B application will require secure communication.

  • Lack of Authentication support
    You cannot pass authentication information to the client to allow only certain users to access your components. This means there's no application level control over who has access to your Web services. The only block possible is based on NT authentication at the ASP file level (module level) as opposed to the method level. You can't let one method be accessed by a specific user and have another method open to all for example.

  • Limited Proxy support
    Limited proxy support requires hand configuration.

  • No support for browser hosting
    ROPE.DLL is not available to browser applications as a safe and signed control so you can't take advantage of this functionality in a browser. That's too bad really, because browser based applications are among the most obvious consumers of Web Services.

Whether these issues are show stoppers for you depends on your application and implementation. As showed you before, you can actually sidestep many of these issues by build a custom SOAP client and server using Wininet to address the Wire Protocol issues and using custom code that uses the XMLDOM to create messages to ensure proper message types. The only insurmountable issue then remains to be passing <![DATA[]] as part of XML messages around.

Note, that because all source code is provided you can even fix up the ROPE client directly instead of rebuilding everything from scratch. However, building a ROPE client in Visual FoxPro code is surprisingly easy to do and may well be worth the effort in the control it gives you. You can also build basic ROPE-like functionality using scripting and the XMLDOM in Internet Explorer.

Check our Web site for updates that will include VFP and Jscript SOAP clients that can interact with the Microsoft SOAP toolkit and custom VFP SOAP servers created with any VFP capable tool (ASP with COM, Web Connection, FoxISAPI etc.).

In the next section I'll describe using a free VFP based SOAP implementation called wwSOAP that can work around some of the problems described with the MS SOAP Toolkit.

 

West Wind Web Connection and Web Services

 

West Wind Web Connection includes direct support for SOAP based Web Services with both a wwSOAP client implementation and a wwWebService process class that can handle incoming SOAP requests mapped to a specific .wwSOAP scriptmap extension in IIS. To demonstrate how all of this works I'll implement a stock lookup service as a SOAP application. We then also look at another example in a VFP fat client application that is a bit more sophisticated in a Time and Billing sample application. All of the samples are available as part of the free wwSOAP classes which you can download from http://www.west-wind.com/wwsoap.asp.

 

Retrieving Stock Quotes from the Web

 

Getting stock quotes over the Web

Let's talk a little about the application I'll build as an example. Now I want you to understand up front that there are other ways to do this example application, especially because some of this data that I'll be using for quotes is directly available over the Web. However, this is meant as an example to show how to present data and as such shows a variety of ways that you can consume data from Web Services.

This application is an HTML based Web Server application that allows you to add stocks to a personal portfolio. The user enters a symbol name and a qty and the app then recalculates the portfolio based on the current stock prices. The portfolio form also contains a simple stock quote retriever that lets you pull a single quote and display the stock price and other info. The stock data is retrieved from a SOAP Web Service that I'll describe in detail. The Web Service is responsible for retrieving the stock quotes in a variety of ways. The Web Service retrieves the actual stock information from the NASDAQ and MSNBC Web sites (I used both for a little variety <s>).So, we're dealing with three Web sites here: The Web site that runs the portfolio application, the Web site that hosts the SOAP Web Service and the stock server at NASDAQ or MSNBC. The portfolio application can be considered an aggregation engine that consolidates data from the local data store (the portfolio) and the Web Service.

 

Getting a Stock Quote from MSNBC

Let's start with retrieving only a single stock price based on a symbol to demonstrate the basics of how Web Services work. Here's the code that retrieves a stock quote from the MSNBC Web site using the wwHTTP class (included as part of wwSOAP):

    ************************************************************************
    * SOAPService :: GetStockQuoteSimple
    ****************************************
    ***  Function: Returns a stock quote by symbol
    ***    Assume: Must be connected to Web and msn.moneycentral.com
    ***      Pass: lcSymbol  -
    ***    Return: Last stock price in string format
    ************************************************************************
    FUNCTION GetStockQuoteSimple(lcSymbol as String) as String
     
    lcSymbol = UPPER(lcSymbol)
     
    oHTTP=CREATEOBJECT("wwHTTP")
    lcHTML=oHTTP.HTTPGet("http://www.msnbc.com/tools/newsalert/nagetstk.asp?s=" + lcSymbol)
     
    RETURN EXTRACT(lcHTML,"N=",CHR(13),CHR(10))
    ENDFUNC
     
    To get the latest stock price for Microsoft for example you'd simply do:
    lcQuote = GetStockQuoteSimple("MSFT")

  

What you'll see is a string result that returns something like: 65.888. A pretty depressing number when considered that it's off from Microsoft's 120 high earlier this year, huh?

Easy enough. So, now lets set this up as a Web Service that can be generically called from other applications. To do this with Web Connection you can use the Create Web Service option of the Web Connection Management Console. To start the console type: DO CONSOLE and you'llget the wizard shown in Figure 1.

 

 

Figure 5 – Creating a new Web Service involves specifying of a new file to create the Web Service class into. The template contains the class plus a small loader function.  

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ]