Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
with
Visual FoxPro
6.0
Rick Strahl
Hentzenwerke Publishing
Published by:
Hentzenwerke Publishing
980 East Circle Drive
Whitefish Bay WI 53217 USA
Hentzenwerke Publishing books are available through booksellers and directly from the
publisher. Contact Hentzenwerke Publishing at:
414.332.9876
414.332.9463 (fax)
www.hentzenwerke.com
books@hentzenwerke.com
Internet Applications with Visual FoxPro 6.0
By Rick Strahl
Technical Editor: Gary DeWitt
Copy Editor: Jeanna Randell
Copyright 1999 by Rick Strahl
All other products and services identified throughout this book are trademarks or registered
trademarks of their respective companies. They are used throughout this book in editorial
fashion only and for the benefit of such companies. No such uses, or the use of any trade
name, is intended to convey endorsement or other affiliation with this book.
All rights reserved. No part of this book, or the ebook files available by download from
Hentzenwerke Publishing, may be reproduced or transmitted in any form or by any means,
electronic, mechanical photocopying, recording, or otherwise, without the prior written
permission of the publisher, except that program listings and sample code files may be
entered, stored and executed in a computer system.
The information and material contained in this book are provided as is, without warranty of
any kind, express or implied, including without limitation any warranty concerning the
accuracy, adequacy, or completeness of such information or material or the results to be
obtained from using such information or material. Neither Hentzenwerke Publishing nor the
authors or editors shall be responsible for any claims attributable to errors, omissions, or other
inaccuracies in the information or material contained in this book. In no event shall
Hentzenwerke Publishing or the authors or editors be liable for direct, indirect, special,
incidental, or consequential damages arising out of the use of such information or material.
ISBN: 0-96550-939-7
Manufactured in the United States of America.
I want to dedicate this book to the Microsoft Visual FoxPro team for all their efforts in
building and maintaining such a powerful development tool. Even in the face of adversity, this
product continues to kick butt, allowing developers like myself to build amazing applications
with ease without sacrificing power. I, like many of you, I suspect, have benefited tremendously
from the power of this product, and I want to thank the team for continuing to pour their
creative resources into this tool. Thanks, guys (and gals)!
And don't ever let a Dilbert-head tell you that it can't be done with the Fox!!!
v
Table of Contents
Acknowledgements xiii
Foreword xv
SECTION 1Internet Technologies 1
Chapter 1: Introduction 3
Who should read this book 3
How this book works 3
What we'll cover 4
What you need to work through the book 5
What you should know about me 6
Is the Web in your future? 6
Commercial Internet development is exploding 6
The move to develop the active Web 7
Why build a Web application? 8
Advantages of building Web applications 8
Distribute widely, administer centrally 8
Universal client interface 9
The application development platform of the future 10
Limitations to building Web applications 11
Configuration issues 11
Interface limitations of HTML 12
Server-based programming model 14
Another state of mind 14
Take a load off 14
Security is not optional 14
Chapter 2: Internet Application Technologies 15
Open standards open doors to the world 15
Of clients and servers 16
Servers 16
Clients 20
Clients on a diet 21
HTTP makes clients and servers go 'round 23
From Web browser to Web server 24
A closer look at the server side 27
ISAPI, the high-performance extension interface 28
Calling all Visual FoxPro servers 28
vi
Microsoft's Component Object Model
The glue tying Windows to the Web 29
COM and Visual FoxPro 30
What else do you need to know? 31
Chapter 3: Setting Up 33
Windows NTPlatform of choice for Web applications 34
Internet Information Server 34
Configuration of the site 37
SSL and secure certificates 39
Active Server application options 40
Explore the dialogs 41
IIS admin objectsdo it programmatically 41
Starting out 42
SECTION 2Server-Side Development 43
CHAPTER 4: Active Server Pages 45
Scripting people in a component world 45
How it works 46
Scripting for the masses 48
Objects galore 50
All input comes from the Request object 51
All coded output goes through the Response object 53
System services through the Server object 54
Keeping state with Session and Application objects 55
Getting down to businessSimple data access with ASP and ADO 58
Creating an ODBC data source 59
Working with the demo code 59
Creating the ASP document 60
Error handling with ASP and ADO 62
Displaying the logging results 62
Looping through the recordset 65
Other ways to handle ADO 66
A TasTrade invoice viewer application 66
Watching out for unnecessary data display 67
Complex forms and data access 69
How the form works 69
A few reservations about VFP and ADO 74
Scripting as an application environment? 75
Taking advantage of COM with Active Server Pages 75
Calling all VFP servers 76
vii
Beware of errant paths! 82
Understanding server security 83
Using VFP to generate HTML 86
Getting around slow HTML tables 89
Using ASP objects with your VFP COM server 89
The IScriptingContext interface 91
Objects from Visual FoxPro 92
Examplean object-based customer browser 93
Sharing data with ASPpassing ADO objects 96
An objective warning 99
Understanding ASP/COM scalability 100
What is Apartment Model Threading and why should you care? 100
Summing up 104
ASP pros 104
ASP cons 105
ASP resources 105
CHAPTER 5: FoxISAPI 107
What is FoxISAPI? 107
How it works 108
Build your application code with Visual FoxPro 110
Building the COM server 111
Server configuration 113
Copy Foxisapi.dll into a Web script directory 114
Run DCOMCNFG to configure your server 115
Unloading and managing servers 118
COM server instancing 119
In-process COM objects 119
Out-of-process COM objects 120
Building FoxISAPI requests 121
Returning HTTP output 124
Content types 125
HTTP directives 125
Show me the data! 128
Decoding form variables 130
Retrieving ServerVariables from the INI file 134
The next stepa basic framework 137
Why do you need a framework? 138
The wwFoxISAPI framework 138
wwFoxISAPI 139
wwRequest 139
wwResponse 140
Centralizing the FoxISAPI entry point 144
Error handling 148
viii
Eliminating those long URLs 150
Scripting and Templates 151
Implementing a scripting engine with Active Server Syntax 151
Templates with MergeText 151
Scriptingmore power and more work 154
Hooking it in... 156
An example application 160
Other cool things you can do 170
Queries against SQL Server 170
Creating Adobe Acrobat documents from VFP reports 174
Sending data over HTTP 178
Rendering Visual FoxPro forms 180
Multiple instances via the pool manager 185
FoxISAPI debug mode 187
Summary 189
FoxISAPI pros 189
FoxISAPI cons 190
SECTION 3Client-Side Development 191
Chapter 6: Internet Enabling Your VFP Applications 193
The easy way to the Internet 193
The Shell API 193
What about Visual FoxPro's Hyperlink object? 196
The Internet protocols of wwIPStuff 197
Sending SMTP Internet e-mail 197
FTP transfers 198
Using the Internet Explorer Shell COM interface 200
Capturing IE COM events 202
The WebBrowser ActiveX control 203
It's not just for Web content 205
The Internet Explorer document model 207
A Web Browser example 215
Editing HTML 219
HTML markup formatting problems 221
Using the DHTML Edit Control 222
Summary 224
Chapter 7: Building Distributed Applications over HTTP 225
Hyper thinking 225
How can you use HTTP in your applications? 226
WinInet with Visual FoxPro 227
Doing data over HTTP 231
ix
Real data over HTTP 234
Sending data with HTTP POST 238
Posting application data 246
Hey, Mr. Postman, bring me some data 250
Building in even more functionality! 253
Putting it all together 260
Don't forget about security! 266
What about other server tools? 267
WinInet issues 268
Summary 270
Pros 270
Cons 270
Chapter 8: Remote Data Service 271
How RDS works 272
An example using Internet Explorer 273
Table-based data binding with RDS 278
Using RDS inside VFP 280
Error handling 281
Using RDS results in your code 283
Problems, problems, problems with RDS data access 287
ODBC problems with Visual FoxPro 287
RDS problems 287
Internet Explorer data binding problems 287
Summary 288
Accessing objects over HTTP with the RDS.DataSpace control 288
How it works 288
An examplea generic server object 289
Summary 293
Beware of security issues! 294
Data security 294
Object security 295
Some workarounds 295
DCOM over HTTP 297
Summary 297
Pros 297
Cons 297
SECTION 4Enterprise Development 299
Chapter 9: Visual FoxPro and COM 301
COM scalability and Visual FoxPro 6.0 301
What is Apartment Model Threading and why should you care? 302
x
Problems with the initial release of VFP 6.0 304
An interim release to the rescue 304
Using the new multi-threaded runtime 305
Multi-threading is not a magic bullet 306
Microsoft Transaction Server 307
Do you need Microsoft Transaction Server? 308
The state of the stateless 309
Understanding JITA 314
MTS security 315
MTS summary 316
Pool managers in FoxISAPI/Web Connection 317
Pool of single-use EXE servers or in-process DLL servers 317
Full administrative control over servers 318
Not generic 319
Reaching out over the network with DCOM 319
Direct instantiation via CREATEOBJECTEX() 320
Remote objects without CREATEOBJECTEX() 320
Beating the beast: DCOM security 321
Security and the IIS client 323
Performance and network issues 324
DCOM conclusion 324
COM summary 325
Chapter 10: Building Large-Scale Web Applications 327
Visual FoxPro is ready for the server! 327
What is a large-scale application? 328
Web site operation 329
Performance 330
Data optimization 330
To SQL or not to SQL 332
Code optimization 334
Web site optimization 336
Web server optimization 337
Visual FoxPro and multithreading 339
IIS and ISAPI are multithreaded, but VFP is not! 339
Understanding CPU load and speed 340
Scalability 342
Multiple instances of VFP required 342
Best scalability achieved with multiple processors on a single box 342
Use network machines to spread load 343
IP Routing/Dispatch Manager 346
Data becomes the bottleneck 348
Scalability summary 349
Site application administration 349
xi
Decisions, decisionsonline or offline data 350
Remote application administration 351
Detailed site and user statistics 351
Should you use cookies? 352
COM server management 353
Security on the Web 354
Keep data in an unmapped path 354
NT Challenge Responsedirectory and file security 355
Basic Authentication for your applications 355
Non-NT custom security 357
Do you need secure transactions via SSL? 357
Summary 358
Chapter 11: The Development Process 359
Source code integration 360
Visual FoxPro developers 361
HTML designers 362
Consultants and staff 362
Separate staging test server 363
Integrating HTML and code 364
HTML is the front-end interface 365
Understand the limitations of HTML 365
Data connectivity 366
Keep HTML and code separate 366
Scripting and templates for data and display logic 367
Graphics and site design 367
Network administrators 369
Site management 371
Summary 373
Chapter 12: Loose Ends 375
Data access over TCP/IP 375
Accessing VFP data directly over TCP/IP 375
Using a SQL server over TCP/IP 376
The Great Active Document Swindle 377
Treat Active Docs for what they are:
browser-hosted standalone applications 378
Scenarios where Active Documents make sense 378
A mini-tour of how Active Docs work 379
Getting your application to run as an Active Doc 380
Limited menu options 383
Active Document Summary 383
XMLThe new data messaging standard 383
What is XML? 384
xii
XML and the Web 384
XML and data 385
Generating XML 387
Nested XML 389
XML parsing 390
The MSXML parserready now, but it's not compliant 390
The IE 5 DOM parser 391
XML summary 394
Other Web Application Tools 395
Cold Fusion 395
West Wind Web Connection 398
Other FoxPro tools 399
FoxWeb 399
X-Works 399
xiii
Acknowledgements
Writing a book always seems to be more work when you're actually doing it, than before or
after the job's in process. I'd like to thank the following people who have helped make the
work easier and worthwhile:
Most importantly I want to thank Whil Hentzen and Hentzenwerke for putting out the
book and the entire Visual FoxPro series. Good books that deal with specific topics for a
particular tool are rare, and this series captures the essence of very specific technical books
well. Gotta thank Whil for seeing the wisdom in that. I also want to thank Jeana Randell and
Gary DeWitt for an excellent job editing the book.
Steven Black also made an 'unofficial' editing appearance, and I thank him for his
thoughtful comments on the scalability chapters. Ken Levy got me out of a bind on a few
topics and bugs with Internet Explorer. He also helped with the short XML section of the book
and provided valuable feedback on various ideas I had during the course of writing. Randy
Brown I have to thank for frequent heads-up on what's supposed to work and what doesn't.
And of course, Calvin Hsia for making me figure out my own COM coding issues (just
kidding). Thanks to Markus Egger for, well, being Markushis feedback has been very
valuable on a variety of coding issues. Rod Paddock also was kind enough to provide a short
review of Cold Fusion for this book. I also want to thank Randy Pearson for his support over
the years and of course for his CodeBlock tool, without which a number of things I've been
doing would not have been possible.
Finally, I want to thank some of my friends who've been wondering what the heck I've
been doing 'inside' the house so much: Morgan V.I.G.G.E.R.S. who has fallen and can't reach
his adrenaline drugglad you caught it good this year on your weekend schedule. Tim and
Layne, who probably can't wait to come back to Mauithe sharks don't bite, Layne! Mike
White, who'll pace a hole in the ground waiting for the swell to come up at Davenportstart
investing in the Ho'okipa fund for next year! C.K. from the Warehouse (when are you getting
your butt over here?) for providing the gear when it breaks. It may be a good thing when it
doessome work actually gets done while bones and epoxy heal. Glenn Phillips and Dan
Bubb at Gorge Net and Solution Engineering in Hood River for putting up with my server.
Andy 'Andyboy' Anderson for the celebrity impersonation phone calls at 2 a.m. Mom and even
Dad, because I have to. And last but not least, the 10:30 p.m. reruns of Seinfeld for keeping me
straight on what's really important.
Mahalo to all of you!
xv
Foreword
In the last couple of years, there has been a tremendous increase in the number of Visual
FoxPro developers building Web applications. One of the people we have to thank for this is
Rick Strahl. Rick has been at the forefront of marrying Visual FoxPro to the Web. His articles
and conference talks, as well as his West-Wind Web Connection, have been invaluable in
making Web development easier for Visual FoxPro developers.
West-Wind Web Connection has proven to be a powerful and easy-to-use tool for building
Web applications with Visual FoxPro. It has been used by several of the finalists in the Best
Web Site category of the Visual FoxPro Excellence Awards (both this year and last year), as
well as the Surplus Direct site, our extremely popular Visual FoxPro Web site case study,
which was developed and documented by Rick.
I can think of no one more qualified to write this book than Rick. He knows Web
technologies inside and out and knows how to apply them to Visual FoxPro (and vice versa).
He has proven time and again that he not only knows how to do this stuff for real, but he also
knows how to explain to others how to do it. That is a very valuable combination.
I hope you enjoy this book and that it helps you build better and more powerful Web
applications with Visual FoxPro.
Robert Green
Visual FoxPro Product Manager
Microsoft Corp.
Section 1 1
Section 1
Internet Technologies
The Internet has tremendously affected the way we build applications and how we think about
information in general. We've seen an incredible boom in information being published on the
Web, and database application development is a key feature in this connected world.
The first section of this book introduces Internet technologies and terminology, and tells
you what you need to get started. The technology that drives the Internet is very exciting but
also somewhat overwhelming at first, because of the many related and distributed pieces that
make it work. These first chapters are meant to ease you into the distributed mindset if you are
new to Internet development. These chapters review the basic concepts of why you might write
an Internet-enabled application as opposed to a standalone application.
Chapter 1 introduces the author and the layout of the book, followed by an overview of
why Internet technologies are important for developers as well as for application functionality.
Chapter 2 dives straight into an overview of how the Internet works by providing information
flow and a few examples that describe how data moves from the client to the server and back.
The discussion starts with a basic overview and then drills into the details, so even if you have
done previous Internet development you might find some useful ideas here. This chapter also
briefly discusses issues such as scalability and server-based environments to get you thinking
along these lines right from the start of your development efforts. (These issues are followed
up in Section 3 of the book, which includes an in-depth discussion of scalability.) Chapter 3
takes you through the software requirements and configuration of Internet Information Server.
Once you've worked through Section 1, you'll have the background to tackle the actual
development tools to start building Internet applications.
Section 1 Chapter 1: Introduction 3
Chapter 1
Introduction
Welcome to Internet Applications with Visual FoxPro. If this is your first experience with
Web development, read on to find out what you'll learn. If you're a seasoned Web
developer, be ready to find out a few new tricks and tips, and explore some advanced
issues of Web development with Visual FoxPro that you might not have thought about.
This book follows a logical format for making it easy to learn with, as well as providing a
reference that you can use to look up specific issues by topic after you've read through
individual chapters. This introduction discusses how the book is laid out and identifies
the high-level topics. It also lets you know what you should be familiar with before diving
in. Finally, this chapter introduces some of the motivations behind Web development,
and some thoughts on the advantages and disadvantages that come with this new
development platform.
Who should read this book
This book is geared toward the very specific topic of building Web applications with Visual
FoxPro. As such, it makes a few assumptions about the reader's understanding of Visual
FoxPro. This book is an intermediate text in terms of knowledge expected of Visual FoxPro. I
won't spend a lot of time discussing FoxPro commands or structures unless there's some
special or unconventional use for them in the context of the discussion. I will discuss advanced
topics, especially in Section 4, Enterprise Development, but these topics are mostly isolated or
are introduced when the prerequisite topics have been discussed in the prior text.
This book begins with the assumption that the reader has never built an Internet
application, so you will find detailed discussions of the technologies and how they tie together
the client and server sides of the Internet platform. We'll start with the very basics and work up
into the more advanced topics. You'll see basic examples that demonstrate how each tool
applies to Visual FoxPro and more complex scenarios that show how this stuff applies to real-
world application development.
And yes, there will be lots of code shown in short snippets. Extensive examples or tool
code might be referenced to code available from the Developer's Download Files at
www.hentzenwerke.com, but I've tried to keep most of the relevant code inside the text
to keep you in the context of the discussion. Paragraphs marked with this "web" icon indicate
that you can download the referenced application or tool from www.hentzenwerke.com. Future
updates of these tools will be available from my Web site, www.west-wind.com.
How this book works
This book is laid out in sections that group together various related topics. Each section
includes a short introduction and general terminology that you should understand before
looking at the specific technologies covered in the individual chapters.
</script>
The key code elements are the Connect and SQL properties and the Refresh() method, which
cause the ActiveX control to download the data from the server. The Connect property can
point to any valid connection string on the server. Keep in mind this will be a DSN or plain
connection string on the server, not the client. In this case I used a DSN on the server, but you
can also access a file directly on the server using the following syntax (and this is very, very
scary security wise! More on this later):
oRDS.Connect = "driver=Microsoft Visual FoxPro Driver;" & _
"Exclusive=No;SourceType=DBF;" & _
"SourceDB=d:\wwapps\wc2\wwdemo;uid=;pwd="
The SQL property allows you to execute SQL commands on the server. Make sure you
minimize the data that comes down by specifying only the fields you actually need. The call to
Refresh() actually causes RDS to retrieve data. Now, depending on how much data you're
downloading, the rest of the page waits for the data download to complete. Once the data has
come down, I use the FillListBox() method to get data into the listbox. This requires some
script code because listboxes can't be data-bound in Internet Explorer:
276 Internet Applications with Visual FoxPro 6.0
Sub FillListBox()
Set rs = oRDS.RecordSet
lnCount = rs.RecordCount
rs.MoveFirst
' Clear the list first manual again
For x = 0 to document.all.lstGuests.length -1
'msgbox document.all.lstGuests.Options(0).text
document.all.lstGuests.Options.remove(0)
Next
' Fill the actual data into the list set display and value
For x = 1 to lnCount
Set loOption = document.createElement("OPTION")
loOption.Text = rs("Name")
loOption.Value = rs("CustId")
document.all.lstGuests.add(loOption)
rs.MoveNext
Next
End Sub
I use the recordset object to loop through the records and populate the listbox using the Add()
method of the list box (lstGuests). First the list must be cleared but there's no built-in method,
so it has to be done manually with a few lines of code. If you remember the ASP chapter you'll
remember that the syntax for accessing the individual fields of the recordset is
rs("FieldName"). In the code, note also that both a value and display text are assigned with
the listbox's Value and Text properties, respectively. The former makes lookups easier in order
to refresh the formbecause the listbox isn't data-bound, the translation between a listbox item
and the data display must be done manually using the ShowRecord() function shown below.
After loading the listbox, the record pointer is reset to the first record in the set with
oRDS.Recordset.MoveFirst(). The actual display of all fields in the HTML form is handled
via IE data binding. Fields are bound using the Dynamic HTML DataSrc and DataFld tags:
<INPUT NAME="txtname" datasrc="#oRDS" DataFld="Name" ID="txtname"
STYLE="POSITION:ABSOLUTE;LEFT:293;TOP:20;WIDTH:288;HEIGHT:23;"
"Font:normal normal 9pt 'Arial';Color:#000000;BACKGROUND:#FFFFFF" >
Each field on the form has the datasrc set to #oRDS, which is the object name of the ActiveX
control in the document preceded by a pound sign (#). DataFld describes the RDS field name
in the recordset that should be displayed.
The bound controls refresh automatically when the record pointer is moved, so it's easy to
implement the navigation buttons of the guest form. For example, the Move Next button
simply embeds the MoveNext() call to the recordset object directly into the Input tag:
<INPUT NAME="btnSubmit" ID="btnSubmit" TYPE="SUBMIT" VALUE=">>"
onclick="oRDS.RecordSet.MoveNext">
Section 3 Chapter 8: Remote Data Service 277
While I took the simple route here, it's usually a better idea to use a separate function in order
to perform extra processing such as checking for beginning or end of file.
When navigating through the listbox, navigation is based on a guest ID, which requires
two methods:
Sub lstGuests_OnChange
ShowRecord(lstGuests.Value)
End Sub
Sub ShowRecord(lcCustId)
Set rs = oRDS.RecordSet
lnCount = rs.RecordCount
rs.MoveFirst
For x = 1 to lnCount
If rs("CustId") = lcCustId Then
Exit For
End If
rs.MoveNext
Next
End Sub
The listbox isn't data bound, so when you move the record pointer or click on an entry,
ShowRecord() must be called to move the record pointer. Because ADO doesn't support
searching a result set, you have to do this manually by searching manually through the
recordset. Note that this can be very slow. I ran the guest book with 350 records and the search
on the end of the list took upwards of five seconds to refresh the record. Because ADO is all
COM and every line of code in VBScript is interpreted, this is no speed demon unless you keep
your recordsets small. ADO does support the Filter property, which lets you quickly get to a
specific record:
rs.Filter = "CustId = '" & lcCustId & "'"
But unfortunately that code filters the entire recordset, so once the filter is set you can't use
MoveNext() and MovePrevious() anymore. You can reset the filter with rs.filter = "", but
there's no way to hang on to the current record pointer location when you do. These are just
some of the seemingly simple issues you run into with ADO in general and RDS specifically
that make database development very difficultthe lack of basic features like searching and
requerying a result set is tough to overcome.
You can also update data. Because the input fields are bound to the data, any updates you
make affect the underlying recordset. The changes are made as soon as you type in new data.
Remember that you're working with the data offlinethe data doesn't go to the server until you
call SubmitChanges(). In the sample, you have to click the Save button when saving a new
entry or making changes to an existing record:
Sub btnSave_OnClick()
oRDS.SubmitChanges
FillListBox()
End Sub
278 Internet Applications with Visual FoxPro 6.0
To add new records you can use the RDS AddNew() method:
Sub btnNew_OnClick()
oRDS.Recordset.AddNew()
'*** Make sure all get initialized to non-null values
oRDS.Recordset("CustId") = LEFT(Timer(),8)
oRDS.Recordset("Entered") = now
oRDS.Recordset("Phone") = ""
oRDS.Recordset("HeardFrom")=""
oRDS.Recordset("Location")=""
oRDS.Recordset("Password")=""
End Sub
Note the initialization here. If you don't do this initialization you can run into problems with
NULL values. The VFP ODBC driver has problems with the NULL formatting that ODBC
assigns to all non-provided values in INSERTS, so this is a constant concern. Initializing each
field you're working with beforehand makes sure the fields aren't NULL to start with.
Table-based data binding with RDS
Data binding is a powerful feature that makes it easy to get data to the client, especially if
you're dealing with read-only data or in scenarios where simple data entry or modification
needs to occur. For a little variety I used the SQL Server Pubs database here, but you could use
a Fox table or database in its place. The following example simply pulls all the records from
the SQL Server sample Pubs database and displays them for simple data editing in a table.
Figure 8.3 shows this simple form.
Figure 8.3. This example uses table-based data binding against an RDS recordset
retrieved from SQL Server. This updateable "grid" of fields takes practically no code
the data binding provides all the display and input field handling of the table.
Section 3 Chapter 8: Remote Data Service 279
What's cool about this example is how little code it takes using the IE data binding
features. As in the example above, the Load event handles loading the data:
Sub Load
oRDS.ExecuteOptions = adcExecSync
oRDS.FetchOptions = adcFetchBackground
oRDS.SQL = "Select au_FName, au_LName, Phone, city from authors"
oRDS.Connect = "dsn=pubs;UID=sa;PWD=;" ' System DSN must be installed on this
system
' oRDS.Connect = "DRIVER={SQL
Server};server=(local);uid=sa;pwd=;database=Pubs"
oRDS.Refresh
End Sub
The real time saver is the way that data binding to an HTML table is implemented:
<table border="1" datasrc="#oRDS">
<tdbody>
<tr>
<td width="50"><input datafld="au_fName" size="15" id="first"
name="first"></td>
<td width="50"><input datafld="au_lName" size="15" id="last"
name="last"></td>
<td width="50"><input datafld="Phone" size="15" id="last"
name="Phone"></td>
<td width="50"><input datafld="City" size="15" id="City" name="City"></td>
</tr>
</tdbody>
</table>
A DataSrc tag can be tied directly to an HTML table. Each row of the table causes IE to fetch
additional data, much in the same way a SCAN/ENDSCAN works in FoxPro. Fields for
display or data entry need only add the DataFld tag to specify which field is to be displayed
from the currently active recordset.
Not only does it take only a few lines of code to create this table with remote data, but it's
also quite efficient. You see, data binding to a table allows IE to render the table as the data
comes in, adding rows dynamically to the table as more data becomes available, rather than
waiting for the data up front. This is efficient both from a data perspective as well as from table
rendering. Normally tables cannot display before the data is in place (it's too bad this dynamic
rendering doesn't work with plain non-data tablesyou can look for this feature in Internet
Explorer 5.0).
The bottom of the form also includes an Update button that takes any changes made to the
displayed data and propagates them back up to the server. The code is simple:
Sub btnUpdate_OnClick()
oRDS.SubmitChanges
End Sub
280 Internet Applications with Visual FoxPro 6.0
That's it! What you have here is a form that is bound to data on the server, but running
offline with the ability to go back online at any time using SubmitChanges(). Note that there's
almost no code involved with this form. The only real code deals with setting up the SQL
query and actually pulling the data down.
Using RDS inside VFP
RDS is implemented as an ActiveX control so you can also use it inside VFP. However, the
ActiveX control is a lightweight, IE-style control, which can't be hosted as a control inside a
Visual FoxPro form. The RDS.DataControl can only be instantiated via code. The code to run
a query over the Internet with RDS looks something like this, using the same guest book
example data from before:
lcConnStr = "dsn=VFPGuest"
lcSQL = "select name,company,Message from guest"
oRDS = CREATEOBJECT("RDS.DataControl")
oRDS.SERVER = "http://Localhost/"
oRDS.CONNECT = lcConnStr
oRDS.FetchOptions = 1 && adcFetchUpFront
oRDS.ExecuteOptions = 1 && adcExecSync
oRDS.SQL = lcSQL
*** Wrap Execute command into 'safe exec' object
*** so we can trap any errors
oEval = CREATEOBJECT ("wwEval")
lnResult = oEval.Evaluate( "oRDS.Refresh()")
IF oEval.lError
? oEval.cErrorMessage
RETURN .NULL.
ENDIF
*** Convert the RecordSet into a DBF file
rs2DBF(oRDS.recordset,"TQuery")
BROWSE
RETURN oRDS
A couple of things are done a little differently here than in the Internet Explorer code. Because
we're running inside VFP it makes sense to run our query synchronously by waiting for
completion of the query. To do so, set the following two settings:
oRDS.FetchOptions = 1 && adcFetchUpFront
oRDS.ExecuteOptions = 1 && adcExecSync
The default is 2 and 2, which runs asynchronously and fires events instead. Handling these
events is a bit tricky so you'll usually want to use synchronous operation.
Section 3 Chapter 8: Remote Data Service 281
!
You can use asynchronous operation with the control if you are willing to do a
little more work. You can use the new VFPCOMUtils COM object from the
Microsoft Web site to capture the OnReadyStateChanged and OnError
events. VFPCOMUtils:
*** In order to capture events we need to bind them
oRDS = CREATEOBJECT ("RDS.DataControl")
oVFPCOM = CREATEOBJECT ("vfpcom.comutil")
oRDSEvents = CREATEOBJECT ("RDSEvents")
oVFPCOM.BindEvents(oRDS,oRDSEvents)
remaining RDS code goes here
DEFINE CLASS RDSEvents AS CUSTOM
lError = .F.
nError = 0
cErrorMsg = ""
PROCEDURE onreadystatechange()
* Add user code here
ACTIVATE SCREEN
? "On ReadyStateChange"
ENDPROC
PROCEDURE onerror(SCode,DESCRIPTION,SOURCE,CancelDisplay)
* Add user code here
ACTIVATE SCREEN
? "On Error" + DESCRIPTION
THIS.lError = .T.
THIS.nError = SCode
THIS.cErrorMsg = DESCRIPTION
ENDPROC
ENDDEFINE
Bindevents() allows another Fox object to handle the events for a particular COM
object instance. VFP doesn't support COM events natively, so this COM utility
allows an extension interface to make it possible to respond to the events via this
secondary object instance. The code in the secondary object can implement any
of the object methods that the COM object exposes. To have an object skeleton
created for you, use the following method call:
oRDSEvents.ExportEvents(oRDS,CURDIR()+"RDSEvents.prg")
This creates a PRG file with a class that contains all the event methods of the
object in question.
Error handling
The other issue is error handling. If you're running in synchronous mode, error handling works
like any other COM object in VFPit throws exceptions. The most common problem occurs
when you call the oRDS.Refresh() method, which runs the query. Refresh() sends the query to
the server and lets the server execute it. The server will return error information to the ActiveX
282 Internet Applications with Visual FoxPro 6.0
control, which in turn throws a regular COM exception if an error occurred. The error
information is returned as an ODBC or OLE DB-type error (similar to errors coming from
SQLEXECUTE()). For an invalid field in the SQL query, an error message would look
something like this:
OLE IDispatch exception code 0 from Microsoft OLE DB Provider for ODBC Drivers:
[Microsoft][ODBC Visual FoxPro Driver]SQL: Column 'ZIPCODE' is not found...
You can capture errors like this easily enough with global error handlers, but with online
applications there are a lot of things that aren't easily handled by generic handlers, so I prefer
capturing the error at the source. To do so I use a safe execution class called wwEval (a full
version is included in this chapter's code and discussed in Chapter 5). Its purpose is to wrap an
EVALUATE() or macro command into a class so that the class's error handler can capture any
errors that occur and pass it forward without causing an error in the calling function or method.
When the call returns, the lError property can be checked and the cErrorMsg property can be
retrieved to decide what to do about the error (exit, in this case).
*** Wrap Execute command into 'safe exec' object
*** so we can trap any errors
oEval=create("wwEval")
lnResult = oEval.Evaluate( "oRDS.Refresh()")
IF oEval.lError
? oEval.cErrorMessage
RETURN .NULL.
ENDIF
The core of wwEval looks like this:
DEFINE CLASS wwEval as CUSTOM
lError = .F.
nError = 0
cErrorMessage = ""
vErrorResult = ""
FUNCTION Evaluate
LPARAMETERS lcEvalString
THIS.lError=.F.
THIS.Result = EVALUATE(lcEvalString)
IF THIS.lError
THIS.lError = .T.
THIS.cErrorMessage=Message()+ " - " + Message(1)
RETURN THIS.vErrorResult
ENDIF
RETURN THIS.Result
ENDFUNC
Section 3 Chapter 8: Remote Data Service 283
FUNCTION ExecuteCommand
LPARAMETERS lcEvalString
THIS.lError = .F.
&lcEvalString
IF THIS.lError
THIS.lError = .T.
THIS.cErrorMessage = Message()+ CR + "Code: " + lcEvalString
RETURN THIS.vErrorResult
ENDIF
ENDFUNC
FUNCTION ERROR
LPARAMETER nError, cMethod, nLine
THIS.lError = .T.
THIS.nError = nError
THIS.nErrorLine = nLine
THIS.cErrorMessage = MESSAGE()
ENDFUNC
ENDDEFINE
When using the Evaluate() or ExecuteCommand() methods, any variable references that you
use must be in scope inside the class. In the above example, oRDS must be a PRIVATE
variableif it were LOCAL, oRDS wouldn't be in scope in the Evaluate() or Execute()
methods because LOCAL is in scope only on the current call stack level. If used in classes,
THIS from the calling method won't be in scope so you have to assign it to a PRIVATE
variable and then use it instead of THIS.
This class comes in very handy when dealing with situations where you know errors
might occur frequently and need to be handled immediately. The wwEval class is
included with the Developer's Download Files at www.hentzenwerke.com and provides
a number of other features, including the ability to evaluate Active Server script syntax with
VFP code from strings.
Using RDS results in your code
Once Refresh() has returned successfully, you have a recordset object to work with. You have
a couple of choices about how to access and manipulate the data that came down from the
server. You can use the RDS data directly or you can convert it to DBF format and use VFP's
data engine to work with it. The approach depends on whether you plan to update the data.
The first and most logical approach is to simply use the ADO recordset directly in your
FoxPro application. You can manipulate the recordset as an object and even bind it to data
controls by using the object as the data source. Figure 8.4 shows the guest application running
as a VFP form talking to the Web server using the RDS.DataControl object.
rs = oServer.ExecuteCode(lcCode)
*** Bind the returned recordset to the RDS Datacontrol
oRDS.SourceRecordSet = rs
*** Make a change to the data
rs.Fields("Name").value = "Ricky Strahl"
*** Marshal changes back to server
oRDS.SubmitChanges()
Summary
The RDS DataSpace object is very powerful, as you can see, but it's crucially important that
you understand how the architecture works. In essence you're calling a remote object on the
server, which functionally has the same implications as calling a FoxISAPI server or an ASP
294 Internet Applications with Visual FoxPro 6.0
COM componenthang it and your app dies. But the idea of controlling the server directly
from the client, rather than using the server to do all of the work, is very appealing. However, it
also exposes some security holes I'll discuss in the next section.
You'll also run into the same scalability issues faced by pure server applicationsyour
component might have huge numbers of simultaneously connected users. Because RDS loads
and then unloads the object on each method call, there's a fair amount of overhead involved. If
you're running the initial release of Visual FoxPro 6.0, you'll run into blocking issues because
only a single method call on the server can execute at one time. A forthcoming Service Pack of
Visual FoxPro 6.0 addresses this by allowing multiple instances of objects to process methods
concurrently on multiple threads (see Chapter 9 for more information about this update).
Beware of security issues!
While I've been demonstrating these examples you might have noticed that the way RDS
works can be a huge security problem. This is because the client can basically dictate how to
access the data or object on the server. Because the client code can be created in any client
application, like Visual FoxPro or Visual Basic or even Internet Explorer scripting code,
anybody has access to your back-end data or RDS object.
Data security
As you might expect, this is a serious security issue. Look at what's possible with the RDS data
control. Earlier I told you that you can connect to the server with:
oRDS.Connect = "driver=Microsoft Visual FoxPro Driver;" & _
"Exclusive=No;SourceType=DBF;" & _
"SourceDB=d:\wwcode\wc2\wwdemo;uid=;pwd="
Well, nothing is stopping the crafty IE VBscript coder from accessing another directory. Even
if you hide your SourceDB in a DSN, the client can possibly still guess directories on your
server and access data directly. It requires some knowledge about what files are available, but
if that knowledge is in place (perhaps an ex-employee?) all data is at risk to be retrieved and
even manipulated without the client having rights on that server. That's rightit doesn't matter
if client X doesn't have rights, because the data is accessed through the Web server and the
impersonation account that RDS runs. The RDS server piece runs in an Admin or System
account and has full rights on the server.
Even a secure data source like SQL Server can be compromised. If RDS is installed and
you have a DSN to access your SQL server, RDS can be scripted from the client side.
Somebody with inside information could gain access to a database and issue DELETE FROM
<YourMostImportantTableHere> to wipe out all of your data!
Scary, ain't it? Once a user has figured out what data is available, he's free to issue a SQL
command of DELETE FROM GUEST. Worse, if you're using Internet Explorer it's easy to
figure out how data is accessed. Take a look at the Guest.asp examplethe DSN is visible
directly if you view the HTML source. You can gain better protection by using a fat client
application, or wrapping the data access logic in an ActiveX control that handles the server
connection without exposing any information about the data. Still, even with that approach
Section 3 Chapter 8: Remote Data Service 295
you're open to somebody guessing where your data sitsit's unlikely, but definitely possible,
especially for people inside the company.
To make things even worse, RDS is installed through the IIS 4.0 install program, which
doesn't warn you about any security issues. If you choose to install Remote Data Service
(which is the default!) during the IIS 4.0 installation process, you're opening up your system to
this behavior, possibly without even knowing what the security issues are. To check this out,
look in the IIS Admin console for the MSADC virtual directory. If it's there, RDS has been
installed. There's no direct way to uninstall RDS, eitheryou have to make some changes in
the server's registry (or you can remove the MSADC virtual directory). RDS determines what it
has access to through the following registry key:
HKLM\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\ADCLaunch
Any of the servers listed here are those that can be invoked through the RDS.DataSpace's
CreateObject() method. Here are the two default keys that RDS installs:
RDSServer.DataFactory
RDS uses this generic data service when you use the RDS.DataControl to access data on
the server. If you remove this key, direct RDS data access will no longer work. This is
probably the biggest security risk.
AdvancedDataFactory
This object is used to create new instances of objects on the server using
DataSpace.CreateObject(). Remove this key to disallow remote object instantiation.
In addition to the default keys, you can add entries for your own objects. Any objects you
create to be called through DataSpace.CreateObject() must be registered here by creating a key
with the ProgId of the server.
Object security
Data access is the most vulnerable security issue because of the way that RDS allows the client
side to directly access a remote data source via an ODBC/OLE DB connect string. Using
Dataspace objects allows a little more security because you can essentially hide the database
connection issues in code that runs on the server. But that still leaves your remote object open
to access from the client. This, however, could be a little more difficult to hack because you
need to know the interface to the object.
Again, the biggest issue is the fact that IE Scripting is wide open for anyone to examine. If
you use a remote Dataspace object and it's scripted through IE, viewing the HTML source will
expose the code required to hit the server. Any method calls made can be misused. Say you
have a method called DeleteRec to delete the current order displayed. From this information it
wouldn't be hard to figure out other order numbers and start deleting those as well.
Some workarounds
RDS is inherently insecure, precisely because the client security is not checked. There are
some ways around this, however:
296 Internet Applications with Visual FoxPro 6.0
Hiding code in ActiveX controls
The biggest problem is that using IE Scripting makes code visible on the client side. You
can work around this by putting your code into binary modules like ActiveX controls (VB
or VC++) instead of VBScript. That way the data access code is not visible to the client.
Using authentication for object access
This type of authentication would have to work through a Web server application that can
log users in. You can obviously limit access to the actual pages that contain the scripted
RDS code. You can use an authentication scheme to force users to log in first and then use
the authentication information in the Web page through ASP scripting (by using <%=
Request.ServerVariables("Remote_User") %> and embedding this into a method call).
Along the same lines, you could validate the user and then pass down a unique cookie on
each hit. The cookie can be retrieved on the client side and passed down to the server on
each method call. This would ensure that users go through the proper paths to get to the
desired page.
However, this approach will not guard against internal sabotage. People who have
legitimate access could still hack into the system by accessing the server directly and
guessing at functionality to which they might not have direct access
(RDSDemo.RDSServer for example).
Fat client
Fat client applications written in VFP, VB, and so on offer a lot more security because the
connect and access information is not visible to the actual user. This provides protection
from client eyes, but it doesn't protect you from somebody in the know who's hacking in
and making the same calls on the server.
Controlling security for applications that have client-side logic and server access services is
always problematic because the nature of the beast requires the client to have generic access.
With that flexibility comes the power both to build cool applications and to abuse security
rights. I demonstrated similar issues with the WinInet data transfer code, but there, at least, you
had control over the process so you could use your own authentication schemes and build them
directly into the "protocol" level. No such luck with RDS because it's generically packaged by
Microsoft.
If you want to use data or objects on the server that can be accessed by a wide Internet
audience, you're opening up a security hole because anybody who knows how can get at the
data logically. However, levels of security vary, and RDS is very bad at this because it doesn't
offer any security options from the client side. Even if you want to build a tightly controlled
Internet application that allows access only to certain users, user verification is difficult.
You're still taking the risk of allowing access to anyone who has knowledge of the data or
object interface.
Section 3 Chapter 8: Remote Data Service 297
DCOM over HTTP
Another option for object access that's coming with NT 5 (Windows 2000) and COM+ is
DCOM over HTTP. DCOM provides similar capabilities to the RDS DataSpace object, but
with direct access to server objects. The advantage of DCOM is that security is configurable at
the component level so you can lock out unauthorized users. For wide-audience applications,
though, you'll run into the same issues as with RDS. DCOM's security model also requires
clients to be configured to access a server over the networksomething that RDS does not
require. For wide-audience apps this would be a major problem, but one that could be
addressed with an ActiveX wrapper module.
Regardless of the route you take, you should start thinking along the lines of building
ActiveX controls for server-access code from browser-based or fat client applications. It's
really the only safe way to deal with remote objects at this time.
Summary
RDS is exciting technology, but I have a lot of reservations about at this time. The ability to
access data directly over HTTP should be a very exciting prospect to most database developers,
and RDS makes this process reasonably easy. It works from within Internet Explorer so you
can build pure browser-based data access applications. Object access over HTTP allows you to
run any COM object on the Web server, providing a simple mechanism to share logic between
the client and server sides.
The biggest problems with RDS have to do with the many little glitches you'll run into as
you use it. From full-on failures of RDS to minor things like the IE data-binding controls not
accepting fields with leading spaces, it can be a frustrating experience to build your first app
that uses RDS. I spent a lot of time building even the simple examples in this chapter. The
bottom line is that RDS works, but it takes some doing to make it work right. I hope Microsoft
improves this tool in the future, but it seems much of RDS has remained stagnant since its
original introduction.
The other issue is security. RDS has no security model, and using any of the RDS
technologies exposes your server to serious security issues. Essentially, RDS allows open
access to any data sources on the server and any objects that are registered to be used with
RDS. Anybody who knows where and how to get at the data can access these tools to perform
damaging operations on the server without proper authorization.
Pros
Easy data access over HTTP.
Compatible with ADO.
Works from IE Scripting model, Visual FoxPro and all COM clients.
Remote object access is extremely powerful.
Cons
Many problems with VFP ODBC driver, IE data binding, and stability.
RDS client data object is very limited for data manipulation.
Lack of a security model makes this technology very dangerous.
Browser support includes only Internet Explorer.
298 Internet Applications with Visual FoxPro 6.0
Section 4 299
Section 4
Enterprise Development
Web development is inherently different from developing standalone or even traditional
client/server applications. Server-based applications tend to run on a single or a small number
of servers and might be accessed by tens of thousands of users simultaneously. In order to
provide smoothly running applications, developers must take care to manage the resource use
of these servers against the incoming transaction load. Scaling applications and load-balancing
resources against available resources is a critical piece of any commercial Web application.
There are many interrelated factors involved in building large-scale applications. Section 4
introduces these concepts and demonstrates how they relate to Visual FoxPro. However, while
the actual application and Visual FoxPro are crucial, many additional factors such as servers,
network hardware, security, and proper configuration of the operating system are just as
important. It's vital to understand the interdependencies of the various pieces and to be able to
put together a team of people that can handle all aspects of a Web applicationmuch of this
goes far beyond just the application development aspect that you may be used to from
standalone applications.
This section introduces these concepts by starting with a discussion of how the Visual
FoxPro COM model fits into this server-based environment. Chapter 9 discusses issues like
multi-threading and Active Server Pages COM calling interfaces as well as using Microsoft
Transaction Server with VFP. Chapter 10 discusses scalability issues in great detail, outlining
the concepts and specific examples of what to look at when putting an application together.
Topics include performance tuning, data access method comparisons, load balancing, stress
testing, running applications across multiple machines, and how security affects the
application. Chapter 11 deals with the development process and discusses the people
requirements for building a large-scale Web application.
While many of these topics may seem like overkill for smaller applications, the very same
issues applyalbeit on a smaller scale. It pays to think about growth issues from the start,
rather than having to re-engineer applications later. Take the time to review the content of this
section even if you don't think you'll be creating 'large-scale' applications.
Section 4 Chapter 9: Visual FoxPro and COM 301
Chapter 9
Visual FoxPro and COM
Microsoft's focus has been on extending functionality of all aspects of Windows and the
Internet through COM. All the new technologies pouring out of Microsoft are becoming
accessible through the use of COM as soon as they become available in the operating
system and the development tools. Visual FoxPro provides the functionality to act as
both a COM client and COM server to allow you to take advantage of the COM
programming model in VFP's familiar environment.
You've already seen how COM is used extensively with Active Server Pages and
FoxISAPI, as well as some client-side technologies. The basics of COM are simple
enough to understand and implement, but things get trickier once you go beyond these
basics and need to scale COM applications (or any kind of application, for that matter) to
large transaction volumes. In relation to scalability, I'll discuss how Visual FoxPro
implements its COM threading model and how this relates to the Web technologies
already discussed, as well as its role for using Microsoft Transaction Server with VFP. I'll
also discuss the basics of DCOM and how running COM objects on remote machines can
help spread the load of your applications across the network.
COM scalability and Visual FoxPro 6.0
COM provides the gateway to hook in Visual FoxPro functionality from Web applications and
other server applications. When you run an app in a server environment, application rules
change quite drastically. You're no longer dealing with "applications," but rather with "servers"
that provide specific functionality to clients. On the Web and in other high-volume operations,
this means that your COM objects can have large numbers of clients that are more or less
simultaneously accessing your server components. The key to building applications that work
well in this environment is being able to handle many simultaneous requests to provide good
responsiveness to the user, without overloading the machine's resources. In addition, it's
important to minimize contention for resources between different components accessing these
resources. This extends to concurrency of objects loaded into a process (IIS in Web
applications), data access and lock issues, as well as hardware resources like the CPU.
Visual FoxPro has been a COM client since version 3.0 and became capable of building
COM servers with version 5.0. Visual FoxPro 6.0 adds support for Apartment Model
Threading (AMT) in order to provide better performance and scalability to its server
implementation. However, the initial release of VFP 6.0 has some serious limitations in terms
of scalability. VFP 6.0 implements AMT, but it's forcing server access to be synchronized and
causing simultaneous method calls to wait for completion of another request before continuing.
In essence, the blocking issues are such that only one method call at a time can execute on any
given server.
The good news is that Microsoft has been working on this issue. The problem will be
addressed in a shortly forthcoming interim release that provides a new multi-threaded runtime
(Microsoft's term). At the time of this writing, there was no official announcement of when this
302 Internet Applications with Visual FoxPro 6.0
update will ship. However, it has been demonstrated in public by Microsoft and I have been
running a very early beta version for testing with this book, which confirms that the multi-
threaded runtime works as expected. I would expect this new update to be released in the first
quarter of '99, but that's my estimate and not Microsoft's (the latest is that it will be in Visual
Studio SP3). If you are doing COM development, this update is significant enough that you'll
definitely want to get your hands on it as soon as it's released.
Let's start this chapter by reviewing how COM accesses objects.
What is Apartment Model Threading and why should you care?
You've probably heard the term Apartment Model Threading before. It's a crucial mechanism
that allows the COM subsystem to run multiple, essentially single-threaded
applications/components at the same time, giving a simulation of multi-threading.
AMT works by allowing multiple, simultaneous instances of your COM component to be
created on separate threads. The operation is transparent and the logistics for the threading
model are built into the Windows COM subsystem, with Visual FoxPro 6.0 complying to the
Apartment Threading Model. Although COM, through this mechanism, makes it possible to
run your COM servers as multi-threaded objects that can operate concurrently, your program
has little control over the multi-threading environment. In other words, the system controls the
threading model and your application behaves just as a standalone application running in a
multi-user environmentthe COM system determines how many instances (threads) are active
at any point of activity on your server.
Contrast this with a "real" multi-threaded object created with C++, which can be reentrant
(enter the same binary in memory code multiple times simultaneously) and serve multiple
requests simultaneously from a single instance. Because high-level tools like VFP and VB
aren't reentrant, multiple separate instances are created on separate threads, each with its own
global data stored in Thread Local Storage. Each instance is independent (no data sharing!),
which eliminates the need for synchronizing access to object data, as a true multi-threaded C++
object would have to do. COM performs a slick sleight of hand to enable applications like VFP
to run in a multi-threaded environment! The downside is that each instance must duplicate
certain data and its environment, which takes up a significant amount of memory. (With the
new multi-threaded runtime, a minimal server takes about 700 KBwith the old VFP 6
runtime it was closer to 1.5 MB.) Hence memory is used and load time is comparatively slow
because an environment block and local storage must be set up for each server.
To take advantage of Apartment Model Threading in VFP, simply create an in-process
component (build a multi-threaded COM server (.dll)) in the Project Manager, making sure
each OLEPUBLIC class is marked for multi-use operation) and then instantiate your
component via CreateObject/CoCreateInstance or an equivalent function call from any COM-
compliant client. If the client is multi-threaded like IIS, it can take advantage of the scheduling
magic that COM performs to allow your server to be called on multiple, simultaneously
operating threads. In the case of Active Server Pages, the client is Internet Information Server
using an ASP page that has created an object reference of your component via the
Server.CreateObject() method.
Regardless of how you create your object, COM always invokes your object on a specific
thread, and guarantees that it's always called on this same thread for the duration of its
Section 4 Chapter 9: Visual FoxPro and COM 303
reference lifetime. In Microsoft-speak, this thread context is known as an apartment. The COM
subsystem in Windows handles the logistics of marshaling requests on your component to the
appropriate thread if necessary. If the thread calling your component is already the correct
thread, no marshaling takes place. Marshaling becomes an issue only if objects are persisted
across multiple threads or, in terms of IIS, across multiple requests. In ASP this manifests itself
as objects tied to the Session or Application objects. If you're using FoxISAPI, the object
references to your server need to be marshaled on each request, from the incoming ISAPI
thread to the originally pooled thread that created the COM object.
If you're curious, marshaling is a process that affects how quickly COM method calls and
parameter passing takes place over cross-apartment or cross-process threads. COM provides a
mechanism for communicating across these boundaries through a proxy and stub object that
duplicates the COM interface of your COM object, providing a sort of store-and-forward
mechanism of any requests. The proxy and stub objects are used for the communication layer
that is abstracted at the COM level to allow communication over the most efficient protocol
available. Because of the way the proxy/stub architecture works, COM can provide a consistent
interface to your server regardless of whether the object runs in the same process, out of
process, or even on another machine. For each of these scenarios, the proxy/stub objects will
know the correct mechanism to marshal the calls from client to server.
With cross-apartment marshaling, a thread has to potentially wait for the required
apartment thread to free up. Once it does, COM switches context to this thread, which involves
copying the thread-specific data block to the existing thread. With Apartment Model
Threading, threads often don't have to switch context because the calling thread is already the
correct one, and the call can be quite fast. At other timesmost likely when the server
becomes very busythread marshaling must take place because the client holds multiple
simultaneous references on various threads. In this case, COM needs to spend the extra
overhead to switch context and potentially wait for threads to clear up.
Marshaling is always used when calling out-of-process Automation servers (including in-
process objects running in an MTS process), where threads need to be marshaled from the
client process to the server process. This is the main reason why in-process components run
faster. But keep in mind that marshaling issues affect only performance of the actual method
call interface and parameter passingnot the actual operation of your Visual FoxPro server
code! For instance, a call to a method that takes two seconds to process won't be noticeably
faster than an in-process method compared to an out-of-process one, because the overhead of
the call is tiny compared to the two-second operation that takes place. But a short method call
that makes maybe two VFP function calls that take a hundredth of a second, running in a tight
loop called 100 times in a row, will be much slowersometimes as much as a few hundred
times slower!
Apartment Model Threading is available only to DLL/in-process COM objects. EXE/out-
of-process servers must be managed manually in thread pools to allow concurrent method
calls. Note that in-process servers in Visual FoxPro 6.0 can no longer have any user
interfacethis means no message boxes, no error dialogs, and no File Open dialogs. Even
WAIT WINDOW and INKEY() are not allowed! All access to any UI will generate a VFP
exception in your COM server. (You can still "run" forms in VFP without generating an error,
but the UI is not visible. It's equivalent to DO FORM NOSHOW.)
304 Internet Applications with Visual FoxPro 6.0
Problems with the initial release of VFP 6.0
VFP 6.0, unlike VFP 5.0, does support Apartment Model Threading, but it blocks access to the
same server while another call to that same server is executing. The server is blocked at the
component level, which means that the components start up on a new thread but have to wait
for a blocking lock to clear before they can enter the processing code. This means that if two
users hit a page that uses the same COM server (remember, a server can contain multiple
objects), the requests will queue up one after the other. If there is a 10-second request followed
by a one-second request, the one-second request might have to wait up to 11 seconds for its
returned result. That's very limiting for an Active Server Page, or any other multi-threaded
client, on a busy Web server that might have hundreds or even thousands of simultaneously
active users!
An interim release to the rescue
This blocking behavior is addressed in a forthcoming Service Pack of Visual Studio (release
date unknown at this time, but expected in early 1999). See Figure 9.1. This release provides a
new multi-threaded runtime that handles thread isolation and does not block simultaneous
method calls. I'm currently using an early beta version of this release, which allows me to fire
up unlimited, simultaneous instances of my server.
Figure 9.1. Using the new multi-threaded runtime allows COM components to execute
requests concurrently. Any number of object calls and server instances can be
accessed at any time, given server resources to process these requests. This
behavior requires the forthcoming interim release of Visual FoxPro 6.0 and will not
behave this way with the original version of VFP 6.0.
It's ironic that this important functionality took so long to get into Visual FoxPro, but the
Fox team did a tremendous job of accomplishing this difficult task with such a huge product
and existing code base. The multi-threaded behavior is a vital requirement for building server
applications that must deal with requests of different execution times. With this code in place,
short requests can execute immediately without having to wait for long requests to complete.
For a hands-on example, take a look at "ASP Scalability" section in Chapter 4, where the
Section 4 Chapter 9: Visual FoxPro and COM 305
behavior of Visual FoxPro with either the multi-threaded or single-threaded runtime is
demonstrated with a detailed sample.
Another obvious application that requires multi-threading support is Microsoft Transaction
Server. As with ASP, the updated multi-threaded runtime allows multiple objects to run
simultaneously inside MTS, whereas the original version of VFP 6.0 did not. This issue is even
more crucial for MTS because the MTS package wraps all access from any application process
on the system to a component. The new multi-threaded runtime offers a solution to these
problems, allowing Visual FoxPro to finally be a fully viable part of the Microsoft Enterprise
Platform.
In my early testing, I've found that the multi-threaded runtime is very efficient and can
take advantage of multiple processors at over 85% utilization rates for multiple processors.
With two processors, processing efficiency reached close to 190% of a single processor, and
with four processors 330% of a single processor was achieved. Since overhead exists in
processor context switches at the OS level, these numbers are excellent.
Using the new multi-threaded runtime
To support the new runtime, Microsoft introduced a new runtime file called Vfpt.dll, which is
used instead of the old Vfpr.dll file for the single-threaded runtime. When you install the VFP
update, both runtimes are added to your system and new compile options in the project's Build
option allow you to route your server to the appropriate runtime. To build your servers with
the multi-threaded runtime, use the following syntax:
BUILD MTDLL aspDemos FROM aspDemos
You'll also find a new Build option to build a multi-threaded COM server, as shown in
Figure 9.2.
Figure 9.2. Using the project's Build dialog to create a
COM DLL with proper multi-threaded support enabled.
306 Internet Applications with Visual FoxPro 6.0
The new version of the runtime strips some functionality from the full VFP runtime (menu
support, reports, old @say..get statements, and so on, which is subject to change at this time)
so it is more lightweight, but you might want to check your code to make sure you don't run
into some of the missing features. Additionally, Microsoft suggests that the multi-threaded
runtime might be slightly slower than the regular runtime because it has to access memory
using thread-specific local thread storage, which is slower than direct-memory access. In my
first tests I didn't experience any performance drops, so new optimizations in the runtime in
general may have offset some of the possible losses. Overall, any minor performance loss
should be a fair tradeoff for the added scalability of the new multi-threaded runtime.
Multi-threading is not a magic bullet
Although it's great news that VFP supports true multi-threaded COM access, realize that this
will not automatically solve all of your scalability problems. In fact, it might introduce new
ones. Visual FoxPro can now create new instances of your object whenever you make an
instance request. However, there are no controls on how many objects can be created. You
might find that all of a sudden you have too much of a good thing, with too many instances
firing up simultaneously, causing your server to become overloaded.
For example, think of an application that runs a long query that takes 30 seconds to run.
While running this query, VFP uses a fair amount of CPU cycles. Now imagine 100 users on a
Web site all running this query simultaneously. COM will create 100 instances of your
objectall of which attempt to use a single CPU (or possibly even multiple machines). The
likely result is that none of those requests will return in time because the combined load brings
the server to its knees. Furthermore, this situation might cause the server to get so backed up
with other requests, or even more of the same slow requests, that it never recoversthe server
becomes busy processing requests that are already timed out.
A slow request like this should probably be offloaded to another machine or a SQL Server
for processing so that the Web server is not responsible for processing the load simultaneously.
In some instances this will cause the load to migrate to another machine, but hopefully other
options will exist for splitting up the load between different machines and processes.
Incidentally, there's a way to limit how many servers are loaded by using Microsoft
Transaction Server. This new feature is available with NT Service Pack 4 or later. By default,
MTS will create a maximum of 100 apartment threads (per package) for client work. These
threads are used for actual processing of each request to your COM object, which means you
can have 100 simultaneously processing requests but many more active object references. If the
pool of active objects is exhausted, requests start queuing, which is what you'd like to have
happen in order to avoid server overload. You can change the value by editing the following
registry key for your MTS component:
HKLM/Software/Microsoft/Transaction Server/Package/{your package GUID}
Add a new DWORD value key named ThreadPoolMax and enter a numeric value for the
number of thread as appropriate. I suggest that you should never have more than a few
instances of your component in call at any given point in timetypically two or three per
processor if all processing is local, but more if you're processing data remotely on a SQL back
end or using DCOM to offload processing to other machines. Anything more can result in
Section 4 Chapter 9: Visual FoxPro and COM 307
unacceptable system slow-downs. If you use packages with multiple components, you'll have
to carefully evaluate your object counts and come up with a number that works for you. The
key is to balance the server's resources against the amount of work it is asked to do without
running the processor into the 85-90%+ range.
Microsoft Transaction Server
There's been a lot of hype around Microsoft Transaction Server, but until Service Pack 3 with
the multi-threaded runtime support ships, Visual FoxPro is not a good client to MTS. Although
the original VFP 6.0 release runs in MTS, it cannot process simultaneous requests on any
server. MTS relies heavily on loading multiple instances of your COM object (one for each
client reference) into its own address space regardless of the calling application process, which
necessitates running and calling simultaneous instances. The new runtime fixes these issues
and lets VFP be a good MTS citizen.
MTS is somewhat misnamedalthough it provides transactional features, it also provides
a number of other useful COM system services, including:
Resource management of COM objectsJust In Time Activation (JITA)
COM objects loaded through MTS can be automatically unloaded and reloaded. MTS
caches the VFP runtime, so load and reload is very fast. MTS may also cache COM
references of your object, so if objects are loaded in quick cycles your servers are never
unloaded/reloaded even though your client code releases references. MTS handles all of
this through its own abstraction layer, saving resources on the server. But there is a cost:
COM operation through MTS is slower than calling COM objects directly. Note: JITA is
not a pool managerobjects are unloaded with each request or method call, which is very
inefficient!
Role-based security
MTS implements a new security model that allows you to configure roles for a component.
The roles are configured at the component level and mapped to NT users. Your code can
then query for the roles under which the component is running, rather
than using the much more complex NT Security model and functions to figure out user
identity. Roles are also useful for deployment because they can be packaged with the
component and can be automatically installed on other machines if the necessary
accounts exist.
Packaging technology
MTS includes a packaging mechanism that allows taking a package (an MTS word for an
application or group of objects), which might contain multiple COM objects, and packing
it up into a distribution file. The distribution file contains all binary files (DLLs, type
libraries and support files) as well as all information about the registry and the security
settings of the component. This package can be installed on another machine for instant
uptime.
308 Internet Applications with Visual FoxPro 6.0
Transactional features
This is where MTS gets its name. MTS allows database-independent management of
transactions spanning multiple data sources and multiple COM objects. Essentially MTS
can wrap operations into its own transactional monitor, which works through the
Distributed Transaction Controller (DTC), causing any database operations to be
monitored. Components can call transactional functions to complete or abort operations, at
which time MTSrather than the applicationcauses the data sources to commit or
revert. Data sources must be DTC compliant, which means SQL Server or Oracle at this
time. (This may also apply to other data sources with third-party drivers.) Visual FoxPro
data is not DTC compliant. The key feature of this technology is that transactions have
been abstracted to the COM layer; rather than coding transactional SQL syntax, you can
use code-based logic to deal with transactions. The transactions are SQL back-end
independent (as long as the back end is DTC capable). Transactions can also span multiple
servers and even across servers running different SQL back ends, such as SQL Server and
Oracle. A single transaction could wrap data access that writes to both.
Context management
MTS includes a static data store object similar to ASP's Application object to allow
components to store data for state-keeping and inter-component communication.
Do you need Microsoft Transaction Server?
Before you decide to use MTS you should ask yourself whether you need its functionality. If
you don't need any of its specialty features like role-based security or multi-phase commit of
database transactions, there might be no good reason to bother with it. Even when running the
new multi-threaded VFP runtime, MTS is not much more than a wrapper around your COM
object. This out-of-process COM object wrapper in turn acts as a proxy and hosts all access to
your component via passthrough. All object access occurs through an out-of-process COM
call, and there's the additional overhead of passing the data through two interface layers. The
result is that running components inside MTS tends to be slower than running them as plain in-
process COM objects, without gaining any scalability benefits.
MTS hosts your COM objects inside a package, which is an out-of-process component you
can see on the Processes tab of the Task Manager running as Mtx.exe. Each package uses its
own Mtx.exe host to encapsulate its objects. When an MTS component is installed into MTS,
the ClassIds and ProgIds get mapped to the package, which in turn knows how to pass on any
interface calls directly to the actual objects. The end result is that a client application never has
a direct connection to your COM object, but always has a proxy reference to the MTS package,
which passes through all interface calls.
Through some registry mapping, an instance of this object is instantiated when you issue
CREATEOBJECT. The package then creates your object and calls the methods in typical
passthrough fashion. Any method calls made to the component hit the package container's
proxy object first, and then are passed down to your internal COM object. Figure 9.3 shows
how objects are called from an ASP page. Note that if multiple simultaneous requests are made
on your component, multiple instances start up and run inside the MTS package. If you're
using the new multi-threaded runtime, you'll be able to get simultaneous method calls with the
Section 4 Chapter 9: Visual FoxPro and COM 309
original version of VFP 6.0. The standard runtime calls to your component will block,
essentially allowing only a single method call to occur at a time in a given package.
The wrapper package provides component services I mentioned earlier, as well as isolates
your component from the client process. (Hey, that's not really a feature since you can do that
with out-of-process components on your ownbut that's marketing for you.)
Out-of-process packages are the norm, but MTS also supports in-process packages. These
work the same as out-of-process packages but run inside the client process. However, I've seen
problems with this approach, and it seems Microsoft provided this feature more for its own
system services (like IIS virtual applications) than anything else. Running components in in-
process packages has resulted in many weird and unexplainable crashes and lockups in my
tests, where the out-of-process packages of the same exact configuration work fine. Your
mileage might vary. In-process packages are slightly faster, but they also void the process-
isolation features that protect the client process from a crashing component in your package.
You might not be able to restart in-process packages, and they often require the calling process
to shut down before refreshing or updating the components.
Figure 9.3. When running through MTS, your components are accessed
indirectly through an MTS package, which provides a pass-through interface.
The state of the stateless
MTS provides some cool features, but it doesn't do much for scalability. Still, even if you
decide you don't need MTS, you should consider building objects that are compatible with it.
You can accomplish the best performance with MTS and any other COM client by using
stateless objects. Stateless objects are objects that don't rely on property values to maintain
their "state" or context between method calls. These objects make no assumptions about any
values for themselves or other "global" aspects of the system. They reestablish themselves
when they start up, by using parameters or values stored in a database or establishing state
from scratch.
If you're going to build an effective component for use with MTS, it should be stateless.
For MTS this means that when a method call completes it should be able to release the object
and hand you a brand-new copy on the next request. In order for this to work, your component
cannot rely on non-default property settings used by multiple consecutive method calls.
310 Internet Applications with Visual FoxPro 6.0
If the object is stateless, MTS can take the existing object reference and recycle it to pass
on to another client. That's the theory, anyway, but in reality MTS destroys your object after
each call to SetComplete() and then totally re-creates it when you make another method call,
rather than simply caching existing references. Object pooling likely will be provided in later
versions of MTS, but the current behavior provides a safe environment for legacy (non-MTS)
components by always guaranteeing startup state to a stateless component that is not the
optimal way to cache objects.
The key functionality to building an MTS component deals with establishing and then
releasing the state of the object or its Object Context. To do so, you have to reference the
MTXAS.AppServer object and use its GetObjectContext(), SetComplete() and SetAbort()
methods.
To demonstrate how MTS works, I'll build a small functional sample that uses the same
logic that I used to demonstrate the multi-threaded behavior of the new multi-threaded runtime.
I'll recycle the same SlowHit() code that demonstrates multi-threaded access to the component
and adjust it so it works within the confines of MTS. To start, create the basic server code:
DEFINE CLASS MTSDemo AS Custom OLEPUBLIC
oMTS = .NULL.
hFile = 0
FUNCTION Init
THIS.WriteOutput("Init Fired")
THIS.oMTS = CREATEOBJECT("MTXAS.AppServer.1")
ENDFUNC
FUNCTION Destroy
THIS.WriteOutput("Destroy Fired")
IF THIS.hFile > 0
FCLOSE(THIS.hFile)
ENDIF
ENDFUNC
************************************************************************
* MTSDemo :: SlowHit
*********************************
*** Function: Allows you to simulate a long request. Pass number of
*** seconds. Used to demonstrate blocking issues with
*** VFP COM objects.
************************************************************************
FUNCTION SlowHit
LPARAMETER lnSecs
LOCAL loMTS, loContext, x
THIS.Writeoutput("SlowHit Fired" + TRANSFORM(lnSecs))
loContext = THIS.oMTS.GetObjectContext()
lnSecs = IIF(EMPTY(lnSecs), 0, lnSecs)
DECLARE Sleep IN Win32API INTEGER
FOR x = 1 TO lnSecs
Section 4 Chapter 9: Visual FoxPro and COM 311
FOR x = 1 TO lnSecs
Sleep(1000)
ENDFOR
ENDFOR
DECLARE INTEGER GetCurrentThreadId IN Win32API
IF !ISNULL(loContext)
loContext.SetComplete()
ENDIF
RETURN GetCurrentThreadId()
ENDFUNC
FUNCTION WriteOutput
LPARAMETER lcString
IF THIS.hFile = 0
Declare INTEGER GetCurrentThreadId IN Win32API
THIS.hFile = FCREATE(Sys(2023) + "\MTSDemo" +SYS(2015) + ".txt")
ENDIF
IF THIS.hFile # -1
FWRITE(THIS.hFile, lcString + CHR(13) + CHR(10))
ENDIF
ENDFUNC
ENDDEFINE
A few optional pieces in this code deal with logging information to diskI'll discuss these
later. The key feature of a COM object built for use in MTS is that it can retrieve an object
reference to the MTXAS.AppServer.1 object, which is the MTS base object. To retrieve the
context within MTS for the currently active request, use its GetObjectContext() method and
store it to a variable:
THIS.oMTS = CREATEOBJECT("MTXAS.AppServer.1")
loContext = THIS.oMTS.GetObjectContext()
I stuck the CreateObject() call into the Init event of the class so I don't have to retype the code
that has to run in each call method. As you'll see in a minute, the Init of your server fires every
time a method call is made to your object, so this is no more efficient than directly calling the
same code in each method. However, it saves some typing and centralizes the code.
The only thing left to do in your server method code is to call either SetComplete() or
SetAbort()it doesn't matter which one you call, since this component isn't participating in
any MTS transactions. To tell MTS that it's okay to release resources on the object, use:
IF !ISNULL(loContext)
loContext.SetComplete()
ENDIF
312 Internet Applications with Visual FoxPro 6.0
MTS components outside of MTS
I explicitly check for the existence of the object before calling
SetComplete(), which allows you to test the server's operation without
running inside MTS. You can always create the MTXAS object (even when you're
not running inside MTS), but the GetObjectContext() method returns NULL if not
called from within an MTS object. If you trap for this state, you can write
standalone components or those that work as part of MTS.
Now create a project and add a PRG file (or VCX if you choose to go that route) named
MTSServer. Now compile this server into a COM object:
BUILD MTDLL MTSServer FROM MTSServer
If you don't have the multi-threaded runtime, just use BUILD DLL insteadbut realize your
server will block simultaneous client requests.
Before dumping the server into MTS, use the following code to test it:
o = CREATEOBJECT("MTSServer.MTSDemo")
? o.SlowHit(5)
This should run the server, wait for five seconds, and then spit out a thread ID number. If that
works, move this component into MTS by following these steps:
1. Start the Microsoft Management Console.
2. In the treeview, select Microsoft Transaction Server, then My Computer, then Packages
Installed.
3. Click the New icon on the toolbar and create an empty package named MTS Book Demo.
4. Select the new package, press the right mouse button, and click Properties.
5. Click the Identity tab and notice that the default is set to Interactive User, which is the
currently logged-in account. If your component runs prior to login, the SYSTEM account
will be used. Here you can also change the user account to a specific user account. Note:
This setting determines the user context and the rights for what resources your component
can access, regardless of the client. Save and exit this dialog.
6. Go down one more level to Components and click the New icon.
7. Click Install New Components and select Mtsserver.dll. If you aren't using the new multi-
threaded runtime, also add the TLB file. Click OK.
8. In the component's Properties, set its Transaction options. If you don't use distributed
transactions through DTC, set the "Does not support Transactions" option, which will
result in slightly faster performance than the other options. Use "Supports Transactions" if
you have a mixed-request load. Figure 9.4 shows how the installed component should
look inside MTS.
Once the component is registered with MTS, nothing changes in the way the client calls
the object, so re-run the previous code but bump the timeout to a higher number:
o = CREATEOBJECT("MTSServer.MTSDemo")
? o.SlowHit(15)
</MyTable>
Section 4 Chapter 12: Loose Ends 387
In this case, the DTD describes the relationship between the various element types of the
document. It shows which tag contains what other tag and whether elements are optional (?) or
required (+). The rules for a DTD can be fairly complex and can be extended to include
detailed information about the formatting of each element. For simple tasks, such as
transferring a flat object like a table or object, the DTD is optional and probably not necessary.
Generating XML
In the course of applications that you build, you'll undoubtedly want to create XML
output. With the Developer's Download Files at www.hentzenwerke.com you'll find
the wwXML class, which is a small tool that can convert objects and table data into
XML. The ObjToXML method allows you to create an XML object from any Visual FoxPro
object. All object properties will be included in the XML string. Unfortunately, because of the
way the VFP AMEMBERS() function works, all properties (including protected and VFP
stock properties) are available. It's simple to use this object:
*** Run a query
SELECT * FROM Guest WHERE CustId = "N404WQYT"
*** Create an object from the result record
SCATTER NAME loGuest MEMO
*** Now create the XML
oXML = CREATEOBJECT("wwXML")
lcXMLString = oXML.ObjToXML(loGuest, "Guest")
*** If you're using FoxISAPI/Web Connection
Response.Write(lcXMLString)
*** With ASP you'd return the result to the ASP page
* RETURN lcXMLString
The result from this operation is:
<?xml version="1.0"?>
<Guest>
<company>123 Systems</company>
<custid>N404WQYT</custid>
<email>bbob@test.com</email>
<entered>05/21/97 02:30:05 AM</entered>
<location>Home</location>
<message>Hello World</message>
<name>Billy Bob</name>
<password>billy</password>
<phone></phone>
</Guest>
Objects that are created with SCATTER NAME are special in VFP because they don't
include any stock properties or methods. If you tried this with a Custom object you'd get stock
properties like Left, Height, Tag, and so on, in addition to your custom properties.
</guest>
CursorToXML() takes the data from the currently selected work area and generically generates
an XML document from it. Looking at this data, you'll probably notice that XML is not a very
efficient data format. There's a lot of wasted space that's related to the start and end tags
unfortunately, this is part of the data format and a price you pay for the flexibility of XML. The
tags are what makes XML work with any kind of data.
However, XML data elements (field values above) cannot contain certain characters. In
particular, the markup tags <, >, and & are not allowed. Linefeeds are stripped and converted
Section 4 Chapter 12: Loose Ends 389
into spaces. In strict parsers, " and ' are not allowed, although the Internet Explorer XML
parser doesn't care about these. In order to allow these special characters to be embedded into
an element, you have to escape the data using the CDATA structure:
<Document>
<data><![CDATA[This text contains markup
<Markup>Text</Markup>]]></browser>
</Document>
Failing to embed data in this fashion when illegal characters are embedded will cause XML
parsers to fail parsing the documentit will be neither well-formed nor valid and the object
collections won't be available.
Besides these simple methods to create XML, you can also create your own XML via
code. You can simply scan through your tables and build up the XML as a string. This gives
you the most flexibility and is the fastest way to generate XML.
Nested XML
One of the nice things you can do with XML is to embed data in hierarchical layouts that
include master and detail relationships. You can nest these as deeply as you see fit. Consider
the following example of an order described as an XML document:
<?xml version="1.0"?>
<Orders>
<Order>
<OrderDate>12/01/98</OrderDate>
<OrderTotal>123.40</OrderTotal>
<Customer>
<name>Rick Strahl</name>
<company>West Wind</company>
<address>32 Kaiea</address>
</Customer>
<Detail>
<item>
<sku>LABOR</sku>
<desc>Labor moving stuff</desc>
<qty>1</qty>
<price>50.00</price>
</item>
<item>
<sku>SALESITEM</sku>
<desc>New Toilet Seat Covers for Gov't</desc>
<qty>10000</qty>
<price>2000.00</price>
</item>
</Detail>
</Order>
<Order>
another order could go here
</Order>
</Orders>
390 Internet Applications with Visual FoxPro 6.0
If you had to download this data to a user using tableseven a fully denormalized oneit
would be a drag. With XML the data is contained in a single text document that can be
downloaded over the Internet. Also, most people can look at this XML document and figure
out what the data means and how it's laid out. I hope you can see how XML can simplify
moving data around from different applications and over the confines of an Internet
connection. Just keep thinking along the lines of data transfer mechanism, not a replacement
for existing database technology!
XML parsing
So far I've discussed creating XML, which is straightforward as long as you understand the
basics. For most applications, creating your own XML is probably OK, but ideally you'll want
to make sure your XML is fully compliant with the XML standard. In order to do so, you
should use an XML parser to create and read your XML data.
The latest XML initiative calls for XML parsers to also follow a strict standardthe XML
Document Object Model. The latest set of parsers all use the same object interface, so
regardless of whether you use a Java, C++ or ActiveX parser they all support the same object
names and methods. For Microsoft applications, a compliant parser will become available with
Internet Explorer 5.0.
The MSXML parserready now, but it's not compliant
Internet Explorer 4.0 ships with an early XML parser that is not DOM compliant, but provides
much of the same functionality. The IE 4 parser is not a validating parser, which means it can't
read and validate a DTD. You might want to use the IE 4 parser for current applications until
IE 5 gets more prevalent. Using the IE 4 MSXML parser (which is also available in IE 5) on
the document above looks something like this:
oXML=create("msxml")
oXML.url = "c:\temp\test2.xml"
*** Check for a well-formed document
IF TYPE("oXML.root") # "O"
RETURN .F.
ENDIF
*** Root Node - table level
oRoot = oXML.root
? oRoot.tagname && Orders
? oRoot.Children.item(0).TagName && Order Note 0 based!!!
? oRoot.Children.item(0).Children.Item("OrderDate").text && 12/01/98 as string
?
oRoot.Children.item(0).Children.item("Customer").Children.item("Address").text
FOR EACH order IN oRoot.Children
? "Order #:",Order.Children.Item("OrderDate").Text
? "Ordered by:
Section 4 Chapter 12: Loose Ends 391
",Order.Children.item("Customer").Children.item("Name").Text
FOR EACH item IN Order.Children.Item("Detail").Children
? Item.Text
ENDFOR
ENDFOR
RETURN .T.
As you can see from the code, the document is abstracted into a hierarchical layout that starts
with a root object. Each object has a zero-based Children collection, which can be used to
iterate through the next lower-level items. Elements are accessed through the Item method,
which can either take a numeric index value or a key name. These collections always start at
element zero; you can check the size of each collection by looking at the Length property. To
iterate through a collection use the FOR EACH construct.
The above demonstrates that anything but a flat structure gets a little confusing to write
quickly. But while the syntax may be lengthy, it really takes very little code to drill down to the
appropriate level if you know which element to access.
Tagnames that make sense
The generic routines in the wwXML class create records with a
Record or Row tagname, which is not optimal for searching. Ideally
you'd want to use a primary key for the record-level tag names, so each row's key
would be easily accessed by its primary key value through the collection:
<?xml version="1.0"?>
<guest>
<N404WQYS>
<custid>N404WQYS</custid>
<name>Rick Strahl</name>
</N404WQYS>
<Q404WQYS>
<custid>Q404WQYS</custid>
<name>Billy Bopp</name>
</Q404WQYS>
</guest>
You can then access the individual records with:
oRecord = oRoot.Children.Item("Q404WQYS")
Using the final parameter of wwXML::CursorToXML you can specify a key
expression that can automatically create keyed records for generic cursor
conversion as well.
The IE 5 DOM parser
Why am I telling you about the MSXML parser if it's likely to become obsolete? Most people
currently have MSXML on their machines, but it will take awhile for the IE 5 version with full
DOM support to be available on all desktops. If you need to build applications with XML now,