Julie MacAller
Microsoft Corporation
January 1999
The Island Hopper News sample is an automated classified ads system created by a fictitious company, Island Hopper Publishing, as a test project to evaluate converting the current paper-based weekly newspaper into an online newspaper. The design team consists of the Island Hopper News editor, the Classified Ads and Accounting department heads, and two developers.
This paper describes where to find the source code for Island Hopper and how the Island Hopper code was designed. Other white papers focus specifically on how code is used within Island Hopper to carry out a specific customer or employee function.
You can find the source code for Island Hopper in the following location on your local drive if you accepted the installation defaults:
Program Files\IslandHopper\ScenarioC\Source
Source code is grouped into the following folders:
Name | Contents |
Server_Components | Source code for all data and business components. A single Microsoft® Visual Basic® project group, Server_Components, will open up source code for all the components created with Visual Basic. Some components—bus_InvoiceC, db_PaymentC, and util_WordCount—were created with Microsoft® Visual C++®. |
VBClient | Source code for the Microsoft® Windows® client (created using Visual Basic). |
Web | Source code for the Web client. |
Within the Server_Components folder, each business and data component has its own folder, as shown in the following table.
Folder | Contents |
bus_AdC | Source code for the bus_AdC component, which handles the browsing and placing of ads. |
bus_CustomerC | Source code for the bus_CustomerC component, which handles the creation, deletion, and modification of customer records. |
bus_InvoiceC | Source code for the bus_InvoiceC component, which handles invoices. Written using Visual C++. |
bus_PaymentC | Source code for the bus_PaymentC component, which handles payments. |
CompatibleDLLs | Compiled versions of the components used in the application. These components are gathered together into one directory for purposes of binary version compatibility. |
db_AdC | Source code for the db_AdC component, which handles updates and queries to the Advertisements table in the database. |
db_CategoryC | Source code for the db_CategoryC component, which handles updates and queries to the Categories table in the database. |
db_CustomerC | Source code for the db_Customer component, which handles updates and queries to the Customers table in the database. |
db_CustomerPasswordC | Source code for the db_CustomerPasswordC component, which handles updates and queries to the CustomerPasswords table in the database. |
db_InvoiceC | Source code for the db_InvoiceC component, which handles updates and queries to the Invoice table in the database. |
db_PaymentC | Source code for the db_PaymentC component, which handles updates and queries to the Payment table in the database. Written using Visual C++. |
db_ProductC | Source code for the db_ProductC component, which handles updates and queries to the Products table in the database. |
DLLs | Compiled versions of the components used in the application.
Note that all of the Visual Basic projects for the components have the Binary Compatibility option set in the project's properties. The Binary Compatibility option ensures that when you recompile a component, its CLSID stays constant—fixed to be that of the DLL in the CompatibleDLLs folder. The DLLs in the CompatibleDLLs folder are simply slightly older versions of the DLLs generated when you build any of the components. |
Globals | Global code for the application. |
Interfaces | Source code for the COM interfaces used in the application. |
util_TakeANumber | Source code for the TakeANumber utility, which assigns unique IDs when necessary. |
util_WordCount | Source code for the WordCount utility, which calculates the price of an ad, based on the number of words in the ad. Written using Visual C++. |
The VBClient folder contains the following folders.
Folder | Contents |
ClassifiedEXE | Contains forms, modules, and a Visual Basic project for the Windows client. |
ErrPlus | Contains Visual Basic source code for the error handling component. |
Util_eventViewer | Contains C++ source code for the util_EventViewer component. |
The following forms in ClassifiedEXE comprise the Windows client user interface.
Form Name | Description |
frmAccountingC | The main form of the Island Hopper News application. Provides a place to choose what task you want to perform with the Island Hopper application. |
frmAdC | The Ad Maintenance form. Displayed when you click Ad Maintenance on the main Island Hopper News form. Used to retrieve a list of the ads submitted by a particular customer. |
frmAdDetailC | The Advertisement Details dialog box. Displayed when you select one of the ads listed on the bottom of the Ad Maintenance form. Used to change the category of an ad, edit the text, change the duration, or change the start date. |
frmCustomerC | The Customer Maintenance form. Displayed when you click Customer Maintenance on the main Island Hopper News form. Used to add, delete, and update customer records. |
frmCustomerSearchC | The Search For Customer By Name dialog box. Displayed when you click Retrieve by Last Name on the Customer Maintenance or Input Payments form. Used to retrieve a list of customers from the database where the last name field matches a supplied last name. |
frmDisplayAdsC | The Browse an Ad form. Displayed when you click Browse Ads on the main Island Hopper News form. Used to browse classified ads by category. |
frmInputPaymentC | The Input Payment form. Displayed when you click Track Payments on the main Island Hopper News form. Used to enter payments made by a particular customer. |
frmInvoiceC | The Invoice form. Displayed when you click View Invoices on the main Island Hopper News form. Used to retrieve a list of invoices for a particular customer. |
frmInvoiceDetailC | The Invoice Details dialog box. Displayed when you select one of the invoices listed on the bottom of the Invoice form. Used to view individual invoice numbers and dates for a particular customer. |
frmParentC | MDI parent form. Provides the Island Hopper News title bar. Has one child form, frmAccountingC. |
The Web folder contains the following files, which comprise the Web client user interface.
Name | Description |
AdConfirm.asp | Online Classified Advertisement Submit Results page. Displays confirmation message when ad is accepted into database. |
AdDisplay.asp | Online Classified Advertisement Display page. Displayed when you click Browse an Ad on the Browse/Place an Ad page. |
AdEntry.asp | Online Classified Advertisement Entry page. Displayed when you click Place an Ad on the Browse/Place an Ad page. |
AdsList.asp | List of ad categories. Called by AdDisplay.asp after you select a category to populate the list of ads in that category. |
Browse.asp | Browse/Place an Ad page. Displayed when you click Classified Ads on the Island Hopper Headlines page. |
Headlines.asp | Island Hopper Headlines page, which is the main screen of the Web client. Shows the front page of Island Hopper News. |
The Island Hopper News sample uses a consistent organization for its Active Server Pages (.asp) files. This organization includes the following elements (in order of appearance in the file):
The @LANGUAGE processing directive sets the language that will be used to interpret commands in a script. The Island Hopper News sample uses Microsoft® Visual Basic Scripting Edition (VBScript) as the primary scripting language.
The @TRANSACTION processing directive specifies a transaction. A transaction is a server operation that succeeds or fails as a whole, even if the operation involves many steps, such as ordering, checking inventory, and billing. Transaction processing is used to update databases reliably. When you are making many related changes to a database or updating several databases at once, you want to ensure that all of the changes are correctly executed. If any of the changes fail, you want to restore the original state of the database tables.
In some places in the Island Hopper News sample, transactions are used for database read operations, such as browsing classified ads. You might wonder why a transaction is used in these circumstances. It's true that you don't usually need transactions for database read operations. It's also true that the transaction settings on the Island Hopper components weren't optimized; all the Island Hopper business and data access components are marked as "requires a transaction," even if they are sometimes used in operations that don't require a transaction.
However, in ASP, the only transaction settings that really make sense are "requires a transaction" and "not supported," since the scope of the transaction is one page. Either the page uses transactions or it does not. If the components used on a page (for example, bus_AdC and db_CategoryC) are marked as requiring transactions and the ASP page is not transactional, two separate transactions would be created for the call into bus_AdC and the call into db_CategoryC, which would impact performance.
In the Island Hopper News sample, pages that use any Island Hopper components are marked transactional, because all the Island Hopper components themselves are marked transactional. This way, all the work of the page is done within a single transaction.
The Option Explicit statement specifies that any variables must be declared before they are used. Using this option helps you identify undefined variables, because you will get an error message. Option Explicit also makes undeclared local variables illegal, which improves the performance of the application. Undeclared local variables are as slow as global variables. Global variables are slow, because they are resolved at run time, and because if they are undeclared, they require a search of the entire object model for the variable name before creating a new variable.
Island Hopper users—who can be either external customers or Island Hopper publishing employees—can perform the following functions with the Island Hopper application:
Function | User | Client |
Browsing ads | Customer or employee | Web or Windows |
Placing ads | Customer | Web |
Editing ads | Employee | Windows |
Maintaining customer records | Employee | Windows |
Viewing invoices | Employee | Windows |
Tracking payments | Employee | Windows |
Each of these functions are implemented in more or less the same way:
All business and data components are Microsoft® Transaction Server (MTS) enabled. MTS provides a set of services for building components that reside on the middle tier in a three-tier architecture design. These services include resource management, transaction management, scalability, deployment, and maintenance. When you enable your components for MTS, you do not need to write extra code to handle any of these services. All you need to do is allow MTS to create and manage your objects. You can concentrate on writing the specific business logic for your application.
If you are unfamiliar with MTS or want more information, search the MSDN™ Library for "Microsoft Transaction Server." This phrase will yield pointers to many articles about using MTS. You can also find the MTS documentation in the MSDN Library; expand the "Platform SDK" node, and then the "Component Services" node.
The Island Hopper News application is a data-driven application. All its functions involve adding data to or getting data from a central database. Island Hopper is also a distributed application. Although you can install the Island Hopper News sample on one machine, the application is designed and written assuming an environment where the database is on a separate machine. Having the database on a separate machine makes data access efficiency a key concept. Whatever mechanism you use to access data should minimize network traffic as much as possible.
The Microsoft® Data Access Components (MDAC) are intended to provide efficient access to remote data; the Island Hopper news application makes use of the following components:
ActiveX Data Objects (ADO) provide a common programming model for any OLE DB data source. ADO is built around a set of core functions that all data sources are expected to implement. In addition to this core set of features, ADO can also access the unique features of specific data sources through general OLE DB service providers. ADO uses native OLE DB providers, including a specific OLE DB provider that provides access to ODBC drivers. ADO was designed to replace the need for all other high-level data access methods. ADO can access relational, ISAM, or hierarchical databases, or any type of data source—as long as there is an ODBC-compliant driver.
The primary benefits to ADO are ease of use, speed, and low memory overhead, which makes it ideal for server-side scripting. In fact, ADO is the recommended technology for data access for ASP applications. ADO can be called directly from server-side scripts or from business components.
Unlike earlier data access methods, you no longer need to navigate through a hierarchy to create objects; most ADO objects can be created independently. This allows you greater flexibility in reusing objects in different contexts. It also allows you to create only the objects you need, resulting in fewer ADO objects and less memory consumption. ADO also takes advantage of ODBC 3.0 connection pooling for ODBC data sources and session pooling for OLE DB providers.
For more information about ADO, search the MSDN Library for "ADO." This acronym will yield pointers to many different articles. Additionally, the ADO documentation is available in the MSDN Library; to see it, expand the Platform SDK node, and then the Data Access Services node, and finally the Microsoft Data Access Components (MDAC) SDK node.
Remote Data Services (RDS) provides the disconnected recordsets mechanism. Disconnected recordsets are a fast way to connect to a database, get some data, disconnect, and return all the data to the client. They reduce the amount of time you spend connected to the database, which makes your distributed application more efficient.
Disconnected recordsets offer performance benefits in a distributed environment, because you connect to the database to get the recordset, send the recordset to the client, and disconnect. You browse and manipulate the recordset on the client, and reconnect to the database only if it's necessary to update data.
OLE DB is a set of COM interfaces which provides a standard way for application programs to access data. ADO is designed to work with OLE DB, and, in most instances, your ADO components will communicate with databases through OLE DB. If there is not an OLE DB provider available for the source of your application's data, you can also use ADO to communicate directly with the ODBC driver.
The Island Hopper News application uses three different methods to access the database from ADO:
These methods are optimized to take advantage of the strengths of their particular objects.
Before you issue a command, you must open a connection. Each method that issues a command represents the connection differently:
Another difference is the way the command is specified in the three methods:
Each method trades off functionality versus performance:
As you study the code in the Island Hopper News sample application, you'll see two different ways to retrieve data from a database: stored procedures and building a SQL command and then executing it directly to the database. Which of these methods you would use really depends on your design and what you are doing. Stored procedures offer a performance benefit, but if you are retrieving only a few records, that benefit might not be significant.
All of the query methods in db_CustomerC use a call to a parameterized stored procedure in the classified ads database. Other query methods in other data access components also call a stored procedure. The code that calls the stored procedure is in a global function called RunSp. Stored procedures offer performance advantages, because they are precompiled.
RunSp creates an updateable, disconnected recordset. A disconnected recordset, new with ADO 2.0, is a recordset in a client cache that no longer has a live connection to the server. What this means for Island Hopper News is it's possible to connect to a database, retrieve some records, return the records to the client, and disconnect from the database. The client can use the records as needed and then connect to the database again, if necessary, to make any updates.
RunSp includes the following steps.
conn.Open "FILEDSN=" & strFileDSN
Set cmd.ActiveConnection = conn
cmd.CommandText = strSP
cmd.CommandType = adCmdStoredProc
The ActiveConnection property determines the Connection object to which the Command object will execute. It lets you use an already-open connection to the database, if one exists. MTS manages database connections as part of its resource management, so using the ActiveConnection property lets you reuse a database connection from the MTS pool.
The CommandText property contains the text of a command you want to execute. In this case, strSP contains the name of the stored procedure to execute; it is passed to RunSp by the calling method. For example:
Set rs = RunSp(fileDSN, "Customer_GetByEmail", 1, Array("@vEmail"), _
Array(adChar), Array(50), Array(strEmail))
The CommandType property indicates the type of the Command object. Setting the CommandType property to a specific type, such as a stored procedure, directs ADO to go directly to the relevant code, so it improves performance.
Dim i As Integer
For i = 0 To intPRMCount - 1
Set prm = cmd.CreateParameter(strPRM(i), strPRMType(i), _
adParamInput, intPRMLength(i), strPRMValue(i))
cmd.Parameters.Append prm
Next i
Each parameter has four pieces of relevant information: name (strPRM), data type (strPRMType), size (intPRMLength), and value (strPRMValue). The intPRMCount variable contains the total number of parameters for the stored procedure.
The strPRM, strPRMType, intPRMLength, and strPRMValue variables are arrays that contain the information for each parameter. They are passed to RunSp from the calling method.
RunSp creates Parameter objects by looping through the arrays and calling the CreateParameter method repeatedly, and then adding each Parameter object to the Command object's Parameters collection using the Append method.
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenStatic, adLockBatchOptimistic
Set RunSp = rs
A static cursor type means a static copy of a set of records that you can use to find data or generate reports. Additions, changes, and deletions made by other users are not visible. Batch optimistic locking is required for disconnected recordsets. Optimistic locking means that the entire recordset is locked when you call the UpdateBatch method. Both of these settings make it possible for the client to modify the retrieved data.
The query method in db_CustomerPasswordC builds the SQL SELECT command necessary for querying the database and executes the command directly to the database using the Open method on a Recordset object. You'll see this same approach elsewhere in the Island Hopper News sample as well. Here's how this approach works.
strSQL = "SELECT Password FROM CustomerPasswords " & _
"WHERE CustomerID = " " & lngCustomerID
rs.Open strSQL, "FILEDSN=" & fileDSN, adOpenForwardOnly, _
adLockReadOnly