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.
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.
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"-->
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.
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.
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.
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.
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 withASP.DLL
and will send the resulting HTML instead of the actual source.
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:
“The low-down on #include s,” found at http://www.4guysfromrolla.com/webtech/080199-1.shtml.
“Security Alert—Using includes Improperly from non-Debugged ASP Pages can allow Visitors to View your source code,” found at http://www.4guysfromrolla.com/webtech/020400-2.shtml.
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.
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.
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.
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.
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.