The Building Blocks for Creating Reusable Administration Pages

The biggest challenge in creating truly reusable administration pages arrives when dealing with complex database tables. Imagine, for a moment, that all our tables were simple database tables that don’t impose foreign key constraints upon other tables, and have no foreign key constraints imposed upon them (that is, a simple database table is one that has no explicit relationships with other tables). Rarely are there many simple database tables in a database. Since relational databases inherently encourage the developer to create several small, related tables, simple database tables are found only in simple data models. Therefore, we need a system that gracefully handles complex tables as well as simple tables.

In this section we’ll examine the theory behind creating powerful, reusable database administration pages that can be used for complex tables. We’ll begin with a discussion of what the ideal reusable administration page should contain. Since the reusable administration pages we’ll later create will make heavy use of ADO schemas, an entire section is dedicated to this topic.

The Ideal Reusable Administration Page Script

Without code reuse, a developer needs to perform seven steps for each administration page. With code reuse, however, the need to repeat each of the seven steps for each administration page can be eliminated. Can code reuse help to eliminate the fact that seven steps are needed, though? The ideal situation would be to have only one step needed to create an entire set of administration pages.

Imagine that we had a page called DatabaseAdministration.asp that expected one parameter through the querystring, the table name. This page would then display the appropriate options for adding, deleting, and updating records, gracefully handling one-to-many and one-to-one relationships. Furthermore, the page would know what form elements would be best suited for collecting input from the user. For example, if a table had a date/time column, it might be wiser in some instances to use select boxes to represent the month, day, and year; other times, it might be wiser to use a simple text box.

This single administration page would also need to be able to determine what columns in a table had foreign key constraints and provide a mechanism to allow the user to only select applicable values from the related table. For example, imagine that we had an Employee table with the structure shown in Figure 6.1.

The Title table serves as a lookup table to Employee

Figure 6-1. The Title table serves as a lookup table to Employee

Note that Title is a lookup table, and its Name column contains values like “President,” “CEO,” “Manager,” “Secretary,” and “Grunt.” Through referential integrity, TitleID in Employee can contain only integer values that are also present in TitleID in Title.

How should our administration page handle such foreign keys when adding a new record or updating an existing record? A simple text box could be used, in which the user could enter an integer value corresponding to a value in the Title table. Of course, this would require the user to know that the title “CEO” had a TitleID of, say, 5.

This is not an acceptable solution, since the user cannot be held responsible for knowing what titles correspond to what TitleIDs and what TitleIDs are valid. Rather than seeing a text box, the user should be presented with a list box containing the acceptable title names. To accomplish this feat, our administration page would not only have to identify what columns are foreign keys, but would also have to decide what column from the lookup table to display in the list box!

Such a single, robust administration page is quite impossible to create, since it would require that the page be autonomous and know the best way to display form fields and lookup table lists. We can’t expect the script to be able to make the best decisions, but what if we could give “hints” to the script to help it decide how to handle form elements and foreign keys? In this chapter, we will strive to build a collection of ASP pages that allow the end user to update, insert, and delete database records with some input from the end developer to help the administration page decide what methods to use to collect user input.

In creating this set of reusable administration pages, some mechanism for determining a particular table’s columns and column properties needs to be used. Furthermore, since these pages strive to be as generic as possible, they shouldn’t be dependent upon the database used; that is, these administration pages should work if we use Access, Microsoft SQL Server, or some other database system. Thankfully, ADO provides a means to collect table and column information regardless of the database provider.

Database Schemas

One of ADO’s most useful features is schemas . Schemas provide low-level database information, such as table information, column information, foreign key constraints, and primary key information, in a high-level format. Using schemas, this system information can be collected in the same manner for a number of different database providers.

Most database systems provide their own methods for collecting such low-level information. For example, if you want to access table and column information in Microsoft SQL Server without using schemas, you would have to query system tables, like sysobjects or syscolumns. Example 6.1 illustrates how to obtain all of the tables in a SQL Server database by querying sysobjects.

Example 6-1. List the Tables in a SQL Server Database Using the sysobjects Table

<% @LANGUAGE="VBSCRIPT" %>
<% Option Explicit %>
<%
    'Open up a connection to the database
    Dim objConn
    Set objConn = Server.CreateObject("ADODB.Connection")
    objConn.ConnectionString = "DSN=MyDatabase"
    objConn.Open

    'We want to query the sysobjects table where type = 'U'
    Dim strSQL
    strSQL = "SELECT * FROM sysobjects WHERE type='U'"

    'Execute the query
    Dim objRS
    Set objRS = objConn.Execute(strSQL)

    'Display the table names
    Do While not objRS.EOF		
      Response.Write objRS("Name") & "<BR>"
      objRS.MoveNext
    Loop

    'Clean up...
    objConn.Close
    Set objConn = Nothing
%>

The sysobjects table is fairly cryptic, containing several columns with single character values that denote their purpose. Rows that have a “U” in the Type column are tables. I think you’ll agree that the code in Example 6.1 is fairly cryptic. Furthermore, the code will only work for Microsoft SQL Server databases.

Schemas make listing database information much easier and more portable. Since the code in Example 6.1 will only work for Microsoft SQL Server databases, it is anything but portable, and portability should be a key concern when developing reusable scripts.

Importing enumerations with adovbs.inc

Before you start using schemas, it is important that you have access to the enumerations defined in ADO. There are currently two ways to do this:

  • Use a server-side include to import the contents of adovbs.inc into each ASP page that needs to reference these database enumerations.

  • Use the METADATA tag in Global.asa to import the ADO constants.

adovbs.inc is a text file that contains all of the ADO enumerations in the form of VBScript constants. To use adovbs.inc, copy it into the directory in which you place your include files (I recommend creating a directory named /inc and turning off Read permissions.) adovbs.inc can be found in Program FilesCommon FilesSystemado.

Once adovbs.inc is in a web-accessible directory, you can use a server-side include to import the contents of adovbs.inc into any ASP page that needs to access the ADO enumerations. Simply use the following line of code at the top of your document:

<% @LANGUAGE="VBSCRIPT" %>
<% Option Explicit %>
<!--#include virtual="/inc/adovbs.inc"-->

Take a moment to look at the contents of adovbs.inc. Note that it is nothing more than a lengthy list of VBScript constants assigned appropriate hexadecimal values. Below is a short excerpt from the schema section of adovbs.inc:

'---- SchemaEnum Values ----
Const adSchemaProviderSpecific = -1
Const adSchemaAsserts = 0
Const adSchemaCatalogs = 1
Const adSchemaCharacterSets = 2
Const adSchemaCollations = 3
Const adSchemaColumns = 4

Tip

For more information on server-side includes, be sure to read Chapter 1, or check out “The low-down on #includes,” available at http://www.4guysfromrolla.com/webtech/080199-1.shtml.

Importing enumerations with the METADATA tag

The METADATA tag imports enumerations from a type library. For example, in each ASP page that uses a server-side include to import the contents of adovbs.inc, we could replace:

<!--#include virtual="/inc/adovbs.inc"-->

with the following line of code:

<!-- METADATA 
        TYPE="typelib" 
        FILE="D:Program FilesCommon FilesSystemADOmsado21.tlb"
-->

The METADATA tag has the following syntax (the line breaks are for enhanced readability and are not required):

<!-- METADATA
        TYPE="typelib"
        UUID="GUID"
        FILE="FilePath"
-->

You only need to specify either the UUID or the FILE when importing constants from a type library. The UUID expects the type library’s GUID, while the FILE expects the full, physical path to the type library. The GUID for the ActiveX Data Objects type library is: 00000201-0000-0010-8000-00AA006D2EA4. I find that specifying the FilePath instead of the GUID makes for much easier-to-read code. However, there is always the chance that the file may be renamed or moved, whereas the GUID won’t change.

Now, you may be wondering why anyone would want to use the METADATA tag in place of adovbs.inc. Typing:

<!--#include virtual="/inc/adovbs.inc"-->

instead of:

<!-- METADATA 
        TYPE="typelib" 
        FILE="D:Program FilesCommon FilesSystemADOmsado21.tlb"
-->

is quicker and less prone to mistake. METADATA’s advantage is that you can specify it once in Global.asa and have access to all of the ADO enumerations in all of your ASP pages! Also, according to Microsoft’s ASP Performance Tips (http://msdn.microsoft.com/library/psdk/bdg/bdgapp03_3rhv.htm), using a single METADATA tag in Global.asa provides for better performance than using server-side includes on each page.

Warning

If you place the METADATA tag in Global.asa, remember that every ASP page will have access to the ADO constants. If you attempt to include adovbs.inc in a page, you will receive a Name redefined: 'adOpenForwardOnly' error. This error occurs because you are attempting to create a constant in adovbs.inc that already exists from the METADATA import. adOpenForwardOnly is listed because it is the first constant defined in adovbs.inc.

Opening schemas

To open a schema, use the OpenSchema method of the Connection object. The OpenSchema method has the following definition:

Set recordset = connection.OpenSchema(QueryType[, Criteria[, SchemaID]])

QueryType is a required parameter that specifies the type of schema to open, and must be set to a valid SchemaEnum type. There are a number of possible schema types that can return a vast array of database information. There are schemas for listing tables, columns, column privileges, foreign keys, indexes, primary keys, referential constraints, and other miscellaneous information. The valid SchemaEnum s are listed in adovbs.inc under the heading “SchemaEnum Values.” Each SchemaEnum constant defined in adovbs.inc is prefixed with an adSchema; some of the more commonly used SchemaEnum values (including those that will be used in code presented in this chapter) are listed in Table 6.1.

Table 6-1. Several Common SchemaEnums Are Defined in adovbs.inc

SchemaEnum

Description

adSchemaColumns

Contains detailed information about each of the columns in a database

adSchemaTables

Contains detailed information about each table in a database

adSchemaForeignKeys

Contains detailed information about all of the foreign key constraints in a database

The available SchemaEnums that can be specified by the QueryType parameter differ among the various database providers. Many database providers support a common set of SchemaEnums (such as adSchemaColumns and adSchemaTables), but some database providers may contain their own unique schemas that aren’t represented by a value in the SchemaEnum list. If you need to use such a schema, set the QueryType to adSchemaProviderSpecific and specify the unique schema in the SchemaID parameter.

Warning

The various schemas supported depend upon the database provider. For example, the OLE DB provider for AS/400 and VSAM only supports four schemas, while the OLE DB provider for SQL Server supports several more.

Example 6.2 contains code that is functionally identical to the code presented in Example 6.1; both scripts list the tables in a database. Example 6.2, however, uses the adSchemaTables QueryType rather than sysobjects, which makes Example 6.2 easier to read and more portable.

Example 6-2. Listing the Tables in a Database with Schemas

<% @LANGUAGE="VBSCRIPT" %>
<% Option Explicit %>
<!--#include virtual="/adovbs.inc"-->
<%
    'Open a connection to the database
    Dim objConn
    Set objConn = Server.CreateObject("ADODB.Connection")
    objConn.ConnectionString = "DSN=MyDatabase"
    objConn.Open

    'Use the OpenSchema method to grab the table schema
    Dim objRS
    Set objRS = objConn.OpenSchema(adSchemaTables)

    'Loop through the contents of the schema.
    Do While Not objRS.EOF
      'Only display TABLES - not SYSTEM TABLES, not VIEWS
      If objRS("TABLE_TYPE") = "TABLE" then
         Response.Write objRS("TABLE_NAME") & "<BR>"
      End If
	  
      objRS.MoveNext
    Loop

    'Clean up...
    objRS.Close
    Set objRS = Nothing
	
    objConn.Close
    Set objConn = Nothing
%>

Since a schema is returned as a Recordset object, we can use the Recordset object’s properties and methods to iterate through the schema. A schema, like an ordinary database table, contains columns, which describe the properties of an abstract object, and rows, which serve as an instantiation of an object. The adSchemaTables schema we used in Example 6.2 contains several columns describing each table, including TABLE_NAME, TABLE_TYPE, DESCRIPTION, DATE_MODIFIED, and DATE_CREATED.

The adSchemaTables schema returns the views and system tables along with the user tables. The TABLE_TYPE column specifies what type of table is being returned, and in Example 6.2 we list only user tables by assuring that TABLE_TYPE equals "TABLE" before displaying the TABLE_NAME column.

Tip

Note that we explicitly included adovbs.inc in Example 6.2. This explicit include would not be needed if the METADATA tag were used in Global.asa. It is strongly recommended that you use the METADATA approach. All the examples in this book that need to use ADO enumerations, however, will explicitly include adovbs.inc to assist with readability.

The Criteria parameter

The OpenSchema method has an optional parameter named Criteria. This parameter can be specified to limit the resulting schema. For example, in Example 6.2, we didn’t specify a Criteria, and all tables were returned, including views and system tables. We could pass in a Criteria parameter, however, that would inform ADO to return only those tables that were user tables. Using a Criteria to limit the contents of a schema is similar to using a WHERE clause to limit the results of a SQL query.

Each SchemaEnum has its own predefined set of potential Criteria that can be used to limit the results of a schema. Some SchemaEnums have no limiting Criteria, while others can have several columns to which Criteria can be applied. To accommodate the differing number of Criteria accepted by various SchemaEnums, the Criteria parameter should be passed in as an array. For example, if a particular SchemaEnum expects four Criteria, the array should contain four elements.

The adSchemaTables schema expects four Criteria: TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, and TABLE_TYPE. To retrieve only user tables when opening the adSchemaTables schema, we need to supply a Criteria parameter that was a four-element array, defined as:

Array(Empty, Empty, Empty, "TABLE")

Since we do not wish to filter on the first three CriteriaTABLE_CATALOG, TABLE_SCHEMA, and TABLE_NAME —we simply leave these blank by specifying the resulting array element as Empty. By using the above Criteria, we can alter Example 6.2 so the check for objRS("TABLE_TYPE") = "TABLE" can be avoided. Example 6.3 displays this new code using the Criteria parameter of the OpenSchema method.

Example 6-3. Using the Criteria Parameter to Selectively List Tables of a Database

<% @LANGUAGE="VBSCRIPT" %>
<% Option Explicit %>
<!--#include virtual="/adovbs.inc"-->
<%
    'Open a connection to the database
    Dim objConn
    Set objConn = Server.CreateObject("ADODB.Connection")
    objConn.ConnectionString = "DSN=MyDatabase"
    objConn.Open

    'Use the OpenSchema method to grab the table schame
    'In this example, we specify the Criteria parameter of the
    'OpenSchema method
    Dim objRS
    Set objRS = objConn.OpenSchema(adSchemaTables, _
                     Array(Empty, Empty, Empty, "TABLE"))

    'Loop through the contents of the schema.
    Do While Not objRS.EOF
      'Since the Criteria parameter returned only user table,
      'we don't need to check TABLE_TYPE here, like we did in Example 6.2
      Response.Write objRS("TABLE_NAME") & "<BR>"
	  
      objRS.MoveNext
    Loop

    'Clean up...
    objRS.Close
    Set objRS = Nothing
	
    objConn.Close
    Set objConn = Nothing
%>

Keep in mind that Example 6.1, Example 6.2, and Example 6.3 all are functionally equivalent.

Tip

There are several possible SchemaEnum values, each with their own columns and possible criteria values — far too many to list in a non-reference book like this one. You can obtain a full SchemaEnum listing at http://msdn.microsoft.com/library/psdk/dasdk/mdae0wfh.htm or through a technical reference book, like Wrox’s ADO 2.0 Programmer’s Reference (Wrox Press Inc.).

Schemas will greatly assist in the quest to create a generic database access script. Since schemas provide portable, low-level database information through an easy-to-use interface, they make an ideal solution for determining how to display particular database fields and database input mechanisms on a generic database page.

For more information on ADO schemas, be sure to read the following tutorials and articles:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.238.70