FT19984 - 5 - Empower ASP With Visual FoxPro
FT19984 - 5 - Empower ASP With Visual FoxPro
FT19984 - 5 - Empower ASP With Visual FoxPro
Seite 1 von 14
This is the second of two articles that looks at Web development using Active Server Pages (ASP). In the first one (see "Activate Your Web Server" in the March 1998 issue), Jim explained the basics of Web development, the tools you need to develop Web pages, how to use Microsoft's Active Server Pages (ASPs), and how to use Active Data Objects (ADO) to access ODBC data. This month, he demonstrates three methods to enhance ASPs and focuses on how you can use VFP to play a crucial part in this architecture.
I'm going to assume that you're comfortable with the fundamental tools needed for creating an ASP and using ADO to access ODBC data, as addressed in last month's article. If you didn't read Part 1, you've missed one of the greatest literary contributions of our time, and you can't pass Go or collect $200. Well, that might be stretching it just a bit, but if you're not comfortable with these technologies, you should read that article, or some other source, before continuing with this month's exciting conclusion. That being said, I'll have to assume that you're ready to get your hands dirty by using the combined powers of ASPs and your favorite database development tool, Visual FoxPro, to build a small Web site. The sample application is a simple two-page Web site that allows users to enter search criteria on the first page and then shows the results formatted in a table on the second page. I think this is typical of what many people's first attempt at a Web site will be like. The target audience for this Web site would be book authors who are interested in seeing how well, or (sigh) poorly, their books have sold. They'll be required to enter an author ID and can also optionally enter a book title in order to see the sales figures. I call this the Book Sales by Author Web site. (Hey, I don't get paid to be creative.) In this article, I'll take three different approaches to illustrate how ASPs become more powerful by instancing COM objects (DLLs) within them. The first technique uses ADO as the data access method, and the other two techniques use Visual FoxPro DLLs to not only retrieve data from SQL Server, but also to create the HTML to pass to the browser. In each case, the architecture is as follows:
n The client is, of course, a Web browser. n The middle tier is a Windows NT Server running Internet Information Server 4.0 (IIS), which includes ASP and other n The data source is the sample Publishers ("pubs") database that ships with Microsoft SQL Server. For the sake of
simplicity, SQL Server will be running on the same NT Server as IIS. (I don't want to get into Distributed COM yet!) server-side components. This will be our Web server.
In the first example, I'll take the most traditional approach to development with ASP. The client browser will make a request of the Web server for the Search page. That Search page will be an HTML file with controls on it for entering the ID and book title. When the author presses the Begin Search button on the form, an ASP file will be called on to validate the search criteria. If the criteria is incomplete, an appropriate error message will be returned. If the criteria is valid, the ASP file will use VBScript to create and use an ADO connection to retrieve the book sales from SQL Server and place the results in an ADO record set. The ASP file will also create the appropriate HTML from the record set, and then pass it to the client browser for display. In the second example, I'll show you how to improve upon the ADO/VBScript technique by instancing a VFP DLL to take over the data retrieval and HTML creation. Finally, I'll use yet another technique to illustrate a little-known feature of ASP -- its ability to pass an object reference of itself to a COM object. This is analogous to the VFP technique of calling a function and passing THIS, so that the called function has a handle to all of the property, events, and methods of the calling object. In other words, by using the object reference that ASP passed to the VFP DLL, you can control ASP from VFP. If you haven't yet said "wow!" to yourself, please re-read the last paragraph until you do. I'll wait right here. Setting the environment Last month I described how to set the environment in both Windows 95 and Windows NT, so I'll assume you have a proper Web development site ready to go. If you're working in Windows 95 and don't have Windows NT installed, you have two choices. You could install Windows NT 4.0, IIS 3.0 (or greater), and SQL Server 6.5. You'd then be able to follow my examples exactly. Otherwise, you could stick with Windows 95 and Personal Web Server, and all you'd have to change is the ODBC connection and SQL statement (there's only one of each) in order to access a Visual FoxPro or Access database.
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 2 von 14
When you create your ODBC DSN in the ODBC Data Source Administrator, make sure you create a System DSN, as opposed to a File DSN. System DSNs are available to all users of your machine, including NT services. Also, when you register your SQL Server in the SQL Enterprise Manager, you should select Use Trusted Connection, so you don't need to provide a login ID and password when connecting. My files for this month's article are in a subdirectory (\examples) of the default Web Publishing directory that Windows NT creates when installing IIS: c:\inetpub\ wwwroot. Also, you'll notice that my NT machine name -- "PUMA" -- will be used in my URLs this time instead of an IP address; it's a little friendlier, and my cat really appreciates it.
Tip!
Don't forget to set the Web Sharing rights of any directory in which you execute ASP files. Remember that the Execute Scripts option must be selected for you to be able to run ASP files in that directory. To do this in Windows NT, right-click on the c:\inetpub\wwwroot directory, choose Sharing| Internet | Add. On the WWW Directory Properties page, click Home Directory, which will make c:\inetpub\wwwroot your default Web publishing directory. Finally, click Execute under the Access section so that ASP scripts can be executed. Once again, for ease of demonstration and because of the simplicity of the HTML pages (and the writer), I'll be using Notepad as my editor. However, feel free to use any HTML authoring tool you choose. That being said, let's get started. Book Sales by Author Web site -- the Search form The first thing to construct is the Search form. This same form will be used for each of the techniques, and I'll make it an HTML file since there's nothing dynamic about it. This form, frmSearch.htm, and the others in this article are available in the accompanying Download file. When an author comes to this site, he or she will be presented with a form with two text boxes in which to enter his or her ID and an optional book title (see Figure 1 ). When the Begin Search button is pressed, the form's ACTION will invoke the GetSales.ASP file using an HTTP Post method. I'm using the Post method so that the form's elements will be available to me via the Form collection of the Request object after the form has been submitted. Figure 1.
Here's the code for the Search Form. It's pretty straightforward: <!-- FrmSearch.htm --><HTML> <TITLE>Book Sales by Author</TITLE> <BODY> <!-- You will have to keep changing the following line to reference the correct ASP file in upcoming examples (i.e., GetSales2.ASP) --> <FORM ACTION="http://puma/example/getsales.asp" METHOD="Post"> <CENTER><H2> <U>Welcome to the Book Sales by Author Web Site</U> </H2></CENTER> <H3><U>Please Enter Your Search Criteria<BR></U> <FONT COLOR="Red">* </FONT>Mandatory <BR><BR> Author ID: <Input Type="Text" Name="txtID" Size=30> <FONT COLOR="Red">*</FONT> <BR> Book Title: <Input Type="Text" Name="txtTitle" Size=30> (Leave blank for all) <BR><BR> <Input Type="Submit" Name="cmdSubmit" Value="Begin Search"> </BODY> </H3> </FORM> </HTML>
Validating the form GetSales.ASP performs two functions. First, it validates that the author entered an ID. If an ID wasn't entered, then the user is shown another HTML form that displays the error message. This is the basic structure of GetSales.ASP. In the accompanying Download file , I've included three versions of GetSales.ASP, one for each data access technique described in this article.
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 3 von 14
<!- - GetSales.ASP - - > <HTML> <FORM> <!--Give user a link to return to Search Form --> <CENTER> <A HREF="http://puma/example/frmsearch.htm"> Return to Search Form </A> </CENTER> <BR><BR> <!-- Validate the form before retrieving sales--> <%If Len(Request.Form("txtID")) = 0 Then %> <!-- Left the Author ID field empty so, create a new page showing the error--> <TITLE>Book Sales by Author</TITLE> <BODY> <CENTER><H2><U>Welcome to the Book Sales by Author Web Site</H2></U></CENTER> <FONT> <CENTER><H3><U>Your request was rejected for the following reason(s):</H3></U> </FONT> <BR> The Author ID is required.</CENTER> <%Else 'Passed validation, so get sales figures <<< This is a placeholder where the code >>> <<< for the three different data access >>> <<< techniques will be placed >>> End if%> </FORM> </HTML>
Getting the sales -- the ADO way So far, all of the code I've shown is applicable to each of the three techniques I'll be discussing. However, the data retrieval portion of the GetSales.ASP file is where the methods differ. A typical ASP page would probably use ADO to handle the data access. ADO, like any COM-compliant, in-process server (DLL), can be instanced within an ASP script. You can use ADO to connect to an ODBC data source similar to the way you would using VFP's SQL Pass-through function, SQLConnect. However, while VFP would put your result set in a local cursor, ADO populates a record set object that has properties, collections, and methods. You then access the data through ADO's interface. Here's the data access code using ADO that will be run if the form validation succeeds. (Replace the placeholder comment in the Else condition of GetSales.ASP with this code.) 'Technique 1: VBScript and ADO 'Passed validation, so get sales figures ' Use the Server object's CreateObject method ' to instance an ADO Connection object Set oConn=Server.CreateObject("ADODB.Connection") 'Open a new connection to the data source oConn.Open("PubsDSN") 'Build the SQL string using author's ID lcSQL = "Select t.title 'BookTitle', " & _ "t.ytd_sales 'YTDUnitsSold'," & _ "t.price 'Price', " & _ "t.ytd_sales*t.price 'Extended', " & _ "t.royalty 'Royalty%', " & _ "(t.ytd_sales*t.price)/t.royalty 'Net' " & _ "From authors a, titleauthor ta, titles t " & _ "Where a.au_id = ta.au_id and " & _
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 4 von 14
"ta.title_id = t.title_id " & _ "And a.au_id = '" & Request.Form("txtID") & "'" 'Add book title to Where clause if was provided If Len(Request.Form("txtTitle")) > 0 Then lcSQL = lcSQL & _ " And t.title Like '" & _ Request.Form("txtTitle") & "'" End If 'Create a record set object by executing the SQL Set oRS = oConn.Execute (lcSQL)
This code should be pretty clear. First, I instanced an ADO connection object, opened a connection to the Pubs database, and then built a SQL string based on the form's data elements. Then, I executed the SQL string to create an ADO record set object. Now it's just a matter of looping through the record set and building the HTML. This is the rest of the Technique 1 code: 'Rest of Technique 1 ' Make sure we have records, ' else Move methods create errors If oRS.BOF AND oRS.EOF Then Response.Write("There are no records found for the criteria you have entered.") Else 'Recurse the record set and build HTML table 'Define table and caption Response.Write("<TABLE Border=1 CellSpacing=1>") Response.Write("<CAPTION Align=TOP>") Response.Write("Sales Activity for Author: ") Response.Write(Request.Form("txtID")) Response.Write("</CAPTION>") ' Define Column headers ' Create a table row Response.Write("<TR>") 'Loop through Fields collection to get field names For Each oField In oRS.Fields 'Create table/column header Response.Write("<TH>") Response.Write(oField.Name) Response.Write("</TH>") Next 'End table row Response.Write("</TR>") ' Build data rows and columns Do While Not oRS.EOF 'Begin a new table row Response.Write("<TR>") 'Recurse fields collection for field values For Each oField In oRS.Fields 'Create data element Response.Write("<TD>") Response.Write(oField.Value) Response.Write("</TD>") Next 'Advance the record pointer oRS.MoveNext 'End the table row Response.Write("</TR>") Loop Response.Write("</TABLE>") 'Close the connection oRS.Close 'Clear resources Set oRS = Nothing End if%>
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 5 von 14
Figure 2.
ADO is a very powerful and robust data access method. However, space prohibits me from examining the entire object model in this article. For more information on ADO and the other data access techniques offered by Microsoft, check out http://www.microsoft.com/data/. You call that a DML? Ugh! At the expense of offending any Visual Basic developers who might be reading these FoxTalk pages (wondering how the other half lives, I guess), I must say that I can't imagine having to write data manipulation code like that every day. Call us spoiled, but we write data-intensive applications that need to be fast. VBScript, which is a subset of Visual Basic for Applications, which is a subset of Visual Basic, is obviously nice and lean, but it's the wrong tool for the job when it comes to data. Fortunately, there's COM. My friend Bill, a Web developer, author, and poet, said this to me some time ago when he first learned of the ability to use COM within ASP pages: "This changes everything!" And he was emphatically right. My predicament here is a perfect example. Why should I use VBScript for something FoxPro could do much more efficiently? With COM, I don't have to. All I need to do is create a VFP DLL (which, if you haven't done so yet, is just another Build option in the Project Manager) to do the data access via SQL Pass-through. The result set will be a FoxPro cursor, where I can INDEX, SCAN, SEEK, REPLACE, or use any other VFP command! Getting the sales -- the VFP/SQL Pass-through way The previous two blocks of code in the ELSE clause of GetSales.ASP would be replaced with code whose purpose is to instance a VFP DLL. This DLL would be responsible for: 1) making calls to SQL Server via ODBC; and 2) formatting the HTML based on the resulting cursor. (Again, Replace the placeholder comment in the Else condition of GetSales.ASP with this code.) 'Technique 2: VFP/SQL Pass-through Way 'Passed validation, so get sales figures 'Instance VFP DLL to do the VBScript/ADO work Set oVFPObject = Server.CreateObject("VFPSvr.GetSales") If IsObject(oVFPObject) Then ' Gather form variables lcID = Request.Form("txtID") lcTitle = Request.Form("txtTitle") ' Call DLL and pass Author ID and Book Title cGetSalesResults = oVFPObject.Execute(lcID, lcTitle) ' Write the HTML table (or failure results) ' to the browser Response.Write(cGetSalesResults) 'Release object variable Set oVFPObject = Nothing Else Response.Write("Could not load COM Object VFPSvr.GetSales") End if
Here's the code for the VFP DLL. I created a project called VFPSvr and added one program class called GetSales2.Prg, indicating the second version of GetSales. I saved the DLL in the WINNT\SYSTEM32 directory to be sure all users have access to it.
* GetSales2.Prg DEFINE Class GetSales As Custom OLEPUBLIC Name = "GetSales" nConn = -1 cDSN = "PubsDSN" cID = "" cTitle = "" cResponse = "" PROCEDURE Execute LPARAMETERS tcID, tcTitle
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 6 von 14
this.cID = tcID this.cTitle = tcTitle this.ConnectToDSN() IF This.nConn > 0 && connection successful this.GetSalesData() SQLDISCONNECT(THIS.nConn) && close connection ELSE this.cResponse = "Sorry, ; cannot connect to data source at this time." ENDIF RETURN This.cResponse ENDPROC PROCEDURE ConnectToDSN this.nConn = SQLConnect(This.cDSN) ENDPROC PROCEDURE GetSalesData LOCAL lcSQL, lnRetval * Build the SQL string using author's ID lcSQL = "Select t.title 'BookTitle', " + ; "t.ytd_sales 'YTDUnitsSold', " + ; "t.price 'Price'," + ; "t.ytd_sales*t.price 'Extended', " + ; "t.royalty 'Royalty%', " + ; "(t.ytd_sales*t.price)/t.royalty 'Net' " + ; "From authors a, titleauthor ta, titles t " + ; "Where a.au_id = ta.au_id and " + ; "ta.title_id = t.title_id " + ; "And a.au_id = '" + This.cID + "'" * Add book title to Where if it was provided' IF !Empty(This.cTitle) lcSQL = lcSQL + ; " And t.title Like '" + ; This.cTitle + "%'" ENDIF lnRetval = SQLExec(This.nConn, lcSQL, "cSales") IF lnRetval > 0 IF Reccount("cSales") > 0 this.GenHTMLTable("cSales") ELSE this.GenHTMLNoRecords() ENDIF USE In cSales ELSE this.GenHTMLErrorState() ENDIF ENDPROC PROCEDURE GenHTMLNoRecords this.cResponse = "There are no ; records found for the criteria you have entered." ENDPROC PROCEDURE GenHTMLErrorState this.cResponse = "Sorry, but an error occurred " + ; " while performing the last operation. " + ; " Please try again later." ENDPROC PROCEDURE GenHTMLTable LPARAMETERS pcAlias LOCAL lnCols, lcFieldValue, lcFieldCaption, ; lcHTMLTable SELECT (pcAlias) * Define table and caption lcHTMLTable = "" lcHTMLTable = lcHTMLTable + ; "<TABLE Border=1 CellSpacing=1>" lcHTMLTable = lcHTMLTable + ; "<CAPTION Align=TOP>" + ; "Sales Activity for Author: " + This.cID + ;
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 7 von 14
"</CAPTION>" * Define Column headers lcHTMLTable = lcHTMLTable + "<TR>" FOR lnCols = 1 To FCount() lcHTMLTable = lcHTMLTable + "<TH>" lcFieldCaption = Proper(Field(lnCols)) lcHTMLTable = lcHTMLTable + lcFieldCaption lcHTMLTable = lcHTMLTable + "</TH>" ENDFOR lcHTMLTable = lcHTMLTable + "</TR>" * Build data rows and columns SCAN lcHTMLTable = lcHTMLTable + "<TR>" FOR lnCols = 1 To FCount() lcHTMLTable = lcHTMLTable + "<TD NoWrap>" lcFieldValue = ; This.ConvertToString(Eval(Field(lnCols))) lcHTMLTable = lcHTMLTable + lcFieldValue lcHTMLTable = lcHTMLTable + "</TD>" ENDFOR lcHTMLTable = lcHTMLTable + "</TR>" ENDSCAN lcHTMLTable = lcHTMLTable + "</TABLE>" this.cResponse = lcHTMLTable ENDPROC PROCEDURE ConvertToString LPARAMETERS tvString LOCAL lcRetval DO CASE CASE TYPE("tvString") = "Y" lcRetval = STR(tvString,10,2) CASE TYPE("tvString") = "N" lcRetval = STR(tvString) CASE TYPE("tvString") = "D" lcRetval = DTOC(tvString) OTHER lcRetval = tvString ENDCASE RETURN lcRetval ENDPROC ENDDEFINE
Other perks and bennies There are numerous benefits to calling COM objects from ASP pages:
n The best tool for the job -- Because COM doesn't care what client it's serving, you can have 10 DLLs written in 10
different languages if you desire, with each DLL being written in the language best suited for the task. For example, a C++ DLL could be written to perform a complicated financial calculation. A VFP DLL could be written to access and massage data. A VB DLL could be written to create HTML strings. n Encapsulation -- Take another look at GetSales.ASP. It contains very little code to do a lot of work. That's because a self-contained COM object has taken the burden off of the scripting language and given it to VFP (or any other language that can create COM/OLE Automation servers). n Reusability -- That same VFP COM object that the ASP page calls can be instanced by an application written in VFP, VB, C++, Word, Excel, or any other COM-compliant client. So, your well-tested, generic code can be written once but can serve multiple applications. You wouldn't have this luxury if you created even the most reusable of ASP pages; it would still only be able to serve a Web browser.
Getting the sales -- the better VFP/SQL Pass-through way Although the last technique offers great benefits and enhances ASP's base functionality, the next technique takes ASP to a new level. A not-well-documented, yet fully supported, feature of ASP is that when you use Server.CreateObject to instance a COM object from within an ASP page, an object reference is implicitly passed to that COM object's OnStartPage method. All you need to do to make this "magic" happen is to add a method called OnStartPage to your class, provide a parameter placeholder, and save the value to a property. Here's how the start of the new custom class should look. (I called both this project and the DLL VFPSvr2, but the program is GetSales3.PRG because it's the third version we've done.) * GetSales3.Prg Define Class GetSales As Custom OLEPUBLIC
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 8 von 14
Name = "GetSales" oASPPage = Null 'Create this method so that ASP passes 'an object reference of itself. Procedure OnStartPage LParameters poASPPage 'Save it to a property for subsequent use This.oASPPage = poASPPage EndProc . . . EndDefine With This.oASPPage, I now have the entire ASP object model available to me -- within VFP! In other words, I can do things like This.oASPPage.Response.Write("Hello, I'm coming to you from VFP"). Now that's powerful. The following is the second technique enhanced to capitalize on this "free" functionality (again, this code goes in the placeholder section of the ELSE condition of GetSales.ASP): 'Technique 3: VFP/SQL Pass-through Way 'passing object context. 'Passed validation, so get sales figures Set oVFPObject = ; Server.CreateObject("VFPSvr2.GetSales") If IsObject(oVFPObject) Then ' Gather form variables lcID = Request.Form("txtID") lcTitle = Request.Form("txtTitle") ' Call DLL and pass Author ID and Book Title ' No need to Response.Write DLL will do that cGetSalesResults = oVFPObject.Execute(lcID, lcTitle) Else Response.Write("Could not load COM Object ; VFPSvr2.GetSales") End if Here's the revised VFP Server DLL (VFPSvr2.DLL): * GetSales3.Prg DEFINE Class GetSales As Custom OLEPUBLIC Name = "GetSales" oASPPage = Null nConn = -1 cDSN = "PubsDSN" cTitle = "" cID = "" PROCEDURE OnStartPage * Implicitly fired when called by ASP LPARAMETERS poASPPage this.oASPPage = poASPPage ENDPROC PROCEDURE OnEndPage * Implicitly fired when ASP script finishes this.oASPPage = Null ENDPROC PROCEDURE Execute LPARAMETERS tcID, tcTitle this.cID = tcID this.cTitle = tcTitle this.ConnectToDSN() IF This.nConn > 0 && connection successful this.GetSalesData() SQLDISCONNECT(THIS.nConn) && close connection ELSE this.oASPPage.Response.Write( ; "Sorry, cannot connect to data source at this time.")
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 9 von 14
ENDIF RETURN PROCEDURE ConnectToDSN this.nConn = SQLConnect(This.cDSN) ENDPROC PROCEDURE GetSalesData LOCAL lcSQL, lnRetval * Build the SQL string using author's ID lcSQL = "Select t.title 'BookTitle', " + ; "t.ytd_sales 'YTDUnitsSold', " + ; "t.price 'Price', " + ; "t.ytd_sales*t.price 'Extended', " + ; "t.royalty 'Royalty%', " + ; "(t.ytd_sales*t.price)/t.royalty 'Net' " + ; "From authors a, titleauthor ta, titles t " + ; "Where a.au_id = ta.au_id and " + ; "ta.title_id = t.title_id " + ; "And a.au_id = '" + This.cID + "'" * Add book title to Where if it was provided' IF !Empty(This.cTitle) lcSQL = lcSQL + ; " And t.title Like '" + ; This.cTitle + "%'" ENDIF lnRetval = SQLExec(This.nConn, lcSQL, "cSales") IF lnRetval > 0 IF Reccount("cSales") > 0 this.GenHTMLTable("cSales") ELSE this.GenHTMLNoRecords() ENDIF USE In cSales ELSE this.GenHTMLErrorState() ENDIF ENDPROC PROCEDURE GenHTMLNoRecords LOCAL lcStr lcStr = " There are no records found for the " + ; " criteria you have entered." this.oASPPage.Response.Write(lcStr) ENDPROC PROCEDURE GenHTMLErrorState LOCAL lcStr lcStr = "Sorry, but an error occurred " + ; " while performing the last operation. " + ; " Please try again later." this.oASPPage.Response.Write(lcStr) ENDPROC PROCEDURE GenHTMLTable LPARAMETERS pcAlias LOCAL lnCols, lcFieldValue, lcFieldCaption SELECT (pcAlias) * Define table and caption WITH This.oASPPage .Response.Write("<TABLE Border=1 CellSpacing=1>") .Response.Write("<CAPTION Align=TOP>") .Response.Write("Sales Activity for Author: ") .Response.Write(This.cID) .Response.Write("</CAPTION>") ENDWITH * Define Column headers WITH This.oASPPage .Response.Write("<TR>") FOR lnCols = 1 To FCount() .Response.Write("<TH>") lcFieldCaption = Proper(Field(lnCols))
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 10 von 14
.Response.Write(lcFieldCaption) .Response.Write("</TH>") ENDFOR .Response.Write("</TR>") ENDWITH * Build data rows and columns WITH This.oASPPage SCAN .Response.Write("<TR>") FOR lnCols = 1 To FCount() .Response.Write("<TD NoWrap>") lcFieldValue = ; This.ConvertToString(Eval(Field(lnCols))) .Response.Write(lcFieldValue) .Response.Write("</TD>") ENDFOR .Response.Write("</TR>") ENDSCAN .Response.Write("</TABLE>") ENDWITH ENDPROC PROCEDURE ConvertToString LPARAMETERS tvString LOCAL lcRetval DO CASE CASE TYPE("tvString") = "Y" lcRetval = STR(tvString,10,2) CASE TYPE("tvString") = "N" lcRetval = STR(tvString) CASE TYPE("tvString") = "D" lcRetval = DTOC(tvString) OTHER lcRetval = tvString ENDCASE RETURN lcRetval ENDPROC ENDDEFINE As you can see, with COM you can get back to writing code in the language that's appropriate for the situation (or in many cases, the language with which you're most comfortable). Keep in mind that this example barely scratches the surface of what you could do with this functionality; all of the ASP objects and concepts discussed in last month's article can be applied right within your VFP code. To illustrate some of them, I'll enhance the performance of our scripts and add some error trapping to the preceding code by using page buffering. Performance One of the ways you can improve page performance is to use the Buffering property of the Response object. Buffering lets you control when, and how much, HTML you send to the client browser. (This concept is similar to VFP's LockScreen form property, which allows you to buffer screen painting.) In my example, I could give the user the illusion of a snappier response to his or her query by writing to the browser only enough rows to fill a screen and then buffering the balance until the SCAN is complete. This is the new start of GetSales.ASP. I'll call this GetSales4.ASP: <%@ Language="VBScript" %> <% Response.Buffer = True %> <HTML> <!-- The Buffer property must be set before the opening HTML tag so that ASP knows whether to send HTML or buffer it. --> <!-- Statement: @ Language="VBScript" sets the default language so that you can set buffering --> <!-- Called from frmSearch.htm --> <!-- Uses VFPSvr3.DLL to get sales from SQL Server and create HTML table This version has page buffering --> <FORM> <!--Give user a link to return to Search Form --> <CENTER> <A HREF="http://puma/example/frmsearch.htm"> Return to Search Form
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 11 von 14
</A> </CENTER> <BR><BR> <!-- Validate the form before retrieving sales--> <%If Len(Request.Form("txtID")) = 0 Then %> <!-- Left the Author ID field empty so, create a new page showing the error--> <TITLE>Book Sales by Author</TITLE> <BODY> <CENTER><H2><U>Welcome to the Book Sales by Author Web Site</H2></U></CENTER> <FONT> <CENTER><H3><U>Your request was rejected for the following reason(s):</H3></U> </FONT> <BR> The Author ID is required.</CENTER> <%Else 'Passed validation, so get sales figures <<< This is a placeholder where the code >>> <<< for the three different data access >>> <<< techniques should be placed >>> End if%> With Buffering set to True, I now have access to three methods of the Response Object -- Flush, Clear, and End. Flush writes all buffered data to the browser, Clear removes all buffered data, and End stops the script from running and Flushes the data. For example, I could use the Flush method in the following way (this code replaces the Scan loop in the VFPSvr2.DLL; it's contained in VFPSvr3.DLL in the accompanying Download file ): * Build data rows and columns Local lnRows lnRows = 0 WITH This.oASPPage SCAN nRows = nRows + 1 *Since buffering is True, these Writes *won't make it to the browser until I *Flush the data .Response.Write("<TR>") FOR lnCols = 1 To FCount() .Response.Write("<TD NoWrap>") lcFieldValue = ; This.ConvertToString(Eval(Field(lnCols))) .Response.Write(lcFieldValue) .Response.Write("</TD>") ENDFOR .Response.Write("</TR>") If nRows = 20 *Display the first 20 rows *to the browser .Response.Flush Endif ENDSCAN .Response.Write("</TABLE>") *Display the rest of the records *Note: Not really necessary ASP * automatically issues a Flush at the * end of a page. .Response.Flush ENDWITH Error trapping Error handling in ASP pages is very weak at this point. When an error occurs in VBScript, the Err object is populated with information pertaining to the error; however, all errors must be handled in-line. In other words, an On Error routine can't branch to a subroutine via the Goto statement. So, the only alternative is to use the On Error Resume Next statement, which will ignore the error and continue with the next line of code. At the end of the script, you can check whether any errors have occurred by examining the Err method. (Okay, so some things about Web development are a step backward. It's still early in the game, so you'll have to be patient while this technology really matures.)
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 12 von 14
The default behavior of On Error Resume Next isn't very elegant; the offending error message is displayed to the browser for the world to see. To prevent that from happening and regain control of your application, you can use the Clear and End methods of the Response object. If an error occurs while you're building an HTML response, you can Clear the buffer, populate it with an error message, and then End the script. Here's the newly error-trapped GetSales.ASP file: <%@ Language="VBScript" %> <% Response.Buffer = True %> <HTML> <!-- The Buffer property must be set before the opening HTML tag so that ASP knows whether to send HTML or buffer it. --> <!-- Statement: @ Language="VBScript" sets the default language so that you can set buffering --> <!-- Called from frmSearch.htm --> <!-- Uses VFPSvr3.DLL to get sales from SQL Server and create HTML table This version has page buffering --> <FORM> <!--Give user a link to return to Search Form --> <CENTER> <A HREF="http://puma/example/frmsearch.htm"> Return to Search Form </A> </CENTER> <BR><BR> <!-- Validate the form before retrieving sales--> <%If Len(Request.Form("txtID")) = 0 Then %> . . . <%Else 'Passed validation, so get sales figures . . . End If%> <% 'Error trap 'After script has run, check for possible errors 'raised during processing by examining the 'VBScript built-in Err object. If Err.Number <> 0 Then 'An error has occurred here Response.Clear ' Clear the current buffered output before sending it Response.Write("An error has occurred! Please try again later.") Response.End ' Stop running the script and send new buffer contents to browser End if %> For code reusability, you'll want to replace the preceding error trap code with a Server Side Include (SSI) so it can be used generically in every ASP page you write. This is the error trapped ASP file skeleton: <% Response.Buffer = True On Error Resume Next %> <HTML> <!-- HTML and script goes here -- > </HTML> <!-- #include File="errhandle.asp" -- > Scalability issues
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 13 von 14
As of VFP version 5.0, major scalability issues remain. Remember, Web sites can be accessed like no other application you've ever developed. Therefore, a DLL that's serving, potentially, thousands of users must be able to replicate itself, or allow threading so that many people can access the same code without having to wait. Fortunately, Microsoft has responded: Tahoe, the next version of VFP, has apartment model threading and better MTS integration. So feel free to develop Web sites with VFP and ASP -- by the time you're ready to deploy, the scalability issues will be solved. Acknowledgements and suggested reading I'd like to acknowledge a few people who really helped me bring these two articles to fruition. Thanks to Bill Martiner, author of The Visual Basic Programmers Guide to Web Development, whose book and words of wisdom led me to spread Web development to the Fox community. Thanks also to John V. Petersen, who helped solidify the VFP Server prototype; Phu Luong, for helping with my hardware and O/S woes; and Rick Strahl, who doesn't even know me, yet quickly responded to my pleas for help. (Be sure to check out Rick's Web site, www.west-wind.com, for articles on working with VFP on the Web.) Lastly, Professional Active Server Pages, by Homer, Enfield, et al. (WROX Press Ltd.) is a great book for learning everything there is to know about ASP. Conclusion As I mentioned in last month's conclusion, it's difficult to do these topics justice within the parameters of an article. (There are entire books about each one of these concepts.) However, I do hope you can see the power ASP brings to Web development and how ASP can be "super-charged" with the addition of COM objects, like those you can build in VFP. Although these technologies are a bit immature, there's a very bright future. Products like IIS 4.0, ADO 2.0, Visual InterDev, MTS, and future versions of the Visual Studio developer tools will all help to bring Web development to the state we'd like.
n Waiting forever -- If you call an ASP file that instances a DLL, and your browser appears to "hang" while its status bar
reads "Web site found. Waiting for reply...", you most likely have a bug in your DLL. Fortunately, VFP will at least report the correct error to you. During development on a single machine, minimizing your browser will display a VFP error dialog box. While useful to you, when users are running the application, the error dialog box will show up on the server, not on their machines, and thus will appear to hang for them indefinitely. Try to prototype your DLLs within VFP before attempting to call them from ASP. Waiting forever: the sequel -- Even if you don't have any bugs in your DLL code, you still might wait forever. NT is very picky about security; users by default have very limited rights. VFP DLLs, on the other hand, create .TMP files in the WINNT\SYSTEM32 directory by default when they're instanced. This causes a security issue when VFP attempts to delete the .TMP files and your ASP page just hangs. See Microsoft KB Article ID: Q174790 to learn how to redirect Fox's temp files. Unloading scripts -- If you get the following error while trying to execute an ASP file -- "ActiveX Component can't create object" -- you must unload your cached ASP script from memory. In IIS 3.0, nothing short of restarting your machine seems to release it. However, in IIS 4.0, if you run the Internet Service Manager (now called Microsoft Management Console) and right-click on your Web publishing directory (that is, example) and choose Properties, the Directory Properties dialog box has an Unload button to remedy this. The everlasting DLL -- When you instance a COM Server from an ASP file, that DLL sticks to RAM like barnacles to a ship's hull! (You'll get a File Access Denied error when you attempt to rebuild a DLL.) In IIS 3.0 and 4.0, nothing short of restarting your machine seems to release it. This is another reason why I try to prototype my DLLs with VFP before calling them from an ASP file. Security -- When you prototype a Web site on NT, it makes the default Web user IUSR_<computername>. For example, when I named my computer "PUMA", NT created a user called IUSR_PUMA. This user doesn't have full read/write access to all directories, but you can temporarily avoid security issues by adding this user to the Administrators Group in the User Manager utility. ADO Connection error -- If you try to create an ADO connection object and you receive the error "ADODB.Connection Error. No current record", you might need to re-install ASP. (See Microsoft KB Article Q 174639 for details.)
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06
Print Article
Seite 14 von 14
Don't forget that this is new to everybody and no one as yet has all of the answers. But Web development isn't going away, so just start slow and don't get frustrated. If you can learn this stuff on your own time, you'll be a hero when your company is ready for its first Web site.
http://foxtalknewsletter.com/ME2/Audiences/Segments/Publications/Print.asp?Module=... 06.02.06