Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Object parsing for Web Services called by Visual FoxPro

I’ve been struggling with the concepts of parsing FoxPro objects into XML for use in Web Services as part of wwSOAP for quite a while. My wwXML utility makes it relatively painless to generate XML from an object, but this XML is limited to lower case characters for entity names because VFP does not support proper case objects.


To generate this XML is pretty simple with:


oInv = CREATEOBJECT("cInvoice")

? oInv.Load(        3144)

lcXML = oXML.ObjectToXML(oInv,"InvoiceDocument")


The XML generated from a complex object might look like this:


<?xml version="1.0"?>


      <InvoiceDocument type="object" class="cinvoice">



            <ocustomer type="object" class="ccustomer">

                  <odata type="object" class="empty">

                        <address>32 Kaiea Place</address>


                        <company>West Wind Technologies</company>

                        <country>United States</country>






            <odata type="object" class="empty">

                  <cc>4111 1231 1231 1231</cc>











            <olineitems type="object" class="clineitems">


                        <arows_item type="object" class="empty">


                              <descript>West Wind Html Help Builder 4.0</descript>







                        <arows_item type="object" class="empty">


                              <descript>West Wind Web Connection Training Montreal</descript>















It works but as you can see all the property names  lower case, which is a problem if you need to send this object to a server that uses proper names. Which is likely if you’re calling a .NET or Java Web Service.


A number of people last week at DevCon were asking about how to properly convert objects and my answer has been to just bite the bullet and hand create the XML. You can do this fairly easily especially if you use wwXML’s AddElement method to do the proper type conversions for you. But this is still a major hassle.


Sooooo… after some more thought about this I decided that there is another way to do this: You can use the WSDL type definition to parse an object and instead of using the property names from VFP to generate the element names we can use the property names defined in the WSDL file. This is not too terribly complicated, but it does get a bit tricky for array and collection handling. Luckily when calling .NET services at least collections must be typed properly and persist very much the same way as arrays so the structural layout for both of these is similar.


By doing this you are limited to the structure that is in the WSDL file so you have to create an object that maps property names to what’s available in the Web Service. In the end I created a parser in the free wwSOAP class that looks something like this:



* wwSOAP :: CreateObjectXmlFromSchema


***  Function: Creates XML from an object using the schema to

***            create the nodes.


FUNCTION CreateObjectXmlFromSchema(loObject,lcTypeName,;


LOCAL lcXML, lnCount, x, lcFieldName, lcFoxType


IF EMPTY(lcPropertyName)

   lcPropertyName = lcTypeName


IF EMPTY(lnIndent)

   lnIndent = 0



*** Retrieve the properties from the type in the WSDL

LOCAL ARRAY laTypeProperties[1]

lnCount = this.aGetTypeProperties(@laTypeProperties,lcTypeName)





lcXML = REPLICATE(CHR(9),lnIndent) + "<" + lcPropertyName + ">" + CRLF

FOR x = 1 TO lnCount

   lcFieldName = laTypeProperties[x,1]  


   lcFoxType = TYPE("loObject." + lcFieldName)

   IF lcFoxType = "U"






   *** Array Checking first


   CASE  TYPE([ALEN(loObject.] + lcFieldName + [)]) = "N"


   CASE TYPE("loObject." + lcFieldName,1) = "A"


      LOCAL y, lnSize, lvValue, lcFieldType, lcChildType


      *** Must copy the array

      DIMENSION la_array[1]



      lcChildType = THIS.RetrieveChildClassFromWSDLObject(laTypeProperties[x,2])


      IF EMPTY(lcChildType)

         lcXML = lcXML + REPLICATE(CHR(9) ,lnIndent+1) + "<" + lcFieldName + "/>" + CRLF


         lnSize = ALEN(la_array,1)

         lcXML = lcXML + REPLICATE(CHR(9) ,lnIndent+1) + "<" + lcFieldName + ">" + CRLF

         FOR y = 1 TO lnSize

            lcXML = lcXML +  this.CreateObjectXmlFromSchema( ;

                                la_Array[y],lcChildType,lcChildType,lnIndent+3 )        


         lcXML = lcXML +  REPLICATE(CHR(9) ,lnIndent+1) + "<" + lcFieldName + ">" + CRLF



   *** Check for Collections

   CASE TYPE("loObject." + lcFieldName + ".KeySort") = "N"     

         lcChildType = THIS.RetrieveChildClassFromWSDLObject(laTypeProperties[x,2])


         loCol = EVALUATE("loObject." + lcFieldName)

         lnSize = loCol.Count


         lcXML = lcXML + REPLICATE(CHR(9) ,lnIndent+1) + "<" + lcFieldName + ">" + CRLF

         FOR y = 1 TO lnSize

            lcXML = lcXML +  this.CreateObjectXmlFromSchema( ;

                                 loCol.Item[y],lcChildType,lcChildType,lnIndent+3 )        


         lcXML = lcXML +  REPLICATE(CHR(9) ,lnIndent+1) + "<" + lcFieldName + ">" + CRLF


   *** Objects: Recurse into the object

   CASE lcFoxType = "O"

      lcXML = lcXML + this.CreateObjectXmlFromSchema( EVALUATE("loObject."+lcFieldName),;

                       this.StripNameSpace(laTypeProperties[x,2]),lcFieldName,lnIndent+1 )


      lcXML = lcXML + loXML.Addelement(lcFieldName,EVALUATE("loObject." + lcFieldName),lnIndent+1,"",lcFoxType)



lcXML = lcXML + REPLICATE(CHR(9),lnIndent) + "</" + lcPropertyName + ">"  + CRLF




*  wwSOAP :: CreateObjectXmlFromSchema


This code uses wwXML and AddElement quite extensively but it turned out to be a relatively short piece of code. If it wasn’t for the handling of arrays this code would have been pretty clean and short. The difficult part here is that we have to make sure that we can figure out the name of the child elements in arrays.


This code assumes that collections or arrays contain objects, not individual values. Further this code doesn’t deal well with recursive references. If the object in the tree has child objects that point back at another object higher up it can easily get hung up.


However, you can now create objects on the fly that match fairly complex objects. For example, here the sample I was doing at DevCon (minus the automatic XML creation):


loAuthor = GetAuthorProxyObject()

loAuthor.au_lname = "Strahl"

loAuthor.au_fname = "Rick"

loAuthor.Phone = "808 123-1211"

loAuthor.Address = "32 Kaiea Place"

loAuthor.City = "Paia"

loAuthor.State = "HI"

loAUthor.Zip = "96779"

loAuthor.Contract = 0


loAuthor.Invoices[1] = loInv

loAuthor.Invoices[2] = loInv2



FUNCTION GetAuthorProxyObject()


*** Manunally parse the result back into an object

loAuthor = CREATEOBJECT("Relation")


















*** Code to add an array for invoices


*DIMENSION loAuthor.Invoices[2]



*LOCAL loInvoices as Collection

loAuthor.Invoices = CREATEOBJECT("Collection")


loInv = CREATEOBJECT("Empty")


ADDPROPERTY(loInv,"InvoiceDate",{05/10/2004 :})




*loAuthor.Invoices[1] = loInv



loInv = CREATEOBJECT("Empty")


ADDPROPERTY(loInv,"InvoiceDate",{01/10/2004 :})



*loAuthor.Invoices[2] = loInv



RETURN loAuthor


The whole thing then yields:




      <HomePhone />

      <WorkPhone />

      <Fax />



   <Au_id />



   <Phone>80_ 123-1211</Phone>

   <Address>32 Kaiea Place</Address>




















You can then take this entire XML string and turn it into a NodeList and send it back up to the server using the SOAP toolkit or wwSOAP:


loDom = CREATEOBJECT("MSXML2.DomDocument")



*** Using MSSOAP Toolkit



llResult = loSOAP.UpdateAuthorEntity(loDom.DocumentElement.ChildNodes)

? llResult

? loSOAP.FaultString



So now, wwSOAP and wwXML provide the ability to both generate objects to XML and parse them back into objects (although this requires a ‘blue print object’).


On the inbound side I’ve also enhanced ParseObject which uses the WSDL file to dynamically construct an object on the fly and assign the properties into it. This routine goes the other way by looking at the WSDL file and adding properties to an empty object, then reading the value from the XML document into it. This has worked for the DevCon sessions and samples, but I’ve also added the ability to parse arrays (and by that route – collections).


In fact the code with wwSOAP to parse Web Service response from above looks like this:



oSOAP.nHttpConnectTimeout = 5

oSOAP.lParseReturnedObjects = .T.


*** Retrieve authors - returns DataSet returned as NodeList



loAuthor = oSOAP.CallWSDLMethod("GetAuthorEntity",lcWSDL)



? loauthor.au_lname

? loAuthor.PhoneNumbers.HomePhone

? loAuthor.PhoneNUmbers.ServiceStarted


*** Array properties
? loAuthor.Invoices[1].InvoiceNumber

? loAuthor.Invoices[2].InvoiceTotal


This also works with MSSOAP. You can call the ParseObject method directly, but it requires picking up the WSDL file again so there’s some extra overhead there:




loNL = o.GetAuthorEntity("486-29-1786")


*** Retrieve the Root Node

loRoot = loNL.Item(0).ParentNode





loAuthor = loSOAP.ParseObject(loRoot,"AuthorEntity")



This is pretty cool, even though this code is probably not 100% foolproof. For one thing you can run into problems if you end up with property names that VFP will not allow or get confused by. This code could be made more reliable by making it VFP 8 and later using the EMPTY object and the ADDPROPERTY() function instead of the AddProperty() method of the RELATION object.


I haven’t posted the update to wwSOAP yet, but look for it over the next few days. I’ll also update the Web Service .NET Interop article with some of these new functions which will reduce the code a bit and provide a solution to one of the missing pieces namely passing object up to the server.

I'll be at DevIntersection in Vegas this fall giving sessions on ASP.NET Core with Angular and Localization. Thinking of coming? Use discount code STRAHL and save a few bucks. If you do be sure to stop by and say hello!

ASP.NET DevIntersection 2017. Rick Strahl Coupon Code

The Voices of Reason


Rick Strahl
October 07, 2004

# re: Object parsing for Web Services called by Visual FoxPro

BTW, the code for the latter demo is part of the article at: http://www.west-wind.com/presentations/foxpronetwebservices/foxpronetwebservices.asp which includes the latest update of wwSOAP and wwXML.

Paul Mrozowski
October 08, 2004

# re: Object parsing for Web Services called by Visual FoxPro

Cool. I'll have to look this up again when I get back to another project. I hated having to change my variable names in .NET to all lowercase just because VFP couldn't handle/pass up the mixed-case version.

Rick Strahl
October 09, 2004

# re: Object parsing for Web Services called by Visual FoxPro

Yeah, no kidding. unfortunately there are still a few open issues, like how to deal with embedded entity objects such as datasets or XML nodes. The WSDL parser will choke on those. There's no standard way that these things are in the WSDL, so it's really hard to tell what they are at parse time.

Jeroen van Kalken
October 20, 2004

# re: Object parsing for Web Services called by Visual FoxPro

When are you releasing the updated wwSOAP?

I'm trying to get my feet wet in webservices.
It seems I need this update to correctly interpret the XML result containing an objectarray (and complex types)

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017