Previous Page TOC Next Page See Page



— 10 —
Using OLEISAPI with the Microsoft Internet Information Server


http://www.microsoft.com

Microsoft has finally jumped head-first into the world of the Internet. One of its first entries into the mainstream Internet product area was the Internet Explorer, a Web browser that originally shipped with the Microsoft Plus! product but which you can now freely download from the Microsoft Web site (http://www.microsoft.com). After the Internet Explorer, the buzz about Microsoft was that they would finally release a Web server that was integrated with Windows NT. An FTP service was already built into NT, but it wasn't the friendliest FTP server you could use.

In early 1996, Microsoft did release its NT Web server and even announced that it would be bundled free of charge in future versions of Windows NT Server. The Microsoft Web server is more than just a Web server, as the name of the product suggests. Microsoft named the product the Internet Information Server (IIS) because it includes not only a Web server, but also a Gopher server and an FTP server. In releasing a Gopher and FTP server as well, Microsoft recognized that not all information on the Internet is stored on Web sites. However, the topic of this book is Web programming, so that's where I'll focus this chapter.

http://www.process.com

As is typical of Microsoft, a product is not complete until it has an API that programmers can use to extend or control the product. The same holds true for the IIS. You can extend the services available on your IIS-based Web site by using the IIS API (known as the Internet Server API, or ISAPI for the acronym-inclined). The ISAPI spec was written with the help of Process Software (http://www.process.com) and provides the interface between the IIS and the DLL extensions that you write. DLL is an acronym for Dynamic Link Library, a fact that is important to keep in mind for this discussion.

"That all sounds wonderful," you're probably thinking. "But I can't write a DLL with Visual Basic." So what's the point? Every time I think Microsoft has forgotten the VB programmer when introducing a new product, I'm proven incorrect. The ISAPI spec is no exception.

Microsoft provides several examples with the IIS that allow VB programs to run on the IIS. These examples, the ISAPI spec, and several other examples are also provided with the ActiveX Software Development Kit (SDK), which is currently available in beta form at the Microsoft Web site.

The first example is IS2WCGI which provides an interface between the IIS and existing CGI applications. This is useful when attempting to run the programs from previous chapters on your IIS. This example and its usage are discussed in Appendix C, "Win/CGI on the Microsoft Internet Information Server."

The second example is the OLEISAPI sample which is the topic of this chapter. OLEISAPI is an ISAPI-compliant DLL that activates OLE Automation servers. These servers can be written with any tool capable of creating OLE Automation servers. This includes, of course, Visual Basic 4.

The first half of this chapter provides an introduction to the ISAPI specification. The second half of the chapter provides a working example of an OLEISAPI application that you can modify to use on your own site. The example is a bare-bones online catalog browser and ordering site. It's far from being a complete system, but it does provide enough examples to allow you to take what's presented and use it on your own server.



This chapter will be of little use to any programmer who is not using an ISAPI-compliant Web server. If your Web server is not ISAPI-compliant, you may wish to skip this chapter.


The Advantages of OLEISAPI over Win/CGI


There are several advantages of using an ISAPI-compliant application instead of a CGI application. The most significant to the users of your Web site is performance. Although I haven't seen any benchmarks, personal experience has shown me that an ISAPI application (even the ones using the OLEISAPI interface described in this chapter) are at least twice as fast at returning their results than CGI applications. The reasons for this are discussed in this section.

The IIS is able to run ISAPI-compliant DLLs as extensions, meaning the DLL is loaded into the same address space that the server is using. This allows the DLL access to all the resources that are available to the IIS server. The DLL is also left in memory until the server decides it can be unloaded, meaning that minimal overhead is required the next time the ISAPI DLL is called. Both of these facts significantly improve the performance of Web-based applications.

A huge performance killer for CGI applications is the fact that the server must create a separate process for that application to run in. This is a time-consuming and RAM-consuming endeavor. The ISAPI interface overcomes this by using a DLL. The sever can then use the Win32 API to provide run-time dynamic linking to an ISAPI-compliant DLL. The server uses LoadLibrary() and GetProcAddress() to retrieve the starting address of specific functions in the DLL. The server uses these functions, or entry-points, as defined by the ISAPI specification which is discussed in the next section.

The VB Programmer's Introduction to the ISAPI Spec


Although a knowledge of C++ is helpful to fully understand the ISAPI specification, the information required to use the OLEISAPI sample to access VB-written OLE Automation servers is not that difficult. This section provides a brief introduction to how ISAPI-compliant DLLs are written. The second half of the section discusses the OLE methods you must expose in order too use the OLEISAPI DLL.

ISAPI Basics


Most of the information in this section is taken from "A Specification for Writing Internet Server Applications" which is available as ISAPI.HTM in the ActiveX SDK. I've simplified and reorganized the information for use in understanding the example provided in this chapter.

An ISAPI-complaint DLL is must have two specific entry points. These are functions that the IIS calls when using the DLL. The first entry point is GetExtensionVersion(). This function is called by the server when the DLL is loaded by the server (recall that the DLL may not be unloaded when it has finished, meaning that this function is not called every time a client requests the DLL, only when the DLL is loaded). The second function is HttpExtensionProc(), which servers as the ISAPI equivalent to Sub Main in a VB program.

The GetExtensionVersion() function provides the server with the version number of the ISAPI specification on which the DLL is based and a short textual description of the extension, such as "OLE Automation Gateway", which is the string returned by the OLEISAPI DLL. This function is called only once after the DLL is loaded.

The HttpExtensionProc() function is where all the interaction between the Web server and the DLL takes place. The server calls this function for each client request it receives. However, because the DLL is loaded only once, the server already has all the information it needs to call this function on each subsequent client request.

The server communicates with HttpExtensionProc() using a data structure called the EXTENSION_CONTROL_BLOCK (ECB). This structure contains information necessary to the DLL, including translated path information (that is, server-relative paths are translated to physical paths), the query string, and the request method. A pointer to an ECB is provided as a parameter to the function. The ISAPI specification provides the details of the ECB, which are beyond the scope of this introduction.

Although the ECB provides a wealth of information, not all the standard CGI variables are present. To provide the DLL with access to the remainder of the CGI data, the ECB provides a pointer to a function named GetServerVariable().GetServerVariable() retrieves information based on which server variable name is passed as its lpszVariableName parameter. The available variables include REMOTE_ADDR, REMOTE_HOST, SERVER_NAME, AUTH_TYPE, and a special variable named HTTP_ALL. Calling GetServerVariable() with HTTP_ALL returns all the HTTP request message headers that aren't provided by one of the other variables. This includes the common CGI variables User Agent and Accept.

Using the OLEISAPI DLL


If you feel overwhelmed by the previous section, don't worry—you don't need to completely understand the ISAPI spec to use the OLEISAPI DLL. This section describes the OLEISAPI object model and discusses how to code the HTML documents that make requests using the OLEISAPI DLL.

The OLEISAPI DLL, once compiled, should be placed in a directory that the IIS recognizes as an executable directory. I've placed mine in the /scripts server-relative directory (c:\inetsrv\scripts is the physical path). You'll reference this server-relative directory in any URLs that are used to access OLEISAPI-compliant objects.

The OLEISAPI Object Model

The OLEISAPI DLL is used to invoke OLE Automation objects. These objects are invoked using the object method specified in the URL that requests the resource (see the next section for details). The OLEISAPI DLL invokes these methods using the same parameters each time, regardless of the method being invoked. This section describes that object model.

OLE methods that are accessed using OLEISAPI should have the following definition in a VB class module:

Sub MethodToBeCalled(ptRequest as String, ptResponse as String)

where MethodToBeCalled is the name of the method, ptRequest is an input containing the request data, and ptResponse is where the object places the method's output.

The ptRequest parameter contains the request data. If the request is a GET request, such as a hyperlink, the ptRequest parameter contains the query string provided in the URL. If the request is a POST request, the parameter contains the form data encoded as

Field1=Field1_Date&Field2=Field2_Data ...

If any of the data entered on the form contains spaces, they are replaced with plus signs. Therefore, if Jim Smith were entered in one of the form fields, it would be passed to the method as "Jim+Smith". The OLE method must then convert this back to "Jim Smith".

The response output is placed by the method into ptResponse. The data placed in this parameter must begin with Content-Type: followed by a valid MIME type (typically text/html). The OLEISAPI DLL returns this data to the Web server, which in turn sends it on to the requesting client.

Addressing OLEISAPI-Compliant Objects in HTML

When coding links to an object using the OLEISAPI DLL, you provide the OLEISAPI DLL with the server name, object's class name, and the name of the method to invoke. A sample HTML document, default.htm, is included with the OLEISAPI sample. It contains examples of the three possible HTML addressing mechanisms for accessing the object.

If the link is a GET link (such as a hyperlink within a Web document), you provide the query string within the link. The link consists of a path to the OLEISAPI DLL, followed by the OLE PROGID for the object, and the name of the method to be invoked. The query string is then provided following a question mark that follows the method name. For example,

<A HREF="/scripts/oleisapi.dll/MyObject.MyClass.MyMethod?field1=hello>

begins a link that invokes the MyMethod method of an object class called MyClass, which is contained in an OLE object named MyObject. If any of the data contains spaces, they must be encoded as plus signs within the URL.

If the object is being invoked from an HTML form, the action element of the form tag should resemble

action="/scripts/oleisapi.dll/MyObject.MyClass.MyMethod"

The data provided in the ptRequest parameter is created using the data entered on the HTML form. Note that any information passed as a query string in the action element is not passed to MyMethod and is wasted effort. Instead of using the query string, simply create hidden fields on the HTML form to pass the data.

You'll see more examples of OLEISAPI HTML coding when this chapter's examples are discussed.

Creating an Online Catalog with OLEISAPI


Now that the basics of using the OLEISAPI server extension have been covered, it's time to put the information to use. This section presents the online catalog system for Widgets Unlimited, a fictitious company that manufactures, you guessed it, widgets.

Widgets Unlimited has a Web server running on the IIS and wants to provide its current and prospective customers with a means of browsing the company's current catalog. In addition, the company wants to be able to sign up new customers and allow the customers to order while browsing the catalog. The benefits of this system to Widgets Unlimited is that a marketing presence is available 24 hours a day that, once it's set up, requires no additional sales staff. The benefit to customers is that they have the latest information from the company available at all times. They can shop when it's convenient, not when the Widgets Unlimited phone banks are staffed.

Designing the Application


For this chapter, the Widgets Unlimited (WU) site is very basic. You'll find room for improvement as well as additional features. However, the purpose of this chapter is to introduce you to programming OLEISAPI-compliant applications using Visual Basic. Therefore, I won't spend a lot of time developing an elaborate order entry and customer information management site. This could certainly be done but is beyond the needs of this chapter.

The WU site consists of two static Web pages, catalog.htm and create.htm, which reside in the document root directory of WU's IIS Web server. The catalog.htm page, shown in Figure 10.1, serves as the front door or entry to the online catalog. It provides a link to create.htm (where customer records are created) and a link to the dynamic catalog browsing page. The form shown is for current customers to use as a login form. A form at the bottom of the page (not shown in Figure 10.1) helps current customers who have forgotten their password to request that the system send the password via e-mail.

Figure 10.1. The Online Catalog entry page.

The page where new customers can create a customer record is create.htm, shown in Figure 10.2. This page contains a form for the user to enter the information necessary to obtain a User Name. After someone has a User Name and password, he or she can place orders online while browsing the catalog. The only required fields on the form are User Name, e-mail address, and the password fields. However, the mailing address would be necessary before ordering anything. The user is required to enter the chosen password twice, a common practice to ensure that the user has typed the password correctly.

Figure 10.2. The create.htm user creation page.

The Browse The Catalog link on the entry page takes the user to a dynamically generated listing of the current catalog contents. The link summons the OLEISAPI DLL and instructs it to invoke a method in the CATALOG OLE Automation object created later in this chapter. This OLE method produces an output page similar to Figure 10.3. The page is produced by querying the WU database's Products table for the current product list. The Products table provides product name, pricing, and even a server-relative path to a picture of the product. The picture is displayed along side the product information.

Figure 10.3. A sample product listing page.

If users are current customers of WU, they can enter their user name and password in the boxes provided on the entry page. Clicking the Log-On button launches OLEISAPI.DLL and invokes another OLE method contained in the CATALOG object. This method verifies the entered user name and password and, if they're valid, produces the page shown in Figure 10.4. This page provides a link to the online catalog just described, as well as a link to a page where users can update their customer information (this page is not implemented in this chapter). The online catalog that the current user browses (see Figure 10.5) contains an Order link for each product. Clicking the Order link causes the system to place an order for that product for the current user.

Figure 10.4. The page for current customers.

Figure 10.5. The online catalog page for current customers.

Designing the Database


The database that powers the WU site as presented in this chapter is not very complex. It consists of only two tables: Customers and Products. The Customers table stores the customer information for each user who has created a user name using the form found on create.htm. The Products table is maintained by the WU Marketing department and consists of product information as well as a link to a picture of the product.



The application presented in this chapter uses Microsoft Access as the database engine. This was done only for convenience. In your applications you can use any database platform that the server machine has access to. I've also hard-coded a path to the database. You may wish to use ODBC to provide path-independence to your application.

You can use any tool you are comfortable with to create the database. I used Microsoft Access 95, but the Data Manager add-in application that ships with VB4 will work just as well for this simple database. Create the database and save it in any directory accessible to applications executing on your Web server machine.

Tables 10.1 and 10.2 list the fields in the two tables. All the text fields should allow zero-length strings for convenience Otherwise, you'd have to check each field from the HTML form to make sure it contains data. If it didn't contain data, you'd have to assign a string such as " " to the database fields. It's much simpler to just allow zero-length strings. Then you can simply assign the database field the value in the HTML form field.

Table 10.1. The Customers table.

Field Name

Data Type

Size

CustomerID

AutoNumber

Long integer

FirstName

Text

20

LastName

Text

20

Address

Text

50

City

Text

30

State

Text

20

ZIP

Text

10

Country

Text

20

EMail

Text

50

Password

Text

8

Phone

Text

20

UserName

Text

10


The Customers table contains one field for each field shown in Figure 10.2 plus a CustomerID field. CustomerID is an AutoNumber field and serves as the primary key for the table. If the application were expanded to include order entry or a shopping cart system, the CustomerID would serve as a foreign key in the additional tables required to implement such systems. The UserName and Password fields are filled by users when they return to the site and wish to browse the catalog and place orders. The UserName must be unique and is not case-sensitive.

Table 10.2. The Products table.

Field Name

Data Type

Size/Default Value

ProductID

AutoNumber

Long integer

ProductName

Text

50

PictureURL

Text

50

Description

Memo


Cost

Currency

0 (default)

Shipping

Currency

0 (default)


The Products table is where information about the various WU products is kept. The ProductID AutoNumber field is the table's primary key. The PictureURL field is used to store the URL that points to a picture of the product. This URL can be either an absolute path (such as http://www.widgets.com/images/product1.jpg) or a relative path (such as /images/product1.jpg). If a relative path is used, make sure it is one that will be correctly translated by the Web server. You can check this by appending the relative path to your server root directory's absolute URL and entering the resulting URL into a Web browser's URL input area. It is recommended that you use relative paths whenever possible. That way, if your server's root URL changes, you won't have to update every path in your site to match the new root URL.

Designing the HTML Pages


The two HTML pages, catalog.htm and create.htm, are simple HTML pages. You can use a text editor or any of the readily available HTML editors to create these pages.

The HTML for the two pages is provided in Listing 10.1 and Listing 10.2. The HTML files are also provided in the same directory as the sample code on the CD-ROM that accompanies the book. Create the two pages and save them in the document root directory of you Web server. After they're created, load them using a Web browser to make sure they match Figures 10.1 and 10.2.

The code for this chapter, including these two HTML files, a sample database, and sample picture files, is in a file named CODE-11.ZIP that was submitted with this document.

Listing 10.1. The HTML for catalog.htm.

<html>
<head><title>Online Catalog</title></head>
<body>
<h1 align=center>Widgets Unlimited Catalog</h1>
<h2 align=left>Welcome to the On-Line catalog for Widgets Unlimited. 
 Here you'll find the complete Widgets product line, be able 
to set yourself up as a customer, browse the catalog, and even 
order On-Line!</h2>
<h2 align=left>So, Let's Get Started:</h2>
<blockquote>
<h3 align=left><a href="/create.htm">Create Your Customer Record</a></h3>
<h3 align=left><a href="/scripts/oleisapi.dll/CATALOG.Browse.MakePage">
Browse the Catalog</a></h3>
<form action="/scripts/oleisapi.dll/CATALOG.Logon.InitialLogon" method="POST">
<h3>If you already have a User Name, enter it and your password here:</h3>
<h3><input type=text size=20 maxlength=256 name="UserName"> </h3>
<h3><input type=password size=20 maxlength=256 name="Password"></h3>
<h3 align=left><input type=submit value="Log-On"> 
<input type=reset value="Reset"></h3></form>
</blockquote>
<hr>
<blockquote>
<form action="/scripts/oleisapi.dll/CATALOG.Logon.ForgotPWD" method="POST">
<h3>If you forgot your password, enter your User Name here 
and we'll e-mail your password to you:</h3>
<p><input type=text size=20 maxlength=256 name="UserName"></p>
<p><input type=submit value="Submit"> <input type=reset value="Reset"></p>
</form>
</blockquote>
</body></html>

Listing 10.2. The HTML for create.htm.

<html><head>
<title>Create Your Customer Record</title></head>
<body>
<h1 align=center>Create Your User Name</h1>
<h3 align=left>To create a User Name, you must enter some 
information about yourself<br>(fields in BOLD are required):</h3>
<form action="/scripts/oleisapi.dll/CATALOG.Logon.Create" method="POST">
<pre><strong>User Name</strong>:        <input type=text size=20 
maxlength=10 name="UserName"> (10 chars. max)
First Name:       <input type=text size=20 maxlength=20 name="FirstName">
Last Name:        <input type=text size=20 maxlength=20 name="Lastname">
Address:          <input type=text size=30 maxlength=50 name="Address">
City, State, ZIP: <input type=text size=20 maxlength=30 name="City">
<input type=text size=9 maxlength=20 name="State"><input type=text 
size=10 maxlength=10 name="ZIP">
Country:          <input type=text size=20 maxlength=20 name="Country">
Phone:            <input type=text size=20 maxlength=20 name="Phone">
<strong>E-Mail</strong>:           <input type=text size=20 
maxlength=50 name="EMail">
<strong>Password</strong>:         <input type=password size=20 
maxlength=8 name="Password">(8 chars. max)
<strong>Verify Password</strong>:  <input type=password size=20 
maxlength=8 name="Verify"></pre>
<div align=left>
<pre>      <input type=submit value="Submit">
         <input type=reset value="Reset"></pre>
</div>
</form>
<h3>Your User Name must be unique. If you receive a response 
saying that the User Name you chose was not unique, use the 
Back feature of your browser to return to this page and try again.</h3>
</body></html>

Coding the Application


All the objects that are used in the Widgets Unlimited OLEISAPI site are contained within a single OLE DLL. This DLL is named CATALOG.DLL. This will also be the name of the VB project (CATALOG.VBP) which creates the DLL. The DLL contains two object classes: LOGON, which contains the user-related OLE methods, and Browse, which contains the catalog-related OLE methods.

For each object class contained within a VB OLE object, there must be a class module in the VB project. The filename of the class module is irrelevant, but the module name assigned on the module's property sheet must match the class name you wish to use.

The easiest location to create and compile the object is on the machine on which your IIS is running. If you compile the object on another machine, you must ensure that it is set up properly on the server machine. This includes copying to the server machine all the Visual Basic DLLs needed to run the object and registering the object on the server machine using REGSVR32.EXE. If you run VB on the server machine, all these details are taken care of automatically.

To get started, run Visual Basic and create a new project. You should remove the default form from the project because there won't be a user-interface component to the OLE DLL. Also, remove all the custom controls because they won't be needed either. The References dialog should contain a reference to the Microsoft DAO 3.0 Object Library.

Next, add two class modules using Insert | Class Module. When the module appears, press F4 to open the Properties window for the module. One module should have its Name property set to Browse. The other should be set to LOGON (the all-caps is not significant, it's just a personal preference I have when dealing with logon objects). Both modules should be Public objects that have an Instancing property of Creatable MultiUse. The Properties window, with the exception of the Name property, should match Figure 10.6. Save these files as BROWSE.CLS and LOGON.CLS.

Figure 10.6. The property sheet for an OLEISAPI object's class module.

After the two class modules have been added to the project, add the DATABASE.BAS module created in Chapter 8, "Database Connectivity: The WebGuest Application", and the FUNCS.BAS module created in Chapter 7, "Creating CGI Applications in Visual Basic". These files are also included in this chapter's directory on the CD-ROM.

After the two class modules have been added to the project, add the DATABASE.BAS module created in Chapter 8, "Database Connectivity: The WebGuest Application", and the FUNCS.BAS module created in Chapter 7, "Creating CGI Applications in Visual Basic". These files are also included in this chapter's directory on the CD-ROM

Next, add a new module using Insert | Module. Save this module as GENERAL.BAS. It will be used to hold some routines that will be specific to the OLE object created in this chapter.

Add another new module and save it as OLEISAPI.BAS. This module will house some routines that can be used in any OLEISAPI objects you may write in the future. It provides functions similar to some of those found in CGI32.BAS that was developed in Chapter 7.

After all the modules have been set up, it's time to specify that this project will be used to create an OLE object. Open the project's properties dialog using Tools | Options. Select the Project tab. Set the Project Name to Catalog. In the Start Mode frame, select OLE Server. Then, enter some text in the Application Description such as Catalog Object for OLEISAPI. Leave the Compatible OLE Server textbox empty. The dialog should resemble Figure 10.7.

Figure 10.7. The Project's property sheet.

Finally, save the project as CATALOG.VBP.

If you've started from scratch as opposed to loading the project from the CD-ROM, you're now ready to enter the code. The following sections provide the listings and a short description for each module. Most of the code is far from rocket science and won't need much of an explanation. The purpose of this chapter is to demonstrate the building of an OLE object that is invoked from the OLEISAPI DLL. This is accomplished in the methods contained in the Browse and LOGON class modules.

The OLEISAPI.BAS Module

The code for the OLEISAPI.BAS module is provided in Listing 10.3. It consists of three functions, which are used to retrieve information from the query string that is provided as the ptRequest parameter to an OLEISAPI-compliant OLE object method.

The declarations section defines the ParameterType user-defined type and a global array named Parameters() that will be used to store the field values passed in the query string. Recall that the query string is formatted as

Field1=Value1&Field2=Value2&Field3=Value3 ...

where each field/value pair is separated by an ampersand, and the value is separated from the field name by an equal sign.

A field may be passed on the query string with an empty value even if no data has been entered on the HTML form. This will be passed as Field1=&Field2=Value2... where Field1 has no text entered on the form. Also, checkboxes on an HTML form are passed in this manner if they are checked. If the checkbox is not checked, the field name is not passed to the application.

The global variable ParameterCount contains the number of fields parsed from the query string.

The first procedure, ParseQuery, takes the delimited query string and places the field/value pairs into the Parameters() array. The Key element contains the field's name. The Value element contains the value assigned to the field.

The first step is to replace any plus signs found in the string with spaces (recall that any spaces contained in the query string are encoded using a plus sign). The next step is to call the ParseString() function contained in FUNCS.BAS to retrieve each field/value pair. The ParseString() function takes a delimited string and parses it into an array containing one element for each delimited occurrence in the string. Once that is accomplished, the ParameterCount variable is set to the upper bound of the array that ParseString() just loaded.

Finally, the array is iterated through and used to fill the Parameters() global array with the Key and Value elements taken from the parsed array.

The two functions found in the module, ParamPresent() and GetParam(), are used to determine whether a field specified is in the query string and to retrieve the value of a specific field, respectively.

The Sub Main procedure is present to satisfy VB's need for such a routine. It must be left empty or the object does not function properly when invoked by the OLEISAPI DLL. However, while you are debugging the project, this is where you should place any test code. For example, you may wish to invoke one of the OLE methods to see what value is returned. Simply declare an new object variable typed as the name of the object you're testing (LOGON or Browse), and invoke the method you're testing:

Dim objLogon as New LOGON
objLogon.InitialLogin "UserName=Foo&Password=Bar", ltReturnVAlue$
Debug.Print ltReturnValue$

This creates a new LOGON object and invokes the InitialLogon method. The ptResult method parameter is placed into ltReturnValue$ and printed to the VB Debug window. Remember, when you're ready to compile your object, remove all the code from Sub Main.

Listing 10.3. The OLEISAPI.BAS code.

Type ParameterType
    Key As String
    Value As String
End Type
Global Parameters() As ParameterType
Global ParameterCount As Integer
Public Sub ParseQuery(ptQuery As String)
    Dim i%, nPos%
    Dim ltTemp      As String
    Dim lParamArray() As String
    
    'replace the plusses with spaces
    ltTemp = tReplaceChars(ptQuery, "+", " ")
    ParseString lParamArray, ltTemp, "&"
    ParameterCount = UBound(lParamArray)
    ReDim Parameters(ParameterCount)
    
    For i% = 1 To ParameterCount
        ltTemp = lParamArray(i%)
        nPos = InStr(ltTemp, "=")
        If nPos Then
            Parameters(i%).Key = Left$(ltTemp, nPos - 1)
            Parameters(i%).Value = Mid$(ltTemp, nPos + 1)
        End If
    Next i%
    
End Sub
Public Function GetParam(ByVal ptParam As String) As String
    ptParam = UCase$(ptParam)
    GetParam = ""
    
    For i% = 1 To ParameterCount
        If UCase$(Parameters(i%).Key) = ptParam Then
            GetParam = Parameters(i%).Value
            Exit Function
        End If
    Next
    
End Function
Public Function ParamPresent(ByVal ptParam As String) As Integer
    
    ptParam = UCase$(ptParam)
    ParamPresent = 0
    
    For i% = 1 To ParameterCount
        If UCase$(Parameters(i%).Key) = ptParam Then
            ParamPresent = i%
            Exit Function
        End If
    Next
    
End Function
Public Sub Main()
End Sub

The GENERAL.BAS Module

The module GENERAL.BAS contains code that is specific to the Catalog object. It contains functions used for validating form data and for producing an error report page. It also contains a standard routine for opening the database, DBOpen, which is called whenever a method needs to access the database. The code is presented in Listing 10.4.

The declarations section declares a global database variable. A global is used because the same database will be used for all the routines contained within the object, particularly the routines found in GENERAL.BAS. A constant is used for the database path and the server-relative path to OLEISAPI.DLL so that you have to modify the values to match your server setup in only one place.

The function ValidateUserName() first checks to make sure a non-empty user name has been entered by the user. If a user name is not entered, an error string is returned. If a user name has been entered, the value is checked against the Customers table to see if such a user name exists. If it does not exist (note that is not a case-sensitive search), an error message is returned. The error message contains links back to the home page and also to the create.htm page.

The function ValidatePWD() checks for the existence of the password field. If it's present, the function compares the value with the value stored in the database for the UserName provided. If the two passwords don't match, an error string is returned. The error string contains a link to the ForgotPWD method of the LOGON object. This method (though not fully implemented in this chapter) is used to e-mail a copy of the correct password to the e-mail address the user specified when creating the account.

The function ValidateCustomerID() is used to validate a CustomerID. After the user logs into the system through the InitialLogon method, the resulting HTML pages reference the customer's ID instead of the user name. This simplifies matters when the system is expanded and the Customers table is related to other tables through the CustomerID field.

The ProduceError() function is used by all the OLE methods to produce a standard error page should a runtime error occur. The procedure takes two parameters: the class name of the object (pClass$) and the method in which the error occurred (pMethod$). The function returns the complete HTML contents that are to be assigned to the OLE method's response parameter.

Listing 10.4. The code for GENERAL.BAS.

'database variable and path
Global db As Database
Global Const DB_PATH = "c:\inetsrv\scripts\catalog.mdb"
'server-relative path to oleisapi.dll:
Global Const OLEISAPI_PATH = "/scripts/OLEISAPI.dll"
Public Function ValidateUsername(pHTML$, db As Database) As Integer
    ValidateUsername = False
    
    If ParamPresent("UserName") = 0 Or Len(GetParam("UserName")) = 0 Then
        pHTML$ = pHTML$ & "Invalid Login</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You did not enter a User Name</H1>"
        pHTML$ = pHTML$ & "<H3>Your User Name is required for logon</H3>"
        pHTML$ = pHTML$ & "<HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    
    lsql$ = "Select Count(*) from Customers where UserName= '"
    lsql$ = lsql$ & GetParam("UserName") & "'"
    If Val(tExecAndAnswer(lsql$, db, lStatus%)) = 0 Then
        pHTML$ = pHTML$ & "Invalid Login</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You Entered an invalid User Name</H1>"
        pHTML$ = pHTML$ & "<A HREF=""/create.htm"">Create One</a><p>"
        pHTML$ = pHTML$ & "<H3>The User Name was not found in the database</H3>"
        pHTML$ = pHTML$ & "<HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    ValidateUsername = True
    
End Function
Public Function ValidatePWD(pHTML$, db As Database) As Integer
    
    ValidatePWD = False
    
    If ParamPresent("Password") = 0 Or Len(GetParam("Password")) = 0 Then
        pHTML$ = pHTML$ & "Invalid Login</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You did not enter a Password</H1>"
        pHTML$ = pHTML$ & "<H3>Your Password is required for logon</H3>"
        pHTML$ = pHTML$ & "<HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    lsql$ = "Select Password from Customers where Username = '"
    lsql$ = lsql$ & GetParam("UserName") & "'"
    If UCase$(tExecAndAnswer(lsql$, db, lStatus%)) <> _
       UCase$(GetParam("Password")) Then
        pHTML$ = pHTML$ & "Invalid Password</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You Entered an Invalid Password</H1>"
        pHTML$ = pHTML$ & "<H3>The Password you entered was "        
        pHTML$ = pHTML$ & " incorrect.</H3>"
        pHTML$ = pHTML$ & "If you've forgotten your password, "
        pHTML$ = pHTML$ & "<A HREF=""" & OLEISAPI_PATH 
        pHTML$ = pHTML$ & "/CATALOG.Logon.ForgotPWD"">"
        pHTML$ = pHTML$ & "click here</A> and we'll e-mail it to you."
        pHTML$ = pHTML$ & "<HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    
    ValidatePWD = True
    
End Function
Public Function ValidateCustomerID(pHTML$, db As Database)
    ValidateCustomerID = False
    
    If ParamPresent("CustomerID") = 0 Or Len(GetParam("CustomerID")) = 0 Then
        pHTML$ = pHTML$ & "Invalid Login</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You did not enter a Customer ID</H1>"
        pHTML$ = pHTML$ & "<H3>Your Customer ID is required for logon</H3>"
        pHTML$ = pHTML$ & "<HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    
    lsql$ = "Select Count(*) from Customers where CustomerID= "
    lsql$ = lsql$ & GetParam("CustomerID")
    If Val(tExecAndAnswer(lsql$, db, lStatus%)) = 0 Then
        pHTML$ = pHTML$ & "Invalid Login</title></head><body>"
        pHTML$ = pHTML$ & "<H1>You Entered an Invalid Customer ID</H1>"
        pHTML$ = pHTML$ & "<H3>The Customer ID was not found in the "
        pHTML$ = pHTML$ & " database</H3><HR ALIGN=CENTER>Return to "
        pHTML$ = pHTML$ & "<A HREF=""/catalog.htm"">Home Page</a>"
        pHTML$ = pHTML$ & "</body></HTML>"
        Exit Function
    End If
    ValidateCustomerID = True  
End Function
Public Sub DBOpen()
    Set db = OpenDatabase(DB_PATH)
    
End Sub
Public Function ProduceError(pClass$, pMethod$)
    Dim lHTML$
    
    lHTML$ = "Content-Type: text/html" & vbCrLf & vbCrLf
    lHTML$ = lHTML$ & "<html><head><title>Error Occurred</title>"
    lHTML$ = lHTML$ & "</head><body><H1>An Error Occurred During 
    lHTML$ = lHTML$ & Login</H1>" & vbCrLf
    lHTML$ = lHTML$ & "Class: " & pClass$ & "<br>Method: "
    lHTML$ = lHTML$ & pMethod$ & "<p>Error Code: " & Err & "<br>"
    lHTML$ = lHTML$ & "Description: " & Error$ & "<p>"
    lHTML$ = lHTML$ & "Request: " & request & "<p>"
    lHTML$ = lHTML$ & "Please report this information to the "
    lHTML$ = lHTML$ & "system administrator</body></html>" 
    ProduceError = lHTML$
End Function

The LOGON Class Module Code

The LOGON class module presented in Listing 10.5 contains all the code related to the Customers table in the database. The LOGON class provides four methods that may be called: InitialLogon, ForgotPWD, Maintain, and Create. All four are defined with the same two parameters, request as string, response as string, so that they may be called by the OLEISAPI DLL. This DLL requires that these two, and only these two, parameters be present in any methods that are accessed using OLEISAPI.

The ForgotPWD and Maintain methods don't really do anything except produce the same output page every time they're invoked. In a complete system, these methods would be used to help maintain the Customers table.

The InitialLogon method is the method that is invoked when the user clicks the Log-On button on the entry page. After the beginning of the HTML output starts, the method replaces any carriage returns or line feed characters that may be in the query string with empty strings. Each field of form data on the Widgets Unlimited site is one line, so these characters are invalid anyway. The OLEISAPI DLL always seemed to pass the query string to the method with a CR/LF at the end. This caused validation functions to fail, so the method strips those characters out of the query string. The method then calls the ParseQuery procedure, opens the database, and validates the form data. If all the validation passes, the remainder of the output page is produced. For an existing customer, the output page (shown in Figure 10.4) contains links to browse the catalog or maintain customer information. After the HTML for the output page is produced, it is assigned to the response parameter, the database is closed, and the method exits.

If a runtime error occurs, it is trapped, and execution continues with the label InitialLogonErr:. The procedure assigns the response parameter the value of the ProduceError() function when called with "Logon" and "InitialLogon" as its parameters. The method then exits. Errors are handled in an identical way within each method in the Catalog object.

The Create method starts off in a manner similar to InitialLogon. After the database is opened and the form data validated, a recordset is created to hold the new customer record. The fields of the Customers table directly parallel those contained on the HTML form found in create.htm, with the exception of CustomerID. Because CustomerID is an AutoNumber field, it is assigned a value by the Jet Engine when the new record is added to the database using the Jet Update method.

After a new record is updated, the record pointer no longer points to the new record. However, a bookmark to the new record is contained in the LastModified property of the recordset. The method assigns this property to the recordset's Bookmark property to move the record pointer back to the newly added record. This occurs so the method can retrieve the assigned CustomerID value.

The CustomerID for the new record is used when producing the method's output page. The output page consists of the same links as the page produced by InitialLogon—a link to browse the catalog as a customer and a link to maintain the customer record.

Listing 10.5. The code for LOGON.CLS.

Public Sub InitialLogon(request As String, response As String)
    
    On Error GoTo InitialLogonErr
    
    Dim lHTML$      'local variable to hold the HTML
    Dim lsql$
    Dim lTxt$
    
    lHTML$ = "Content-Type: text/html" & vbCrLf & vbCrLf
    lHTML$ = lHTML$ & "<html><head><title>"
    
    'parse the parameters
    ' replace any CrLf characters w/ empty spaces (everything is one line)
    lTxt$ = tReplaceChars(request, Chr$(13), "")
    lTxt$ = tReplaceChars(lTxt$, Chr$(10), "")
    ParseQuery lTxt$
    
    'open the database
    DBOpen
    
    'validate the form data
    If (ValidateUsername(lHTML$, db) = False) Then
        response = lHTML$
        db.Close
        Exit Sub
    End If
    If (ValidatePWD(lHTML$, db) = False) Then
        response = lHTML$
        db.Close
        Exit Sub
    End If
    
    'get the UserName field       
    UserName$ = GetParam("Username")
    
    'Produce the output page:
    'get some customer information
    Dim snap As Recordset
    lsql$ = "Select * from Customers where UserName = '" & UserName$ & "'"
    Set snap = db.OpenRecordset(lsql$, dbOpenSnapshot)
    CustID$ = snap("CustomerID")
    lName$ = chknull(snap("FirstName"), "Widgets Customer")
    lHTML$ = lHTML$ & "Welcome, " & snap("FirstName") & ", "
    snap.Close
    
    lHTML$ = lHTML$ & "to Widgets Unlimited</title></head><body>" & vbCrLf
    lHTML$ = lHTML$ & "<CENTER><H2>Welcome Back, " & lName$ & "</H2></CENTER>"
    lHTML$ = lHTML$ & "<H3>Choose an activity:</H3>" & vbCrLf
    lHTML$ = lHTML$ & "<blockquote>" & vbCrLf
    'link to browse the catalog as a customer
    lHTML$ = lHTML$ & "<a HREF=""" & OLEISAPI_PATH & "/CATALOG.browse.MakePage?"
    lHTML$ = lHTML$ & "CustomerID=" & CustID$ & """>Browse</a> "
    lHTML$ = lHTML$ & "the Catalog<p>"
    
    'link to the (unsupported) customer record maintenance page   
    lHTML$ = lHTML$ & "<a HREF=""" & OLEISAPI_PATH & "/CATALOG.Logon.maintain?"
    lHTML$ = lHTML$ & "CustomerID=" & CustID$ & """>Update</a> "
    lHTML$ = lHTML$ & "your customer account information<p>"
    
    response = lHTML$
    db.Close
    Exit Sub
    
InitialLogonErr:
    response = ProduceError("Logon", "InitialLogon")
    Exit Sub
    
End Sub
Public Sub ForgotPWD(request As String, response As String)
    'customer forgot password, e-mail it to them
    response = "<html><head><title>Request Acknowledged</title></head><body>"
    response = "<H2>Your password will be sent to the e-mail "
    response = response & " address on file.</H2></body></html>"
    
End Sub
Public Sub Maintain(request As String, response As String)
    'not supported, just produce some output:
    response = "<html><head><title>Not Yet Supported</title></head><body>"
    response = response & "<H1>Customer Account Maintenance not yet "
    response = response & " supported</H1></body></html>"
    
End Sub
Public Sub Create(request As String, response As String)
    
    On Error GoTo CreateErr
    
    Dim lHTML$      'local variable to hold the HTML
    Dim lsql$
    Dim lTxt$
    
    lHTML$ = "Content-Type: text/html" & vbCrLf & vbCrLf
    lHTML$ = lHTML$ & "<html><head><title>"
    
    'parse the parameters
    ' replace any CrLf characters w/ empty spaces (everything is one line)
    lTxt$ = tReplaceChars(request, Chr$(13), "")
    lTxt$ = tReplaceChars(lTxt$, Chr$(10), "")
    ParseQuery lTxt$
    
    'validate required fields:
    If ParamPresent("UserName") = 0 Or ParamPresent("Email") = 0 Or _
      ParamPresent("Password") = 0 Or ParamPresent("Verify") = 0 Or _
      Len(GetParam("UserName")) = 0 Or Len(GetParam("Email")) = 0 Or _
      Len(GetParam("Password")) = 0 Or Len(GetParam("Verify")) = 0 Then
        lHTML$ = lHTML$ & "Validation Error</title></head><body>"
        lHTML$ = lHTML$ & "<H2>User Name, E-Mail, Password, and Verify "
        lHTML$ = lHTML$ & "fields must have data</H2>"
        lHTML$ = lHTML$ & "</body></html>"
        response = lHTML$
        Exit Sub
    End If
    
    'open the database
    DBOpen
    
    'validate uniqueness of UserName
    lsql$ = "Select count(*) from Customers where UserName = '" 
    lsql$ = lsql$ & GetParam("UserName") & "'"
    If Val(tExecAndAnswer(lsql$, db, lStatus%)) Then
        lHTML$ = lHTML$ & "Validation Error</title></head><body>"
        lHTML$ = lHTML$ & "<H2>The User Name you chose has been taken.</H2>"
        lHTML$ = lHTML$ & "</body></html>"
        response = lHTML$
        Exit Sub
    End If
    
    'validate password
    If UCase$(GetParam("Password")) <> UCase$(GetParam("Verify")) Then
        lHTML$ = lHTML$ & "Validation Error</title></head><body>"
        lHTML$ = lHTML$ & "<H2>Passwords Did Not Match!</H2>"
        lHTML$ = lHTML$ & "</body></html>"
        response = lHTML$
        Exit Sub
    End If
        
    'create a recordset to hold the new customer record
    Dim ds As Recordset
    Set ds = db.OpenRecordset("Customers", dbOpenDynaset)
    'add the new record
    ds.AddNew
    'set the field data from the HTML form data
    ds!UserName = GetParam("UserName")
    ds!FirstName = GetParam("FirstName")
    ds!LastName = GetParam("LastName")
    ds!Address = GetParam("Address")
    ds!City = GetParam("City")
    ds!State = GetParam("State")
    ds!ZIP = GetParam("Zip")
    ds!Phone = GetParam("Phone")
    ds!EMail = GetParam("EMail")
    ds!Password = GetParam("Password")
    'update the reocrd
    ds.Update
    'move the record pointer BACK to the record just added
    ds.Bookmark = ds.LastModified
    'get the CustoemrID AutoNumber field
    CustID$ = Trim$(Str$(ds!CustomerID))
    
    'produce the HTML output page
    lHTML$ = lHTML$ & "Welcome Aboard!</title></head><body>"
    lHTML$ = lHTML$ & "<H1>User Name Created!</H1><p>"
    lHTML$ = lHTML$ & "<H3>Choose an activity:</H3>" & vbCrLf
    lHTML$ = lHTML$ & "<blockquote>" & vbCrLf
    'link to browse the catalog as a customer
    lHTML$ = lHTML$ & "<a HREF=""" & OLEISAPI_PATH & "/CATALOG.browse.MakePage?"
    lHTML$ = lHTML$ & "CustomerID=" & CustID$ & """>Browse</a> "
    lHTML$ = lHTML$ & "the Catalog<p>"       
    'link to the (unsupported) customer record maintenance page
    lHTML$ = lHTML$ & "<a HREF=""" & OLEISAPI_PATH & "/CATALOG.Logon.maintain?"
    lHTML$ = lHTML$ & "CustomerID=" & CustID$ & """>Update</a> "
    lHTML$ = lHTML$ & "your customer account information<p>"
    
    'finish up and exit
    response = lHTML$ & "</body></html>"
    db.Close
    Exit Sub
    
CreateErr:
    response = ProduceError("Logon", "Create")
    Exit Sub
    
End Sub

The Browse Object's Code Module

The Browse object provides only two methods: MakePage and Order.

The MakePage method produces the catalog listing pages shown in Figures 10.3 and 10.5. If the method is passed a CustomerID field in the query string, the customer catalog page of Figure 10.5 is produced. Otherwise, the standard catalog page of Figure 10.3 is produced.

The method uses the same startup code as the methods discussed previously. After the database is opened, the code checks to see if a CustomerID field is present in the query string. The CustomerID field is present in the query string if a valid customer has requested to view the catalog. If CustomerID is present, it is validated using ValidateCustomerID().

The Products table is then loaded into a recordset variable. If any records are in the table (it would be a pretty dull catalog without any), they are displayed within an HTML <TABLE> tag. Each record is checked to see whether a picture is specified. If so, an <IMG> tag is used to display the specified picture within the table cell for the current record. The product name, cost, and shipping charge are also displayed within the table cell. If the CustomerID field has been found, a link is placed in each table cell to allow the customer to order the specific product. The link contains a query string that specifies the customer ID and the current ProductID field from the Products table. The link invokes the Order method, which is discussed next.

After all the records in the Products table have been added to the HTML table, the method finishes up the HTML coding, closes the database, and exits.

The Order method is not fully implemented in this example. It merely produces an output page informing users that the widget they ordered is in the mail. It expects the ProductID and CustomerID fields to be provided in the query string. These are used to obtain the product name of the product ordered and the ordering customer's name.

Listing 10.6. The code for BROWSE.CLS.

Public Sub MakePage(request As String, response As String)
    
    On Error GoTo MakePageErr
    
    Dim lHTML$      'local variable to hold the HTML
    Dim lsql$
    Dim lTxt$
    
    lHTML$ = "Content-Type: text/html" & vbCrLf & vbCrLf
    lHTML$ = lHTML$ & "<html><head><title>"
    
    'parse the parameters
    ' replace any CrLf characters w/ empty spaces (everything is one line)
    lTxt$ = tReplaceChars(request, Chr$(13), "")
    lTxt$ = tReplaceChars(lTxt$, Chr$(10), "")
    ParseQuery lTxt$
       
    'open the database
    DBOpen
    
    'get the customer ID if provided
    CustID$ = ""
    If ParamPresent("CustomerID") And Len(GetParam("CustomerID")) Then
        CustID$ = GetParam("CustomerID")
        'validate the customer ID
        If ValidateCustomerID(lHTML$, db) = False Then
            response = lHTML$
            db.Close
            Exit Sub
        End If
    End If
    
    lHTML$ = lHTML$ & "Catalog Contents</title></head><body>"
    lHTML$ = lHTML$ & "<H1>Catalog Contents</H1><CENTER>"
    
    'open the Products table
    Dim snap As Recordset
    Set snap = db.OpenRecordset("Products", dbOpenSnapshot)
    If snap.RecordCount Then
        'format as a table
        lHTML$ = lHTML$ & "<table border = 2>"
        While Not snap.EOF
            'start a new row
            lHTML$ = lHTML$ & "<tr><td>"
            'if there's a picture, display it
            lPicture$ = chknull(snap!PictureURL, "")
            If Len(lPicture$) Then
                lHTML$ = lHTML$ & "<img src=""" & lPicture$ & """ align=left>"
            End If
            lHTML$ = lHTML$ & snap!ProductName & "<br>"
            lHTML$ = lHTML$ & "Cost: " & Format$(snap!Cost, "$ 0.00") & "<br>"
            lHTML$ = lHTML$ & "Shipping: " & Format$(snap!Shipping, "$ 0.00")
            'is this for a customer?
            If Len(CustID$) Then
                'yes, provide an Order link
                lHTML$ = lHTML$ & "<br><a href=""" & OLEISAPI_PATH
                lHTML$ = lHTML$ & "/CATALOG.Browse.Order?ProductID="
                lHTML$ = lHTML$ & snap!ProductID
                lHTML$ = lHTML$ & "&CustomerID=" & CustID$
                lHTML$ = lHTML$ & """>Order Item</a>"
            End If
            lHTML$ = lHTML$ & "</td></tr>"
            snap.MoveNext
        Wend
        lHTML$ = lHTML$ & "</table>"
    Else
        lHTML$ = lHTML$ & "<H2>Catalog Database is Empty!</H2>"
    End If
    lHTML$ = lHTML$ & "</center></body></html>"
    response = lHTML$
    Exit Sub
    
MakePageErr:
    response = ProduceError("Browse", "MakePage")
    Exit Sub
    
End Sub
Public Sub Order(request As String, response As String)
    On Error GoTo OrderErr
    
    Dim lHTML$      'local variable to hold the HTML
    Dim lsql$
    Dim lTxt$
    
    lHTML$ = "Content-Type: text/html" & vbCrLf & vbCrLf
    lHTML$ = lHTML$ & "<html><head><title>"
    
    'parse the parameters
    ' replace any CrLf characters w/ empty spaces (everything is one line)
    lTxt$ = tReplaceChars(request, Chr$(13), "")
    lTxt$ = tReplaceChars(lTxt$, Chr$(10), "")
    ParseQuery lTxt$
       
    'open the database
    DBOpen
    
    'get the user's name
    lsql$ = "Select FirstName & ' ' & LastName from Customers where "
    lsql$ = lsql$ & "CustomerID = " & GetParam("CustomerID")
    lCustName$ = tExecAndAnswer(lsql$, db, lStatus%)
    
    'get the name of the product they ordered
    lsql$ = "Select ProductName from Products where ProductID = "
    lsql$ = lsql$ & GetParam("productID")
    lProductName$ = tExecAndAnswer(lsql$, db, lStatus%)
    
    lHTML$ = lHTML$ & "Thanks!</title></head><body>"
    lHTML$ = lHTML$ & "Thanks for the order, " & lCustName$ & ".<br>"
    lHTML$ = lHTML$ & "A " & lProductName$ & " is in the mail!"
    lHTML$ = lHTML$ & "</body></html>"
    response = lHTML$
    Exit Sub
        
OrderErr:
    ProduceError "Browse", "Order"
    Exit Sub
    
End Sub

Compiling the OLE Object


After all the code has been entered and the project saved, it's time to create the OLE object's DLL file. Select File | Make OLE DLL File. When the Make OLE DLL File dialog appears, select a location to store the DLL and click the OK button.

If you are compiling the object on the same machine it will be accessed from (that is, on the IIS machine that will invoke the object), you can choose any directory. After it is compiled, Visual Basic will register the OLE object with the operating system. This registration information includes the path to the DLL file so the DLL can be placed anywhere on the system.

If you will be copying the DLL file to another system, select a location that you'll remember. Also, you must manually register the DLL on the target system before it can be used. You do so using the DOS shell command:

regsvr32 myobject.dll

In addition, keep in mind that you must have all the necessary VB support files on the target machine so that the DLL can be executed.

Testing the Application


After compiling the OLE DLL, check the following points before attempting to test the application.

  1. Have you created catalog.htm and create.htm and placed them in your server's document root directory? The HTML for these files is given in Listings 10.1 and 10.2.

  2. Have you created the database catalog.mdb and placed it in a directory accessible to applications running on your server machine? Also, does the path to the database match the DB_PATH constant found in GENERAL.BAS?

  3. Have you copied OLEISAPI.DLL to a directory your server considers to be an executable directory? Does the server-relative path match the OLEISAPI_PATH constant found in GENERAL.BAS?

  4. Have you entered some product information into the products table of the Customers database? Pictures make a nice addition to the catalog, do you have any entered and are they properly referenced in the PictureURL field? Several "widget" pictures are included on the CD-ROM if you'd like some to play with.



Picture files used on Web pages should be in either GIF or JPEG format. Almost all graphical Web browsers support these two formats. There are many commercially available packages that can convert graphics files to and from the GIF and JPEG formats. The GIF format should be used if high picture quality is not a great concern because the files are much smaller and, therefore, will load much faster on the user's browser. If you have high-quality photographs and you want to preserve that quality, use the JPEG format. You'll be sacrificing Web page loading time, however.

Have you entered some product information into the products table of the Customers database? Pictures make a nice addition to the catalog, do you have any entered and are they properly referenced in the PictureURL field? Several "widget" pictures are included on the CD-ROM if you'd like some to play with.

After you're satisfied with the preceding points, you can begin testing. The application is not difficult to test. If any problems occur, they're pretty simple to narrow down. You can determine which method is being invoked by examining the URL of the link that has been clicked or the action element of the <FORM> tag for the form that has been being filled in.

So, to begin testing the application, follow these steps:

  1. Load your favorite Web browser and point it to your server and the catalog.htm page. For example, if the host name for your server is www.myhost.com, you would browse the location http://www.myhost.com/catalog.htm.

  2. Because you're the first customer to come along, you should create a customer record. Click the Create Your Customer Record link to load the Create form.

  3. On the Create form, enter at least a user name, e-mail address, password, and verified password. Then click the Submit button. If the record is created properly, you will see a page similar to Figure 10.8.


  4. Figure 10.8. The new username page.

  5. Click the Browse Catalog link. A catalog page similar to Figure 10.5 appears, but with the product information from your database, of course.

  6. Reload the entry page. Select the Browse the Catalog link. The catalog page should be identical to the one produced by step 4 but without the Order links.

  7. Reload the entry page again. Enter your user name and password in the fields provided. Click the Log-On button. You should see a page similar to Figure 10.4.

  8. Click the Browse link. The page produced should be identical to the page produced in step 4.

  9. Now return to the entry page again. Run through the system entering invalid information to test the validation code. For example, enter a user name that is not in the database or a password that doesn't match the password you entered.

  10. Back on the create page, try to create a record with the same user name you used previously. Also, enter different values in the password and verify fields to make sure the password verification code is working.

  11. Continue testing the system until you're satisfied.



If you ever have to recompile the Visual Basic code, you must stop the IIS Web server before using File | Make OLE DLL File. Otherwise, you'll probably get a Permission Denied. Another process is using the file error message when you attempt to make the DLL. Although the OLEISAPI DLL should be freeing the objects when it is finished with them, it didn't do so on my system.

You may also have to turn the Cache Extensions option of the Web service off. Doing so ensures that the DLL is not kept in memory by the server. This option, as of this writing, must be manually changed in the system registry using REGEDT32.EXE. The registry key is HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\CacheExtensions. The value for this key should be either 0 (don't cache extensions) or 1 (cache extensions). Set it to 0 while debugging your applications. When you're ready for production, return this key to 1.

After you have recompiled the DLL, restart the IIS Web server.

Summary


This chapter has presented the VB programmer with a high-performance method of programming Web extensions for the Microsoft Internet Information Server. Although Win/CGI applications can still be used (see Appendix C, "Win/CGI on the Microsoft Internet Information Server"), they are extremely slow compared to ISAPI applications. Even using the OLEISAPI DLL to invoke objects that you create in Visual Basic is markedly faster than executing a Win/CGI application on the same server.

The drawback to using the OLEISAPI DLL as it is shipped with the IIS is that most of the standard Win/CGI variables are not made available to the OLE objects. This could easily be remedied by creating a standard set of object properties to which the OLEISAPI DLL would assign the CGI variables before invoking the object's methods. However, it is well beyond the scope of this book to begin mucking around in C++ code, especially C++ code that accesses OLE Automation objects!

Previous Page Page Top TOC Next Page See Page