What Can Be Done to Improve ASP Design?

Even though ASP scripts are, obviously, scripts, there are several approaches that can be taken to modularize your source code and encapsulate complex tasks. These include using server-side includes, taking advantage of VBScript classes, and making calls to the Server.Execute and Server.Transfer methods.

Server-Side Includes

One of the most common approaches to creating modularized code is to use server-side includes (SSI). Server-side includes are used to import ASP code into an ASP page. The benefit of being able to import code from one ASP page to another is you can create individual ASP pages with common functions or classes, and have these functions and class definitions imported into the pages that require their use. Instead of having to cut and paste a particular function that is needed in several ASP web pages, you can place that function in a single file, and then use a server-side include to import the function definition into each ASP page that needs to make use of that particular function.

For example, imagine that you run an e-commerce site. Conceivably, there are a number of ASP pages in which you need to compute the sales tax. Rather than hardcode a sales tax percentage in each of these pages, you could create a single function—ComputeTotalWithSalesTax—that would accept the total less the sales tax as a parameter, returning the new total including sales tax. Example 1.1 contains an example of the ComputeTotalWithSalesTax function.

Example 1-1. Determining the Sales Tax

Function ComputeTotalWithSalesTax(curTotalLessSalesTax)
  Const curSalesTax = 0.0695
  ComputeTotalWithSalesTax = curTotalLessSalesTax + _
          curTotalLessSalesTax * curSalesTax
End Function

Tip

The above snippet of code places business logic—determining the sales tax—within an ASP page. As we’ll discuss in Chapter 7, business logic should be placed in custom components. The above code snippet only serves to show an example of using server-side includes.

If this function existed on each page that needed to calculate the sales tax, imagine what would happen if the sales tax percentage changed. You would have to poke through every ASP page, checking to see if it referenced a hardcoded sales tax value, and if it did, make the appropriate change. Of course, if you missed a page, you’d be in for a headache when certain users were being charged less tax than others.

Needless to say, such an approach is error-prone; furthermore, the developer who chooses this approach is ulcer-prone. A wiser decision would be to place a single instance of the ComputeTotalWithSalesTax function into a file, say /CODEREUSE/ComputeSalesTax.asp, and then use server-side includes in each ASP page that needs to reference this function.

When using a server-side include to import the contents of one file to another, the text from the included file is just dumped straight into the file that issues the server-side include. Using a server-side include is functionally identical to copying the entire contents of the included file and pasting them into the file that initiated the server-side include. Therefore, if you have Option Explicit declared in the file that issues the server-side include (which you always should), you will need to have every variable you use in the included file declared in the included file. Also, the code in the file to be included should be placed within an ASP code block, using either the <% and %> delimiters or the <SCRIPT RUNAT=SERVER LANGUAGE="VBSCRIPT" > and </SCRIPT> delimiters.

Tip

ASP pages are not the only type of file that can execute server-side includes. Later in this chapter, in Section 1.4.1.1, we’ll look at using server-side includes in non-ASP pages.

To revisit our sales tax example, if we created a file to hold the sales tax computing function and named that file /CODEREUSE/ComputeSalesTax.asp, we would want to enter the following code into that file:

<%
Function ComputeTotalWithSalesTax(curTotalLessSalesTax)
  Const curSalesTax = 0.0695
  ComputeTotalWithSalesTax = curTotalLessSalesTax + _
          curTotalLessSalesTax * curSalesTax
End Function
%>

Note that the above code is nearly identical to the code in Example 1.1. The only difference is the code snippet above contains the <% and %> delimiters around the ASP code. Remember, this is needed to be properly included.

Now, for each page that needs to access this function, we need to add a single line of code. The following is an example ASP page that uses a server-side include to make the ComputeTotalWithSalesTax function accessible:

<% @LANGUAGE="VBScript" %>
<% Option Explicit %>
<!--#include virtual="/CODEREUSE/ComputeSalesTax.asp"-->
<%
  Dim curTotal
  curTotal = 46.72

  'Output the total with sales tax
  Response.Write "Cost Before Sales Tax: " & FormatCurrency(curTotal, 2)
  Response.Write "<BR>Cost After Sales Tax: " & _
           FormatCurrency(ComputeTotalWithSalesTax(curTotal), 2)
%>

The server-side include has two forms:

<!--#include file="filename"-->

and:

<!--#include virtual="filename"-->

If you use the file keyword, then filename is relative to the directory in which the ASP page that issues the server-side include resides. If you use the virtual keyword, filename is relative to the web server’s root directory. For example, if from an ASP page in the /MyASPScripts directory you had:

<!--#include file="ComputeSalesTax.asp"-->

then the file ComputeSalesTax.asp would need to exist in the /MyASPScripts directory; if it did not, an error would result when attempting to use the server-side include.

Remember that when using the virtual keyword, you need to specify filename relative to the Web’s root directory, regardless of the directory that the ASP page that issues the server-side include exists in. Therefore, if in an ASP page you used:

<!--#include virtual="/CODEREUSE/ComputeSalesTax.asp"-->

the file ComputeSalesTax.asp would need to exist in the /CODEREUSE directory, regardless of what directory the ASP page that issued the server-side include existed in.

Your include files can contain HTML formatting and/or ASP code. Also, an include file can use server-side includes itself to further modularize the code! However, if you try to perform a cyclic include —that is, Page1.asp includes Page2.asp, and Page2.asp includes Page1.asp —an error message will be generated. (More verbosely, the only limitation for including files in included files is included files can’t include the file that included them. Be sure to check out An Interesting Lingo) Forcing a cyclic include to generate an error makes sense, because if a cyclic include did not generate an error, Page1.asp and Page2.asp would recursively include one another until the web server ran out of available memory.

Example 1.2 contains the code for Page1.asp , while Example 1.3 contains the code for Page2.asp. If you visit Page1.asp through a browser, the error message shown in Figure 1.1 will be generated.

Example 1-2. Page1.asp Uses a Server-Side Include to Import the Contents of Page2.asp

<%
   Response.Write "Preparing to include Page2.asp<P>"


   'Include the file Page2.asp
%>
<!--#include file="Page2.asp"-->

Example 1-3. Page2.asp Uses a Server-Side Include to Import the Contents of Page1.asp

<%
   Response.Write "Preparing to include Page1.asp<P>"


   'Include the file Page1.asp
%>
<!--#include file="Page1.asp"-->
Attempting to perform a cyclic include generates an error message

Figure 1-1. Attempting to perform a cyclic include generates an error message

File types that can perform server-side includes

ASP pages are not the only files that can perform server-side includes. Any file type that is mapped to asp.dll or ssinc.dll can perform server-side includes.

When a client requests a file from an IIS web server, the web server first determines the file extension being requested by the client. Next, the web server checks to see if that extension is mapped to a particular ISAPI DLL. If there is a mapping between that file extension and an ISAPI DLL, the ISAPI DLL is invoked. This is, essentially, what happens when you request an ASP page through your web browser.

ssinc.dll is an ISAPI DLL that allows server-side directives to be executed. A server-side include is one facet of server-side directives. See Section 1.5 for an article that discusses server-side directives in more detail. In IIS 5.0, files with the extensions .shtml, .shtm, and .stm are, by default, mapped to ssinc.dll, and therefore can perform server-side includes. Of course, files with these extensions cannot process ASP code, unless you explicitly map these extensions to be processed by asp.dll.

Tip

asp.dll is capable of performing only one type of server-side directive: server-side includes. ssinc.dll, on the other hand, can perform all server-side directives but cannot process ASP code. For more information on all of the server-side directives, be sure to refer to the article “Using Server-Side Directives” listed at the end of this chapter in Section 1.5.

You can explicitly map particular file extensions to particular ISAPI DLLs. To change a file extension mapping in Windows 2000, go to Start Programs Administrative Tools Internet Services Manager (in Windows NT, open up the IIS MMC). A listing of the web sites on your computer should appear. Right-click on the web site whose file extension mapping you wish to alter, and click on Properties. A tabbed Web Site Properties dialog box will appear; select the “Home Directory” tab, and click the Configuration button. You should now be presented with the Application Configuration dialog box shown in Figure 1.2.

The “App Mappings” tab of the Application Configuration dialog box lists each file type’s corresponding ISAPI DLL

Figure 1-2. The “App Mappings” tab of the Application Configuration dialog box lists each file type’s corresponding ISAPI DLL

Note that the .asp file extension is mapped to asp.dll, while the extensions .shtml, .shtm, and .stm are mapped to ssinc.dll. You can add, edit, and remove these mappings. For example, you could create a new extension mapping, having all files with the extension of .scott map to ssinc.dll. With such a mapping, files on your web site with the .scott extension would be functionally identical to files with extensions of .shtml, .shtm, or .stm; in short, .scott files would be able to perform server-side directives!

Warning

Be careful when adding new application mappings. There is a bug in IIS 5.0 that can arise when the .htm extension is mapped to ssinc.dll and the default document is a .htm file. For more information on this bug, be sure to read: http://support.microsoft.com/support/kb/articles/Q246/8/06.ASP.

Dynamic server-side includes

Server-side includes are executed before any ASP code is processed. Due to this fact, you cannot specify a dynamic filename. For example, it would be nice to be able to do something like:

<%
  Dim strPage
  strPage = "/scripts/MyPage.asp"
%>
<!--#include virtual="<%=strPage%>"-->

However, since the server-side includes are performed before the ASP code is processed, the above code will cause IIS to complain that the file <%=strPage%> cannot be found. There are some methods available to fake dynamic server-side includes. One way is to use a Select Case statement, with a Case for each potential server-side include. For example, if you knew you would need to process the code in one of five potential include files, you could use the following code:

<%
Select Case strPageToExecute
   Case "IncludeFile1.asp" %>
    <!--#include virtual="/IncludeFile1.asp"-->
<% Case "IncludeFile2.asp" %>
    <!--#include virtual="/IncludeFile2.asp"-->
<% Case "IncludeFile3.asp" %>
    <!--#include virtual="/IncludeFile3.asp"-->
<% Case "IncludeFile4.asp" %>
    <!--#include virtual="/IncludeFile4.asp"-->
<% Case "IncludeFile5.asp" %>
    <!--#include virtual="/IncludeFile5.asp"-->
<% End Select %>

Each server-side include is issued immediately before any of the ASP code in the ASP page is run. That means all of the five server-side includes will be executed and their contents imported into the ASP page.

The downsides of this approach are that you are limited to a finite number of potential include files, and for each potential include file, you need to hardcode a case statement. As well, whenever you use an #include directive, the entire contents of filename are inserted into the ASP script before processing. If you have a large number of potential include files and these files contain large amounts of code, this could cause a performance bottleneck.

Another way to “fake” dynamic server-side includes is to use the FileSystemObject object model to read in the contents of the include file you are interested in inserting into your ASP page. The following code allows for a file to be inserted into an ASP script:

<% Option Explicit %>
<%
  '************************* DESCRIPTION **************************
  '* Output the entire contents of a text file, whose name can be *
  '* dynamically generated.                                       *
  '****************************************************************
  Dim strFileName, objFSO, objFile

  'Create an instance of the FSO object
  Set objFSO = Server.CreateObject("Scripting.FileSystemObject")

  'What file do we want to import?
  strFileName = "C:\InetPub\wwwroot\Adages.htm"

  'Open a text file, using the OpenTextFile method
  Set objFile = objFSO.OpenTextFile(strFileName)

  'Output the entire contents of the text file
  Response.Write objFile.ReadAll

  objFile.Close     'Close the file...

  'Clean up
  Set objFile = Nothing
  Set objFSO = Nothing
%>

Tip

Note that the FileSystemObject expects full physical filenames (C:\InetPub\wwroot\Adages.htm) and not virtual filenames (/Adages.htm).

The above code may seem like it accomplishes a dynamic server-side include, but it is a little misleading. Since the text file cannot be opened and read until the ASP script is running, if there is any ASP code within the file specified by strFileName, it won’t be executed; rather, the ASP code will be output just like any other HTML content in the file! This method is an acceptable one if you only need a dynamic server-side include to read in an HTML header or footer. However, any ASP code in the file specified by strFileName will not be executed!

With ASP 2.0, truly dynamic server-side includes were impossible. However, with ASP 3.0 and the new Execute method of the Server object, true dynamic includes are possible! Server.Execute is discussed inSection 1.4.3.

Naming your include files

When grouping common functions into a particular file to be included by other ASP pages, be sure to give the file an .asp extension. If you fail to do this, and a web surfer guesses the correct filename of an include file, the user can view the contents of the include file! Files with an .asp extension are safe from prying eyes because when an ASP page is requested, IIS steps in and executes the ASP page, turning the ASP code into HTML. Files without an .asp extension are not processed by IIS and are sent directly to the client. This can reveal the source code of your include files.

If for some reason, you must use include files with a non-ASP extension, be sure to place these files in a directory that has HTTP read permissions turned off. With the HTTP read permissions turned off, ASP pages can still import the contents of these include files, but if someone attempts to request these files directly through an HTTP-request (i.e., entering the full URL of the include file in their browser), they will receive an error message indicating that HTTP reads are not permitted.

Since developers often place constants, database connections, and common functions in include files, it is imperative that the contents of include files remain away from prying eyes.

It’s true there is not a great chance that anyone will be able to exactly guess the name of an include file. However, that does not mean your include files are safe. Jerry Walsh has detected a potential security bug that can occur when using server-side includes. This security bug happens when you create an include file that has a programmatic error in it. For example, imagine a file named /CODEREUSE/dbConn.inc that contained the following code:

<%
  Dim objConn
  Set objConn = Server.CreateObject("ADODB.Conection")
  
  objConn.ConnectionString = "DRIVER={Microsoft Access (*.mdb)};" & _
                 "DBQ=" & Server.MapPath("/MyDatabase.mdb")
  objConn.Open
%>

Note that there is an error in the above code snippet: the class ID for the Connection object is misspelled, with “Conection” missing an “n.” Many ASP sites use an include file to have one database connection file. However, making such a page with an error can yield disastrous results, as we’ll see shortly. Now, if some page on your site uses this include file like so:

<% @LANGUAGE="VBScript" %>
<% Option Explicit %>
<!--#include virtual="/CODEREUSE/dbConn.inc"-->
<%
  'Do stuff with the database connection...
%>

an error will occur when a user visits the page, since there is an error in /CODEREUSE/dbConn.inc. Specifically, the user will see the error message:

Microsoft VBScript runtime error '800a004' 

Invalid Class String

/CODEREUSE/dbConn.inc, line 3

Notice that the full path to the include file is displayed in the error message! Since the include file does not contain an .asp extension, anyone who stumbles across this erroneous page can now visit your database connection script, which contains the path to your Access database (MyDatabase.mdb). The user can now download directly!

You might think you are safe if you do not provide some mechanism for your users to reach the ASP page that uses the erroneous include file. That is, if you provide no direct hyperlinks to this page, no user will see the error message and no user will know the include file’s path. While it is true that no user will likely stumble across the ASP page, search engines may still index them!

Again, you might think that this is a minor problem and that there is an incredibly low probability that a search engine will index this odious page. However, if you go to AltaVista and enter the following search terms:

+"Microsoft VBScript runtime error" +".inc, "

you’ll find that an alarming number of pages are returned that display the complete path to an include file whose contents can be read by any visitor!

The moral of this story: don’t put files whose contents will be imported into ASP pages via a server-side include on your production web site until they have been thoroughly tested and do not contain any errors that will reveal their location. Or, more simply, just make sure you give your include files an .asp extension or place them in a directory where HTTP read permissions are turned off. Another potential solution is discussed in Chapter 3. In that chapter, we’ll look at how to have include files (and other ASP pages) handle errors more gracefully than simply spitting out an error message.

Warning

According to an article on Microsoft’s web site (http://msdn.microsoft.com/library/tools/aspdoc/iiwainc.htm), “...it is good programming practice to give included files an .inc extension to distinguish them from other types of files.” Be sure not to follow this suggestion unless you place these files in a directory that has read permissions disabled.

Protecting the contents of your include files from prying eyes

There are some simple steps you can take to ensure that your include files’ contents won’t be seen by prying eyes. The two simplest options are:

  • Create a directory for all your include files, and remove the read permissions from this directory. This will prevent HTTP read requests on the specified directory. If a web visitor attempts to retrieve any file from that directory, they will receive a Permission Denied error.

  • Give all include files an .asp extension. If a user tries to view a file with an .asp extension, IIS will attempt to process the file first with ASP.DLL and will send the resulting HTML instead of the actual source.

More about server-side includes

Server-side includes are extremely useful for developing and using code modules. Placing common code in modules allows for easier code reuse. Throughout this book, server-side includes are used quite frequently. Often classes will be created that are used among many ASP pages. Rather than copying the class definition into each ASP page, it makes much more sense to define the class in a single file and to use a simple server-side include in all of the pages that need to utilize the given class.

Since server-side includes are used so frequently throughout this book, it is important you have a solid understanding of how they work. If you are a little rusty on server-side includes, I highly recommend that you take the time to read the following two articles:

VBScript Classes

Since Version 5.0 of the VBScript scripting engine, developers have had the opportunity to use VB-like classes in their VBScript code. Classes provide for an object-oriented-like programming approach when developing ASP pages, which greatly enhances the reusability of a particular ASP page.

Classes are great for creating black-box modules. For other developers to use a black-box module, they do not need to know any of the specific implementation details; rather, they just use the black box through its publicly accessible methods and properties.

We will discuss classes in detail in Chapter 4. Classes are used extensively in Chapter 5, and Chapter 6, as we look at code reuse techniques. Since classes encapsulate complexity, hide implementation details, and function as a black box for the developer, classes assist greatly when creating reusable code.

Tip

In Chapter 5 and Chapter 6 we will be examining reusable scripts that make heavy use of VBScript classes.

Using Server.Execute

Server.Execute can also be used to modularize your ASP code. Server.Execute branches the control flow from one ASP page to another. When the page that was called via the Server.Execute completes processing, control is returned to the page that issued the Server.Execute command. Figure 1.3 illustrates the semantics of Server.Execute.

Server.Execute branches the control flow to a separate ASP page, runs the page, and returns the control flow to the original page

Figure 1-3. Server.Execute branches the control flow to a separate ASP page, runs the page, and returns the control flow to the original page

Server.Execute’s main advantage is its ability to perform truly dynamic includes. For example, using Server.Execute, you could do the following:

<%
  Dim strPage
  strPage = "/scripts/MyPage.asp"
  Server.Execute(strPage)
%>

When an ASP page uses Server.Execute to branch control to another ASP page, all of its built-in ASP objects are passed along. For example, if Page1.asp issues a Server.Execute("Page2.asp"), Page2.asp will have access to Page1.asp’s intrinsic ASP objects. Remember that the Request object is an intrinsic ASP object and that it contains the Form and QueryString collections. Since all the intrinsic objects are shared from Page1.asp to Page2.asp, when Page2.asp attempts to read from either of these Request object collections, it is reading the values from Page1.asp’s Request object collections. Now that’s a mouthful!

For example, if Page1.asp simply had the code:

Server.Execute("Page2.asp")

and Page2.asp simply had the code:

Response.Write Request("Age")

if we visited Page1.asp through a browser, entering the URL http://localhost/Page1.asp?Age=21, the Response.Write in Page2.asp would output 21.

To summarize: Server.Execute can be used in place of server-side includes when dynamic includes are needed. If you don’t need dynamic includes, and a vanilla server-side include would suffice, I would recommend sticking with the server-side include.

Using Server.Transfer

Server.Transfer can also be used to improve ASP script design, although it cannot serve as a modularization technique like Server.Execute and server-side includes. Server.Transfer is similar to Server.Execute. If Page1.asp performs a:

Server.Transfer("Page2.asp")

the control flow is transferred to Page2.asp. When Page2.asp finishes executing, control is not returned to Page1.asp. Figure 1.4 illustrates the semantics of Server.Transfer.

Server.Transfer branches the control flow to a separate ASP page, runs the page to completion, and stops executing

Figure 1-4. Server.Transfer branches the control flow to a separate ASP page, runs the page to completion, and stops executing

As with Server.Execute, when Page1.asp performs a Server.Transfer ("Page2.asp"), Page1.asp’s built-in ASP objects are passed along to Page2.asp. Being able to access the Request.Form and Request.QueryString collections from Page1.asp in Page2.asp is an incredibly useful feature, which we’ll capitalize on in Chapter 5 and in Chapter 6.

Server.Transfer improves ASP design by providing a mechanism to seamlessly move from one ASP page to another. Part of good application design is creating robust, reusable code, which usually results in the creation of several generic helper ASP pages. With Server.Transfer, we can “plug into” these helper pages, moving from one to the next. In Chapter 5 we’ll look at applying Server.Transfer to gracefully hop from one ASP page to another!

Get Designing Active Server Pages now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.