[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]

Figure 5 - The offline Invoice Manager uses the same business objects used on the server, and receives invoices via invoice objects sent over the wire as XML. Fields in this form are bound to the business object, such as THISFORM.oInv.oData.InvDate and THISFORM.oInv.oCustomer.oData.Company.

The offline manager has a download link, which runs in a loop to download any unprocessed invoices from the server. The relevant import code for an individual invoice looks like this:

    loXML = CREATE("wwXML")
    lcXML = loXML.LoadUrl("https://www.west-
    loInv = CREATE("cInvoice")
    loInv.Load(0)  && Load empty invoice & subitems
    loXML.lRecurseObjects = .T.
    ? loInv.oCustomer.oData.LastName
    ? loInv.oCustomer.oData.Company
    ? loInv.oData.InvDate
    ? loInv.oData.InvTotal
    ? loInv.oLineItems.ARows[1].Sku
    ? loInv.oLineItems.ARows[1].Descript

Again, notice how little code is involved here - 4 lines of code to download and import the object. However, in order for the import of this hierarchical object to work, a couple of pre-requisites are required. The objects must exist on the client side in order for wwXML to be able to figure out the structure of the object to import to - hierarchical objects cannot be dynamically created from an export DTD. Furthermore, the object structure must be prefilled with empty values of the proper types in order for the import to work. 

Member objects imported to cannot be NULL or otherwise undefined, as that would prevent them from being parsed and filled. wwXML requires an object structure, as it works by walking through the object and looking for matching elements in the XML document. In the case of the LineItem array, at least the first item of the array must be prefilled with the proper type - in this case, an item object.

Notice the special call to loInv.Load(0). This method call actually handles creating the properly formatted 'empty' objects. If we look at the business object code's Load method we'll see the following:

    IF lnPK = 0
      THIS.oCustomer = CREATE("cCustomer")
      *** Create an empty item list
      THIS.oLineItems = CREATE("cItemList")
      loLItem = CREATE("cLineItems")
      THIS.oLineItems.aRows[1] = loLItem.oData
      RETURN .F.


Each object is pre-initialized as an empty object. This type of behavior is not only useful for wwXML's import parsing, but also for displaying the business objects in forms. For example, in the invoice manager form the actual field ControlSource values are bound to the object's properties. NULL values would cause all sorts of problems for the databinding.


The EPS folks in Houston use the Invoice Manager to enter orders at their offices and post those invoices to the West Wind Web Server located in Oregon. West Wind can then download these orders in Hawaii like any other order placed online. This is a good example of the benefit of a distributed application in a small business environment! 

POSTing data to the server

So far, I've shown how you can get data downloaded from the server and use it on the client side. You can also go the other way! Let's look at the offline manager application, which is actually in use by several people in our organization. West Wind Technologies is associated with EPS Software, and EPS Software may take orders at their offices in Houston. However, West Wind manages all product sales, so all credit card transactions are processed by West Wind in Hawaii. The idea is that the folks in Houston can use the Invoice Manager to enter orders at their offices, and post those invoices to the West Wind Web Server located in Oregon. West Wind can then download the orders posted to the Web server, in the same way as online orders placed directly on the Web site. The concept could be extended to allow third party vendors to also post orders in XML format directly to the West Wind Web site. This is a good example of the benefit of a distributed application in a small business environment!



We already know how to create the invoice XML easily - the process in the Invoice Manager client application is identical to what's happening on the server. What's different now is that we're sending data to the server:

    * frmInvoice :: UploadInvoice
    LOCAL loXML, loIP, lcResult, loInv
    IF !EMPTY(lnPk)
       loInv = CREATE("cInvoice")
       IF !loInv.Load(lnPK)
          RETURN .F.
       loInv = THISFORM.oInv
    *** Export invoice to XML hierarchically
    loXMl = CREATE("wwXML")
    loXMl.lRecurseObjects = .T.
    lcXML = loXMl.ObjectToXML(loInv)
    lcResult = loXML.LoadUrl(THIS.oConfig.cUploadINvoiceWebLink,;
                            THIS.oConfig.cAdminUsername, ;
    *** check for errors
    IF lcResult # "OK"
       IF loXML.lError
       MESSAGEBOX("Invoice uploaded.",64,WWSTORE_APPNAME)


The only new thing that happens here is that when we call wwXML::LoadUrl(), we're passing the XML generated from the invoice export as the second lcXML parameter, which is POSTed to the Web server. LoadURL() posts data in raw format and doesn't URLEncode the string. The server knows how to pick up this raw XML buffer. This simple bit of code handles posting an invoice from the client to the server. On the server, the code is straightforward:

      FUNCTION SubmitXMLOrder
      *** Security Check
      IF !THIS.Login(Config.cAdminUser)
      *** Retrieve XML input if passed
      lcXML = Request.FormXML()
      *** Create an Invoice object and initialize
      loInv = CREATE([cInvoice])
      *** Import the object
      loXML = CREATE("wwXML")
      loXML.lRecurseObjects = .T.
      *** Check if data is Ok
      IF !(loInv.oData.Pk # 0 AND loInv.oCustomer.oData.pk # 0)
         Response.Write( "Error: Invalid Order Info")
      *** Must fix up invoice for PK conflicts

Keep in mind that these types of replication scenarios require thought about synchronization: You may run into duplication of primary keys. This invoice object includes a method that checks to see whether an invoice PK exists already, and if necessary, reassigns the PK for the invoice and updates the lineitems accordingly. For the customer, if a PK exists already, it double checks to see if the name and address match the original record. If it doesn't, a new customer record is created instead. Conflict resolution is not always easy, as this type of online/offline application becomes an exercise in replication, along with all the issues related to this complex topic of synchronization of data.



You can download wwXML for free from the West Wind Web site:

For additional HTTP features in your Visual FoxPro application, you can also check out the West Wind Internet Protocols, which provide full control over HTTP as well as high level XML services for remote SQL access and remote COM method calls.

Finally, Microsoft's XML Web site provides a number of good articles on building distributed XML applications. Microsoft also recently posted the Simple Object Access Protocol (SOAP) toolkit for making remote method calls on servers. This is another good avenue for taking advantage of the concepts I've shown in this article. http://msdn.microsoft.com/xml

More power to you!

I hope this article has given you some ideas on how you can utilize XML from Visual FoxPro client applications. Even if you're not using Visual FoxPro, you can probably see that with the right tools for your development language of choice, you can perform powerful transfers of data with minimal coding effort. As the examples above show, you need only a few lines of code to handle convert tables and objects into XML, transfer that data over the wire, and reassemble the XML back into the appropriate data or object structure.

I'm sure that you can think of some useful applications for this stuff. Using these strategies, you can very easily build powerful applications with existing business objects, which can be transferred painlessly over the wire. The possibilities are endless.

And remember, XML can be useful for lots of things, even in non-distributed applications. By persisting objects and tables as XML, you can pass data around in applications, and even save object state into a table's memo field. 

In the next issue, we'll look at how to build XML-based services that provide data access and remote code activation to a variety of clients generically. I'll talk more on how to access and consume XML in browser applications, as well as fat client applications, using the same application logic. We'll also look at SOAP and how it figures in that space. Until then, give it a shot and put what you've learned to good use.

Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Internet application development and tools focused on Internet Information Server, ISAPI, C++ and Visual FoxPro. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for Visual FoxPro, West Wind HTML Help Builder, co-author of Visual WebBuilder, a Microsoft Most Valuable Professional, and a frequent contributor to FoxPro magazines and books. He is co-publisher and co-editor of CoDe magazine, and his book, "Internet Applications with Visual FoxPro 6.0", has been recently released by Hentzenwerke Publishing. For more information please visit: http://www.west-wind.com/.

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]