A Programmer's Introduction to ASP

Version 0.1 -- May 2001

This document is still very much in development. Please send email to asp@jeays.net if there are any errors. Missing parts will be filled in over time. Any constructive criticism that you have is welcomed.

Table of Contents

Introduction

The purpose of this document is to introduce Active Server Pages (ASP) 2.0/3.0 to someone who is already familiar with programming. You will find complete coverage of the ASP objects as well as an overview of the most common ASP objects and techniques. I wanted to write a single document (suitable for printing or distributing) which gave fairly complete coverage of basic ASP programming.

Please note that this is not a tutorial on if statements and for loops. Also note that this has nothing to do with Application Service Providers or Egyptian snakes, and there is no discussion of ASP.NET. Sorry, if you got here through a search engine :p

ASP is a server-side technology which allows you to create HTML pages on the fly. ASP is COM-aware, so it can fully use any COM object, even Microsoft Word. ASP is a good "glue" language for sticking together the capabilities of different COM objects.

In order to run an ASP page, create a text file and type in some ASP code (there are examples later in this document). This file must be in a folder accessible to the web server. In order to run the file, navigate to the location of the file using a URL (i.e. http://localhost/myfolder/mypage.asp) and not the physical location of the file (i.e. C:\inetpub\wwwroot\myfolder\mypage.asp).

ASP is most often run using Microsoft Personal Web Server or Internet Information Server on Windows 95/98/2000/NT. ASP can also run under UNIX using ChiliSoft ASP, however I have no experience with that so I'll leave discussion of that to someone else.

A note on the examples: all code is assumed to be surrounded by the proper script delimiters, for which you have two options:

<%
' code
%>

or

<script language="VBScript" runat="server">
'code
</script>

You can set the default scripting language in IIS, using the Microsoft Management Console (MMC). If you wish to override this and use a different scripting language then you need to supply the language directive, which must be in the first line of the .asp file, as follows:

<% @language="JavaScript" %>
<%
' code
%>

Virtually all of the time you can use the <% 'code %> delimiters. The only real use for the <script> tags is if you are using both scripting language is in the same document. As you might guess, open and close script tags cannot nest and there must be the same number of open and close tags.

A Brief Guide to the ASP Environment

ASP is a DLL that extends Microsoft Internet Information Server. It is installed by default when you install IIS from the NT option pack. This means that whenever you load a file in your web browser with a .asp extension it invokes the DLL and does some server-side processing.

One of the most common beginner mistakes is not understanding the order in which everything gets executed (I'm leaving out some detail here but this is the important part).

  1. The user makes a request for an ASP through a web browser.
  2. The server receives the request and executes any server-side includes. The result of this is an in-memory ASP file.
  3. The server then parses the ASP file and executes it, resulting in an in-memory HTML file.
  4. This HTML file is sent to the user's web browser.

Two important things to note are that server-side includes happen before the ASP code is processed, therefore it's impossible to do a "dynamic include", i.e.:

if x = 1
	<!-- #include file="a.asp" -->
else
	<!-- #include file="b.asp -->
end if

This will just end up including both files (although you will get the right result). The second important thing is that all you are doing is writing out an HTML file. Therefore you can use ASP to programmatically write any sort of client-side scripting code (i.e. VBScript, JavaScript/JScript, dHTML or CSS).

It's possible to do a sort of "dynamic include" with an ASP 3.0 feature, Server.Execute, as follows:

if x = 1
	server.execute "a.asp"
else
	server.execute "b.asp"
end if

This will transfer execution to the appropriate ASP file and then return it to the file that called it when it has finished.

If you are having trouble getting an ASP to work, note that cookies must be enabled on the browser. This is because a session key is stored as a temporary cookie (one that expires when the browser is closed). This session key is used by the server to maintain state in your pages.

Note that since ASP is a server-side technology, you cannot use any sort of interactive code, such as msgbox or alert. Also, ASP is completely unaware of the client's browser. This means that you cannot change focus between form elements, clear form fields or activate onClick events, and so forth. However, you can use any client-side JavaScript or VBScript outside your ASP code blocks as you would with any regular HTML page. One technique that sometimes proves powerful is to use ASP to dynamically write client-side JavaScript.

Server-Side Includes (SSI)

ASP supports the same server-side includes as you may be familiar with if you have used the Apache web server. SSI are written as magic HTML comments, of the form <!-- #function attribute="argument" -->.

If you wish to make use of SSI but you do not need to do any scripting, you do not need to make your files .asp files. What you need to do is configure IIS so that files with a particular extension (usually .shtml, but it could be anything) are associated with a DLL called ssinc.dll. This will allow you to do SSI with a lower overhead that if the file is treated as an ASP.

It bears mentioning again that Server-Side Includes occur BEFORE any ASP code is processed. Therefore instead of using the FileSystemObject to programmatically determine a file's size, you could do something like this:

Dim mysize
mysize = <!-- #fsize file="" -->
if mysize > 1024 then
	response.write "this file is larger than a kilobyte"
else
	response.write "this is a very small file"
end if

That being said, it's not very elegant code and I'd recommend not doing this sort of thing as a matter of course.

config

config is used in conjunction with flastmod and fsize in order to change the format of the result. A full list of available formats is kind of long but you can find one at another site. A suitable format for a file modification date might be <!-- #config timefmt="%Y %m %d" -->, which returns the date in YYYY MM DD format. A suitable format for a file size might be <!-- #config sizefmt="bytes" --> which returns the number of bytes.

echo

The echo SSI can be used to output environment variables, such as HTTP_USER_AGENT or REMOTE_ADDR. This particular SSI originated on UNIX systems and the same functionality is obtained by using the Request.ServerVariables collection. An example of this in use is <!-- #echo var="HTTP_USER_AGENT" -->./p>

exec

exec can be used to execute commands on the server and return the result to the browser. Of course, this is a perfect opportunity for shooting yourself in the foot if you have dangerous commands here. It should be noted that using exec is not really something that you should be doing regularly. Regular ASP program logic is almost always the preferable solution. There are two permissible attributes to this SSI function, cmd for running commands as if from the prompt, and prog for running executable programs.

flastmod

flastmod returns the last modified date of a file. It's useful for inserting "This page was last modified on: ..." text on your page. If you want to alter the format of the date, you need to use the config SSI beforehand. Here's an example: <!-- #flastmod file="" -->. By entering no file name it will return the last modified date of the current file. You can get the last modified date of another file by specifying a different file name. You can also use the virtual attribute instead of file to specify a different file using a virtual path, i.e. one starting with a /.

fsize

fsize returns the file size of a given file. If you wish to format the result, you need to use config beforehand. The following code, <!-- #fsize file="" --> outputs the file size of the current file. As with flastmod, you can specify any file, or use virtual to specify a virtual path to a file.

include

include is probably the most frequently used SSI function. It allows you to dynamically insert the entire contents of a file into another at the time that it is run. Include files are most often used for page headers or footers, and for storing program code that needs to be run on more than one page. There are two attributes. file specifies a relative path, such as <!-- #include file="inc_functions.asp" -->, which references a file in the current directory. virtual specifies a full virtual path starting with /, such as <!-- #include file="/includes/myapp/inc_functions.asp" -->.

Some older Microsoft documentation recommends naming include files with a .inc extension. This can be a bad idea if IIS is not configured to handle .inc files as ASP files. If someone goes directly to that .inc page, then they will be able to view the source code for that file.

If you have ASP code in your include file, make sure that if that code is within script tags, then the include function is outside script tags, and vice versa. One final note, you cannot have circular includes (file A includes file B which includes file A...) for what should be obvious reasons.

A few simple pages

Hello World

The traditional "Hello world" example is very simple:

'VBScript
resPonse.WRITe "hello world"

or

'Javascript
Response.Write("hello world");

Note that VBScript doesn't mind how the method name is capitalized, while the JavaScript code requires that particular capitalization. Also recall that the VBScript call is actually a subroutine and as such parentheses around the string argument are optional.

This uses the Response object's Write method to print text directly to the browser. If you run this and do a "view source" you will see simply:

hello world

Note that tags like <html> and <body> were NOT added to this file.

One of the little ASP shortcuts is using = for Response.Write, within a one-line block of script. The hello world program above could also be expressed as <%="hello world"%> (that's the entire file).

You can write a large number of useful ASP pages with plain VBScript and JavaScript and the Response.Write function to print HTML text. Of course, you need other objects to interact with the file system, databases, the web browser, email and other data sources.

Multiplication Table

In this example we'll build a basic multiplication table to show how we can just embed some ASP code inside regular HTML. Note that you can always finish a given part of the script by using the close script tag, and then have regular HTML or text. In this example I print <tr> using HTML and I print </tr> using ASP. These two methods are functionally the same -- they both get printed once for each iteration of i. Note that there is a small performance penalty incurred for each block of script code. Therefore it is generally preferable to have fewer rather than more blocks of code delimited by the script tags.

The other important point is that this shows that the ASP code all gets executed before the HTML page is generated. Despite the fact that we have a plain <tr> tag that is NOT in script, it will get printed out once for each iteration of the i loop.

<html>
<body>
<%
Response.Write("<table border=1>")
For i = 1 to 10
%>
	<tr> <!-- open table row using HTML -->
<%
	For j = 1 to 10
		Response.Write("<td>" & i * j & "<td>")
	Next
	Response.Write("</tr>") ' close table row using ASP
Next
Response.Write("</table>")
%>
</body>
</html>

I wrote the table row open and close that way for the purposes of this example only. If you write code like this that other people ever read they will be very confused. Try to retain parallelism in your code.

You can easily use ASP to write quick prototypes or simple scripts to do one-off jobs or calculations, although it's probably far from the best language for these types of things it will do in a pinch.

Mass converter

Our final simple example will be a program to convert between kilograms and pounds. It will consist of just one ASP that submits to itself. Later on I recommend that this probably isn't the best way of writing this program but it is instructive nonetheless.

There are three basic "states" that this page can be in: the user's first look at the form (no submit has taken place and the form is being displayed), an error state (a submit has taken place but the user entered invalid input), or a success state (a submit has taken place, the user entered valid input and the result is being displayed).

There are a few ASP methods used here other than Response.Write. The Request.Form calls get the value entered in the form. Request.Form("mass") returns whatever the user entered in the text box. Request.Form("conversion") returns either "i2m" or "m2i" depending on which of the radio buttons was selected. Request.Form("submit") returns "Calculate!" (the form element's value) if the submit button was clicked. Finally, Request.ServerVariables("URL") returns the URL of the current page, so as to make the form submit to itself. Of course, if you save this to a file and call it "mass.asp", you could just replace that with "mass.asp". It is best to write your code in as general a way as possible, so I would recommend not explicitly writing the filename wherever possible. This will make it easier if you ever rename your file or if you wish to copy some segments of your code.

Const KG_IN_POUND = 0.45359248

function validate(amass)
	if isnumeric(amass) then 
		if amass > 0 then ' an arbitrary restriction
			validate = true
			exit function
		end if
	end if
	validate = false
end function

function metric_to_imperial(amount)
	metric_to_imperial = amount / KG_IN_POUND
end function

function imperial_to_metric(amount)
	imperial_to_metric = amount * KG_IN_POUND
end function


' start of program
Dim errormessage, mass
mass = Request.Form("mass") ' just to save some typing.

if request.form("submit") > "" then

	if validate(mass) then
		Select Case request.form("conversion")
			Case "m2i"
				response.write "<p>" & mass & " kg is equivalent to " & metric_to_imperial(mass) & " lb.</p>"
			Case "i2m"
				response.write "<p>" & mass & " lb is equivalent to " & imperial_to_metric(mass) & " kg.</p>"
		End Select

	else 
		' create a text string for an error message
		errormessage = "<p>Sorry, the mass must be a positive number (you entered " & mass & ")</p>"
	end if

end if

<html><head><title>Mass Conversion</title></head><body>
<%=errormessage%>
<form action="<%=Request.ServerVariables("URL")%>" method="post">
Quantity: <input type="text" name="mass">
<input type="radio" name="conversion" value="i2m" checked>Imperial to Metric 
<input type="radio" name="conversion" value="m2i">Metric to Imperial
<input type="submit" name="submit" value="Calculate!">
</form>
</body>
</html>

Please note that there are MANY ways of writing the previous code. As you use ASP over time you will find which style(s) you prefer. One brief comment, I specified that one of the radio buttons should be checked by default, which means that no matter what the user does, one of the buttons will be checked when the form is submitted. If I hadn't specified that one should be checked by default, then I would have to consider that possibility in my Select Case statement (admittedly, one should pretty much always have a Case Else option for unexpected input). One other note, if there is no error then errormessage will just be an invisible empty string.

Intrinsic ASP Objects

ASP would be useless if it couldn't interact with the web browser and the web server. There are five programming objects that are always accessible from an ASP page, Request, Response, Server, Session and Application. These objects are implicitly created if you use any of their methods. If you are using ASP 3.0, there is a sixth object available, the ASPError object.

The only method from one of these objects that has been introduced so far has been the Write method from the Response object, which writes text to the outputted HTML file. In general, the Response object adds information to what the server sends back to the client's browser. The Request object obtains extra information from the client. The Server object allows the programmer to instantiate objects through the web server, as well as perform some utility functions. The Session object controls the ASP Session, which is by default defined as any page views from one client such that 20 minutes without visiting a page have not elapsed. Finally, the Application object deals with information and variables that apply to all clients concurrently accessing the ASP site.

Although this isn't a programming tutorial, I will take a moment to review Collections, because they form an integral part of these built-in objects. A Collection is basically an associative array whose elements can be iterated over using a for each loop in VBScript or a for loop in Javascript. The following is a short piece of code that prints out all of the items in the server variables collection, which includes information like the IP address of the client's browser and the name of the server:

for each item in request.servervariables
	response.write item & ": " & request.servervariables(item) & "<br>"
next

item is a variable which takes the name of each of the keys in the ServerVariables collection and then the response.write method is used to write the information from the browser. Note that a collection from the Request object is used to obtain information FROM the client and the Response object is used to send information TO the client.

The next few sections provide brief explanations of all the methods and properties of the five ASP objects. Note that if you are using other platforms other than the native Microsoft IIS/PWS, not all of the methods and properties may be available.

The Application Object

The Application object contains objects and variables that are available to all users of a web application.

Contents (collection)

Application.Contents is a collection for storing variables or objects that are available to all current ASP Sessions.

Lock (method)

If you call Application.Lock in your code, it prevents any other ASP page within your application (i.e. used by another client) from accessing the Application.Contents collection. This should be used whenever your are modifying one of its members so that bogus values are not read if there is a race condition between two users.

OnEnd (event)

This event is supposed to occur when the application has ended, which basically means when IIS is shut down. This is not completely reliable, so it is best not to use this event for code that absolutely must be run. This event is usually called in the global.asa file.

OnStart (event)

This event occurs when the application is used by any user for the first time. It is usually called in the global.asa file.

StaticObjects (collection)

This collection contains all of the objects that were added with the <object> tag in the global.asa file.

Unlock (method)

Calling Application.Unlock releases the lock created by Application.Lock. You should always use these in a pair, as in this example:

Application.Lock
Application("answer") = 42 ' modify a variable
Application.Unlock

The ASPError Object

ASPCode (property)

This property holds a string which holds the error code generated by IIS.

ASPDescription (property)

This property returns a more detailed description of the error, if it's an ASP error.

Category (property)

This property returns a string representing the source of the error: ASP, the scripting language or a component.

Column (property)

This property returns the column number at which the error occured in the line that caused the error.

Description (property)

This property returns a short description of the error (i.e. "Type mismatch"). As an aside, as a programmer, it's easy to forget that such phrases are technical jargon and are completely incomprehensible to non-techies. Check out Error Messages for all sorts of amusingly bad error messages, including a true "type mismatch" story.

File (property)

This property returns the name of the file that contained the line of code which resulted in the error.

Line (property)

This property returns the line number of the line which caused the error.

Number (property)

This property holds the COM error number (same as Err.Number in ASP 2.0).

Source (property)

This property returns the line of source code that caused the error (the line numbered with ASPError.Line). This should probably not be displayed on a production site, partially because it could reveal your business logic, and partially because it would confuse the majority of people viewing your site.

The Request Object

The Request object is used to retrieve information about the client's request of a web page. It is used most often to access one or more of five different collections: Form, QueryString, ServerVariables, Cookies and to a lesser extent, ClientCertificate.

A shortcut that is often used is Request("key"). This takes the value of Request.QueryString("key") or Request.Form("key"), whichever is populated.

Form (collection)

The Request.Form collection contains the keys and values of all form fields if the previous ASP page was a form that was submitted to the current page. The keys of the collection are the form element names and the values are the values that the user entered. For example, if you had a form item <input type="text" name="test"> and the user entered I like forms and then submitted the form to a second page, then Request.Form("text") would contain the string I like forms.

In order to test whether the user has entered text into a form element, compare it to the empty string, i.e.:

if request.form("myfield") = "" then
	response.write "no input"
else
	response.write "input was " & request.form("myfield")
end if

Do not use IsNull or IsEmpty, these sound like they might be suitable for this problem but that is not what they are for. IsNull tests for the special null value and IsEmpty tests to see if a variable has been initialized.

Values inputted into HTML form elements will be available with the Request.Form collection if the POST method was specified. If the user is going to be entering a password or other sensitive information, you will need to use the POST method so that the password does not appear in the URL. Note that using POST alone does not provide any real security -- you would need to investigate SSL or other security methods if you are transferring private information over the Internet. Heavy-duty security is definitely beyond the scope of this article, check the links section near the bottom of this document for some pointers to good resources on security.

QueryString (collection)

The QueryString collection contains any key/value pairs that are appended to the URL after a ?. For instance, if the URL of the current page is http://localhost/test.asp?name=mark&favorite+color=blue then Request.QueryString("name") would be mark and Request.QueryString("favorite color") would be blue. Recall that both + and %20 in a URL represent a space. Request.QueryString contains the entire query string, name=mark&favorite+color=blue in this case. You could parse it yourself, if you wanted, however this is rarely necessary.

If you have an HTML page with a form and you submit it using method="GET", then it will write all of the values that the user enters to the query string. It is up to the developer to decide whether GET or POST will be used. One advantage for using GET and the QueryString collection to access user-submitted values is that users can bookmark a page that may include a search result.

Note that if you wish to specify a document fragment with # in a URL that includes a query string, it goes at the very end, i.e. http://localhost/test.asp?x=1&y=2#middle.

ServerVariables (collection)

The ServerVariables collection contains general information about the browser and the environment. A complete list of these can be obtained on the web by running the code snippet under the Intrinsic ASP Objects heading. I'll outline some of the more interesting server variables here. In general, these are accessed with Request.ServerVariables(servervariable), where servervariable is a string containing the name of the variable.

HTTP_USER_AGENT contains information about the user's browser. A very trivial browser sniffer could be written as follows:

agent = Request.ServerVariables("HTTP_USER_AGENT")
if instr(agent,"MSIE") then
	Response.Write "Internet Explorer"
elseif instr(agent,"Mozilla") then
	Response.Write "Netscape"
else
	Response.Write "Other"
end if

I have not made any serious effort to do this properly because it has already been done many times. Search the Internet for "browser sniffer" and you can find many implementations. In general, there is no point in writing a perfect browser sniffer because the user agent string can be spoofed. For the purposes of outputting slightly different HTML depending on the assumed target web browser, the above code may well be all you need.

REMOTE_ADDR contains the IP address of the client's computer. Again, this (and just about everything on the Internet) can be spoofed so do not base too much on this value. Note that this is a poor way to determine if a user is unique because all people behind a common firewall may appear to have the same IP address.

AUTH_USER contains the network logon of the user, if an authorization has occured. This seems to be an appropriate point to briefly discuss the way authorization occurs. There are three settings for web pages under IIS: Anonymous Access, Basic Authentication and NT Challenge/Response. Anonymous Access means that any user on the Internet can access the resource. There is no attempt made at authentication and Request.ServerVariables("AUTH_USER") is not set. If Basic Authentication is used, the user is prompted to enter a user name and a password. The password is Base-64 encoded (not a secure encoding) and then validated. If the user is known to the system and has the appropriate file permissions set on the file, Request.ServerVariables("AUTH_USER") is set to the user name. This method works with both Netscape and Internet Explorer. The final method is NT Challenge/Response. This transparently sets Request.ServerVariables("AUTH_USER") to the user's network user name, without any input from the user. Unfortunately, this only works using Internet Explorer.

More than one type of authentication can be enabled for a given page. If you are coding an Intranet application and know that everyone is using IE then NT Challenge/Response may be suitable. Otherwise you can enable both Basic Authentication and NT Challenge/Response and take your chances with people sniffing packets on the network and decoding the Base-64 encoded passwords emitted with Basic Authentication, if someone uses Netscape. Note that if you have Anonymous Access enabled, then this will be used, even if other authentication methods are also enabled.

One other interesting server variable is HTTP_REFERER, which contains the URL of the referring page. Note that "referer" is a spelling error -- one of those errors that once made are impossible to correct without breaking countless old programs.

Cookies (collection)

The Cookies collection provides access to browser cookies. The Request Collection allows you to access cookies, while the corresponding Response.Cookies methods allow you to set or delete them.

There are two methods associated with the Request.Cookies Collection. Contents is the default method, which gives access to the value of the cookie for a given key, and HasKeys returns true if the key has subkeys associated with it. Here is some code to read all of the cookies available to a given page:

for each item in request.cookies
	if request.cookies(item).haskeys then
		response.write item & " has the following keys: <ul>"
		for each subitem in request.cookies(item)
			response.write "<li>" & subitem & ": " 
			response.write request.cookies(item)(subitem) 
			response.write "</li>"
		next
		response.write "<ul>"
	else
		response.write item & ": " & request.cookies(item)
	end if
next

Note that request.cookies(item) represents another collection -- the collection of subkeys for the given cookie. Check the Response.Cookies section for more info on cookies.

ClientCertificate (collection)

The ClientCertificate collection can be used to get information about any certificates that the client has sent. These are the certificates used for SSL which are required for connecting to the secure port (usually 443) using https://.

TotalBytes (property)

TotalBytes is a property containing the number of bytes that the client is sending to the server. This is usully used in conjunction with the BinaryRead method. You should not need this method writing basic ASP/HTML forms.

BinaryRead (method)

BinaryRead is a method that you can use to obtain binary data (such as if the user uploads a file. Warning: this is notoriously difficult to use correctly. Unless you are an ASP expert or your time is worth $1.00 an hour, you will probably find it cost effective to buy or download a component to read binary files for you. That being said, BinaryRead returns a SafeArray containing the data. You will need to write a COM object to do this since VBScript does not support SafeArrays. Along with TotalBytes, this method is not normally used for regular ASP/HTML forms. Note that if you are allowing users to upload arbitrary binary files to your server you had better be extremely careful about what might be in them.

One other thing, using BinaryRead and the Form collection on a given page are mutually exclusive. That is, if you use one, then you will not be able to use the other later in the page.

The Response Object

AddHeader (method)

This method is used to add custom HTTP headers, in the same way that regular headers are added (e.g. Server: Microsoft-IIS/4.0). This not very commonly used.

AppendToLog (method)

This method adds a comment to the log that IIS creates of each page request. I would generally not recommend using this function as for a large web site, logs tend to get extremely huge and a pain to process. It may easier to create your own logging mechanism using the FileSystemObject.

BinaryWrite (method)

This method is used to write binary files such as images to the client. Using this is definitely advanced stuff. Check the documentation for more details.

Buffer (property)

Response.Buffer is a boolean variable that you can use to buffer the output. By default with IIS 4.0 this is set to false, meaning that output on the client's browser appears at the point that it is calculated. For instance, if you have a very slow loop that prints out some result every second, then the user will see a new result every second. Setting this property to true means that the output HTML is stored and only output to the client's browser when you call Response.Flush. In IIS 5.0 the property is set to True by default.

I generally use unbuffered output instead of buffered output. My reasoning is as follows: the only case in which the user would notice anything either way is if the output is taking a long time to generate. Unbuffered output at least shows the user that some progress is taking place, which is reassuring. I believe there are some small performance improvements in using buffered output, so this is one tradeoff you will have to assess if your pages get very popular or if they take a very long time to load.

CacheControl (property)

This propety controls whether the page is can be cached by proxy servers. Set the value to either "Public" to allow caching, or leave it at the default "Private" to disable caching.

Charset (property)

The Charset property is used for those pages that require a different character set. Setting this tag is equivalent to using a meta tag of the form <meta http-equiv="content-type" content="text/html;charset=[charset]">. If you use regular ASCII files you should not need to use this property.

ContentType (property)

This is used to set the Content-type of the response. If you have programmed CGI before you probably had to include this explicitly. ASP does this automatically, adding "Content-type: text/html" by default so you generally do not need to worry about this.

Clear (method)

Response.Clear empties whatever is in the buffer. This only does anything if Response.Buffer is True. You should generally not need to use this method. Use conditional program logic to determine whether or not you display HTML, not buffering.

Cookies (collection)

The Cookies collection allows you to write cookies to the client's browser, which you can later read with the Request.Cookies collection. Each cookie has several properties: Expires (expiry date of cookie), Secure (boolean indicating whether it must be accessed through https), Domain (domain which can use the cookie) which can be set by accessing the cookie by its key. Perhaps an example would be clearest.

response.cookies("test") = "hello"
response.cookies("test").value = "hello" ' same thing as previous
response.cookies("test").secure = False
response.cookies("test").expires = "+3d" ' in three days

If you do not specify an expiry date, the cookie will expire when the user closes the browser. This is a common source of errors so be sure to specify a suitable expiry date. Always be aware that some users will not have cookies enabled. You can check to see if a user has cookies enabled by simply writing a cookie and then testing to see if it had the value you just set it to.

response.cookies("test") = "hello"
if request.cookies("test") = "hello" then
	response.write "cookies enabled"
else
	response.write "cookies disabled"
end if

Note that this does not leave a permanent cookie because we did not specify an expiry date.

In general, try to avoid setting too many cookies on the user's browser and give reasonable expiry dates. You can delete a cookie by setting its value to an empty string.

End (method)

Response.End simply halts the execution of the ASP at that line. This is often used in conjunction with error handling. If a serious error is detected you can stop the web page and perhaps ask the user to click the back button and re-enter information, for example. Using this method will produce a partially finished page if you have buffering turned off (which is the default). Note that you do not need Response.End to end your script -- it will end when all of the script code is execute automatically.

Expires (property)

The Expires property controls how many minutes the page can be cached for. It is roughly equivalent to the HTML meta tag <meta http-equiv="expires" content="January 1, 2000, 00:00:00 EST">, but you can specify the date in relative terms, rather than worrying about the exact date. The format of the property is a plus sign followed by a number, followed by a time period, either "s" (seconds), "n" (minutes), "h" (hours), "d" (days), "m" (months) or "y" (years). For example, Response.Expires = "+3h" would make the cookie expire in 3 hours. If you want to set it using absolute terms, use the ExpiresAbsolute property.

ExpiresAbsolute (property)

See the Expires property above, except this allows you to specify an actual date and time. You set this property to a VB Date, i.e. Response.ExpiresAbsolute = "2000/01/01 00:00:00". Generally speaking you will probably find yourself using Expires more often that ExpiresAbsolute.

Flush (method)

Response.Flush is used to write the contents of the buffer to the client's browser. This can only be used if Response.Buffer is set to True. Here's a sample buffering strategy (not optimal or anything, just a simple example). Let's write out the first 10000 or so even numbers for something to do. I will use both a buffering and a non-buffering strategy and compare the two for speed.

Dim a

' do it with buffering
Response.Buffer = True
a = 0

start = Timer()
For i = 1 to 10000
	Response.Write a & " "
	a = a + 2 ' why did they "forget" +=, -= etc. in VB???
	If i mod 1000 = 0 Then
		Response.Flush
	End If
Next

Response.Write "<p>Buffered this took " & FormatNumber(Timer()-start,2) & " seconds.</p>"


' now do it without buffering
Response.Buffer = False
a = 0

start = Timer()
For i = 1 to 10000
	Response.Write a & " "
	a := a + 2
Next

Response.Write "<p>Unbuffered this took " & FormatNumber(Timer()-start,2) & " seconds.</p>"

On my system the buffered one took X seconds and the unbuffered one took Y seconds. Rule of thumb: if you have a lot (say 500 or more) of Response.Write calls then use buffering to speed things up.

IsClientConnected (property)

This property indicates whether or not there is an active HTTP connection between the server and the client. This is a low-level operation and it is generally not very reliable.

PICS (property)

You can use this property instead of an HTML meta tag with the PICS information. If you are unfamiliar with this it is a standard for self-rating pages on the level of naughty stuff on your page. So if you're running a pr0n site then you may need to look into this further.

Redirect (method)

The Redirect method sends an HTTP redirect (code 302) to another page. It is roughly equivalent to the HTML meta tag <meta http-equiv="refresh" content="0;URL=http://someserver/somepage.htm">.

One of the ASP errors that everyone makes several times when learning ASP is attempting to do a redirect after having written out HTML to the browser. The message is: The HTTP headers are already written to the client browser. Any HTTP header modifications must be made before writing page content.. If you have already used Response.Write in your ASP code or you have any plain HTML written, you cannot do a Redirect. Note that even an HTML comment will cause this problem. The solution is simply to place your code after the Redirect. If you need to compute something, then test its value and either Redirect or continue processing, you can always place the text that you were going to write out into a string, and then write it out all at once after you make the decision about whether to redirect.

Status (property)

This property contains the HTTP status. This includes the familiar 404 for page not found, etc.

Write (method)

The Response.Write method prints output to either the client's browser or to the buffer for future writing with Response.Flush. You can add extra newlines to the HTML source by including the vbcrlf constant if you are using VBScript. This can make sifting through the HTML page source (a very useful debugging technique, by the way) a little easier. Here's a short example:

Response.Write "hello" & vbcrlf
Response.Write "world"

will produce

hello
world

in the source (and hello world in the browser window, because browsers treat all consective whitespace as equivalent to a single space character).

If you have to use Response.Write many times, it can be more efficient to build up a long string instead of making a call to Response.Write for every line. So instead of code like this:

' example 1
For i = 1 to 2000
	reponse.write i & " " 
Next

consider writing it like this instead:

' example 2
dim result
result = ""
For i = 1 to 2000
	result = result & i & " "
Next
response.write result

Example 2 runs faster on my system, but if I change it to 10000 iterations, Example 1 runs faster (because concatenating very long strings is expensive). It's best to run some tests using Timer() to see how long these types of operations take. In general, however, prefer fewer Response.Write calls.

The Server Object

CreateObject (method)

CreateObject is probably the most frequently used member of the Server object. Surprise, surprise, it is used to instantiate COM objects on the server. As a result, the component may not have any graphical or interactive elements. Note that CreateObject is a relatively expensive method, so be sure that you need and use each object you create. Also, each object you create should be set to nothing after you have finished using it, to free the object for reuse.

Here's an example of this in action. We will create a mythical object, use a function and then destroy it.

Dim obj
Set obj = Server.CreateObject("Macrohard.MOC")
obj.DoStuff()
Set obj = nothing

This is the general course of action you will follow when using Server.CreateObject. Also note that you can supply a 128-bit GUID instead of the ClassID as the parameter to the method, if that's what you prefer.

Execute (method) (ASP 3.0 only)

The Server.Execute method runs another complete ASP page and then returns to the current page at the next line. This can be used to do "dynamic includes" which were not really possible with ASP 2.0. Compare this with Server.Transfer. The only parameter for this method is the URL of the ASP script that you want to execute.

GetLastError (method) (ASP 3.0 only)

This method returns an error object associated with the last error that occured.

Here's an example:

Dim myerrorobj
Set myerrorobj = Server.GetLastError
response.write "The last error occured on line " & myerrorobj.line
Set myerrorobj = nothing

HTMLEncode (method)

This method is used to escape the special HTML characters, <, > & and ", like I had to when writing this document. To give a quick example, Server.HTMLEncode("<this> & <that>") would return "&lt;this&gt; &amp; &lt;that&gt;" (the HTML for that is very messy).

MapPath (method)

This method takes a virtual path, i.e. /myfolder/mysubfolder/myfile.htm and converts it to the corresponding physical path on your web server, perhaps D:\inetpub\wwwroot\myfolder\mysubfolder\myfile.htm. Be sure that your virtual path begins with a /. This function does not accept relative paths to your file. Note that the physical path of the current page is available with Request.ServerVariables("PATH_TRANSLATED"), or even Server.MapPath(Request.ServerVariables("PATH_INFO")) if you like extra typing. The PATH_INFO variable returns the virtual path of the current page while the PATH_TRANSLATED one returns the physical path.

ScriptTimeout (property)

This read/write property allows you to set the number of seconds that the script will run before timing out. By default this is 90 seconds. Typically, however, you should not be writing code for which the user has to wait 90 seconds. Note that this amount is in seconds but the Session.Timeout amount is in minutes. This property is extremely straightforward to use: Server.ScriptTimeout = 30 would stop the current page from running after 30 seconds.

Transfer (method) (ASP 3.0 only)

The Server.Transfer method tranfers execution to a new URL of an ASP that is specified as a parameter. Control is not returned to the page in which the command is executed. This is similar to Response.Redirect except it happens all within IIS and state is maintained (therefore you can still access form elements and other ASP variables). Compare this method with Server.Execute.

URLEncode (method)

Server.URLEncode is used to escape certain special characters from a URL. Most of the non-alpha-numeric characters are escaped by replacing each character by a percent sign and then a two-digit hex number representing the ASCII value of the character. For instance, space is encoded by %20 (or a plus sign actually, this is a special case), which when converted to decimal gives 32, which is the familiar (for some!) ASCII value for the space.

One of the great annoyances of VBScript is that there is no corresponding URLDecode. While the perl wizards have the at-first cryptic s/%([\da-f][\da-f])/chr(hex($1))/ig to fix this, VB programmers have to either write their own method or borrow JavaScript's built-in function, unescape, for this (highly recommended). Simply write a wrapper function that calls unescape with a string parameter in JavaScript, specifying runat="server" and call this from your VBScript code.

This function can be useful if you are creating your own URLs with querystrings programmatically. Note that if a user fills out a form and you specify method="GET", the web browser will automatically escape any special characters that the user might have entered, so you do not have to worry about that.

URLPathEncode (method)

Server.URLPathEncode is an undocumented method which is quite similar to Server.URLEncode but is used specifically to encode path names. The differences are as follows: URLPathEncode encodes space as %20, not + like URLEncode, and it doesn't encode the following characters at all (but URLEncode does): !#$&*+-./:?@.

The Session Object

The concept of an ASP session is often difficult to describe, especially to non-programmers. A simple definition is as follows: a user's session starts when upon the first visit to an ASP page that is a part of an application. An application is defined as one or more related pages, usually in a folder and its subfolders. The session ends if the user does not visit any of the pages in that site for 20 minutes (or some specified amount). Sessions are implemented using cookies on the client, so if they are not enabled then Session variables will not be available.

Sessions are a source of many scalability problems. I will not write at length about this since it has been done many times before, such as on LearnASP and in Developing ASP Components. The quick summary is: Do not store objects in session. The basic reason is that due to the threading model of ASP, if you have multiple people accessing the same object, then the use of the object is serialized. In other words, each user has to wait for their turn to access the object.

In general, using Session variables of any kind is poor programming style. It's akin to global variables -- not really incorrect, but it's usually a sign that the code is inelegant and that maintenance and enhancements may be difficult. If you need to pass information from one form to another, there are many possibilities -- hidden form fields, the query string of the URL or cookies, for example.

Abandon (method)

Abandon is used to terminate a session programmatically. This causes all of the Session variables to be lost and resources to be reclaimed. This does not occur until the current page has finished, however, so you cannot use it to clear all the Session variables within a page.

It is not usually necessary to use this method, as the session times out automatically after a short while. You could, for example, call this after a user logged out of password-protected application, since they could not be using the Session variables after they had logged out.

Contents (collection)

The Contents collection is where "Session variables" are held. In VBScript this is the default member so if you wanted to get the "test" member of the collection you could just write Session("test"). As with collections in general you can use the HasKeys and SubKeys methods. As mentioned above, do not store objects here. You can enumerate Session variables in the standard way with the For Each syntax. Session variables are deleted by setting the item equal to the empty string, "". They are also deleted automatically when the session is over.

CodePage (property)

The read/write CodePage property refers to the character set used by ASP. This would be useful for internationalization along with LCID. If you are using ASCII you will probably never need to touch this.

LCID (property)

LCID is short for "locale identifier". This is ASP's attempt to provide some sort of internationalization. The default for the web site is set using IIS. If you wanted to set the LCID to French Canadian, you could do that with Session.LCID = 3084.

I have not seen a complete list of locale identifiers so I decided to write one. This isn't very elegant code in that it expects trapped errors, but it prints out all of the valid locale identifiers and the date, time and a number for each locale. If only there were a way to display some sort of textual description for each of these locales as well.

Const maxLCID = 65535 ' maximum number to check
Dim today, number, LCID
today = Now()
number = -1234567.123456
response.write "<table border=""1"">"
response.write "<tr><th>LCID</th><th>General Date</th><th>Long date</th><th>A number</th></tr>"
for LCID = 0 to maxLCID
	on error resume next ' this clears err.number
	Session.LCID = LCID
	if err.number <> 0
		' LCID could not be set so do not print anything.
	else
		' LCID ok, print a row.
		response.Write "<tr><td>" & LCID & "</td><td>"
		response.write formatdatetime(today,0) & "</td><td>" 
		response.write formatdatetime(today,1) & "</td><td>" 
		response.write FormatNumber(number,4) & "</td></tr>"
	end if
next
response.write "</table>"

It is possible that this script will take too long to run if you have a slow web server. If this is the case, try adding Server.ScriptTimeout = 180 (3 minutes) at the top of your script.

Update: I found a site which lists all of the LCIDs and shows how numbers, dates and currencies are formatted. Check out ASPRing.com.

SessionID (property)

This is a read-only property that returns the SessionID that IIS is using to identify the user's session. You can use this to create your own session-tracking statistics or as a key into a database if you have a session-drive application. It is guaranteed that no two concurrent sessions will have the same SessionID but that is the only guarantee of uniqueness. It cannot be used as a unique identifier of sessions over time.

StaticObjects (collection)

This is a collection containing the objects that have been created using HTML <object> tags. It can be manipulated as a regular collection.

Timeout (property)

The Session.Timeout read/write property controls the length of time that a session remains active during inactivity by the user. The IIS default is 20 minutes. Increasing this amount of time could prove to be a load on the server, since it must maintain information for each user, while decreasing this amount of time will add to the annoyance of the user if they get logged out of an application or lose information when their session ends. You must consider this tradeoff when designing a large ASP site.

Choosing a Scripting Language

At the risk of getting boatloads of flame mail, I would recommend using VBScript rather than JavaScript. There are certainly good arguments for each of the two but my main reasoning is as follows:

The pro-JavaScript camp usually leans on the existence of an "eval" function, better array handling, the fact that it's probably faster and the existence of built-in regular expressions from the start. However, you can call a function in language x directly from language y! So if you're writing in VBScript and you need to eval something, you can just write a wrapper function (I'll write out the full file in this example):

<% @language="VBScript" %>

<script language="JavaScript" runat="server">
function do_eval(anExpr) {
	return eval(anExpr);
}
</script>

<%
response.write do_eval("2+3")
%>

Running this file will just print "5", the result of the expression.

So the moral of the story is, write primarily in the easier language for you and borrow from the other language if you need a particular feature. As for the speed difference, it probably won't make much of a difference for most appliations. One of the points that people seem to miss concerning the speed of a scripting language such as JavaScript or VBScript is that if you're really worrying about the time it takes to concatenate 100,000 strings then you shouldn't be using a scripting language. Write a COM object or choose a different solution.

The FileSystemObject

The FileSystemObject is an COM interface to the file system. You can use it to manipulate text files in any way, including editing their contents and moving, copying and deleting them. It also gives you access to folders, drives (both network and local) and file information. The contents of files are manipulated using a TextStream object. I will use a few of the more common methods and functions in this section, and hopefully give you a feel for using the FileSystemObject (FSO), but I will not give exhaustive coverage (unless I get a lot of emails about it). Refer to MSDN or one of the good ASP books for complete details.

Unlike some other popular COM objects which let you access methods by creating different top-level objects, you always need to start by creating a FileSystemObject, something like this:

Dim fso
set fso = server.createobject("Scripting.FileSystemObject")

This allows you to use all the associated methods. For example:

set myfile = fso.getfile("C:\myfolder\test.txt")
response.write myfile.size

would print out the number of bytes in the specified text file. Note that you create another object using set. This is a File object, which represents, you guessed it, a file. There exists also a Folder object, representing a folder or directory, and a Drive object, which represents a local drive, mapped network drive, or UNC share.

Let's try a more interesting example. We'll print out a list of all files in a given drive that are bigger than a certain number of bytes. There is no built-in recursive way of listing directories so we will have to write our own. If you're not comfortable with the concept of recursion, try looking for some examples on the web.

' print files larger than this number of bytes
const limit = 1000000 

dim fso
set fso = server.createobject("scripting.filesystemobject")

sub processfolder(aFolderName)
	' create a folder object using the given folder.	
	set folder = fso.getfolder(aFolderName)	
	
	' loop over every file within this folder
	' folder.files is a collection of all files in this folder.
	for each file in folder.files
		if file.size >= limit then
			response.write file.path & "<br>"
		end if
	next
	
	' call this routine on every subfolder within this folder
	for each subfolder in folder.subfolders
		processfolder subfolder.name
	next
end sub

processfolder "C:\"
set fso = nothing

With this function, we pass the name of the folder as a parameter to the subroutine processfolder. Then a folder object is created from the global FileSystemObject. All the files within the folder are checked, and after that the routine is called recursively for all the subfolders. Note that this may take a while to run, if you have a lot of files. Try it with a small folder if necessary. Note that there is a difference between the name of a folder (a string) and a folder object.

Let's do another example. Instead of dealing with folders and files we will use textstreams to read in a file, change certain bits of text and then write out this result to another file.

dim fso
dim infile, outfile
dim line
infile = "C:\oldfile.txt"
outfile = "C:\newfile.txt"

set fso = server.createobject("scripting.filesystemobject")

' 1 means open for reading
set intextstream = fso.opentextfile(infile,1) 

' 2 means open for writing, True means create the file if necessary.
set outtextstream = fso.opentextfile(outfile,2,True)

while not intextstream.atendofstream
	' read in a line from the input file
	line = intextstream.readline 

	' manipulate it the line
	' replace some text & lower case (not useful)
	line = replace(line, "hello", "goodbye")
	line = lcase(line)

	' write out that line to the outfile file
	outtextstream.writeline line
wend

'clean up those objects
intextstream.close
set intextstream = nothing
outtextstream.close
set outtextstream = nothing
set fso = nothing

Please do not write me stating that this is equivalent to running perl -en 'print lc s/hello/goodbye/g' < infile.txt > outfile.txt from your command line.

There are a few constants that are required for use with the FileSystemObject. These are available in adovbs.inc, which should be on your system if you have IIS installed. There are also a few little non-orthogonalities, for instance the existence of both a CreateTextFile function and an OpenTextFile function, which includes a parameter which allows you to create a file if one does not exist (as I used in the example). Do not worry too much about this -- just use whatever seems suitable and be consistent.

One other tip: if you want to programmatically generate an ASP file using the TextStream object, you will find that a string such as "%>", intended to be the end of the scripting block in your generated file, will terminate your ASP block, which is not what you wanted. You need to use the special sequence "%\>", which magically prints a "%>" to the TextStream. You can use "<%" in a string to indicate the beginning of ASP script in the generated file without any problems.

That does it for our look at the FileSystemObject. You will probably find yourself using this object quite frequently if you find yourself doing a lot of ASP programming, so it pays to learn how to use it.

Error Handling

Error handling in ASP 2.0 is notoriously primitive. In VBScript, the only error handler you can use is On Error Resume Next. Like in VB, this simply continues execution, and sets the various Err properties to reflect the error. The properties and methods of this object are as follows:

For frequently-used applications, I recommend writing out error information to a log file. Use the On Error Resume Next so that your code does not crash, leaving the user with a confusing screen that won't do much to instill confidence in your site. You can tell if an error has occured if Err.Number is not equal to 0. If this is the case, append a line of text to a text file with the name of the script, time, error number, description and comments. Check the information on the FileSystemObject to see how to do this. This can be invaluable when trying to tract down errors. Some webmasters email errors to themselves but this is usually a bad idea since by a still unnamed corollary of Murphy's Law you will at some point create an endless loop of errors and then get flooded with a couple of thousand emails before you know what hit you! It's much better to append to a logfile and then check this periodically.

Error handling in ASP 3.0 has been considerably improved, specifically with the addition of the top-level ASPError object which contains a wider range of information about each error.

Databases

I think it's a safe bet that most ASP sites in business applications talk to a back-end database. Interfacing with databases is easy and reasonably fast with ASP. Unfortunately there are a whole bunch of acronyms to muddle through when figuring out how to connect to your database using ASP. Here are a few:

If you're curious for technical details, check the Microsoft site. If you're content to know just enough to get by for now, keep reading. Basically, you create instances of ADO objects within your ASP page. There are easy interfaces to database operations that mean you don't even have to know SQL. So, err... you can create an ADO connection given a DSN, and then execute SQL that is run transparently by an ODBC driver or OLE DB provider.

For the rest of this tutorial I'll assume you understand basic SQL. If you don't, there's an excellent tutorial at photo.net. SQL is an easy language to learn the basics of, and a little certainly goes a long way.

The basic high-level ADO objects the Connection, the Recordset and the Field. A Connection object represents a link from your program and the database. Typically, all that you need to provide is a DSN or an absolute path to a file-based database (i.e. Microsoft Access). You should normally only create one Connection object per page. A Recordset object represents a possibly empty set of records. Each Recordset is composed of a collection of Fields. Each Field object represents a particular field, column or attribute in the database.

You have a certain degree of choice when creating recordsets. This choice can cause confusion when learning ADO. For instance, the following pieces of code are identical (I'll be lazy and not declare my variables).

Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "MyDSN"
SQL = "SELECT name FROM Phonebook"
Set RS = Conn.Execute(SQL)
' code ...
RS.Close
Set RS = nothing
Conn.Close
Set Conn = nothing

and

Set RS = Server.CreateObject("ADODB.Recordset")
SQL = "SELECT name FROM Phonebook"
RS.Open(SQL, "MyDSN", 0, 1, 1)
' code
RS.Close
Set RS = nothing

In the first example, a Connection is explicitly created, and then a recordset is implicitly created when Conn.Execute is called. In this case, with a SELECT query, a recordset object is returned. Note the use of Set, which means that an object was created (in JavaScript, there is no difference for assigning to regular variables or objects). In the second example, the Recordset object was explictly created, and then the actual database connection was all dealt with behind the scenes. I don't think it matters much which of these you use. One other point, it usually pays to assign your SQL statement to a variable, so that you can print it the browser if (when) it goes wrong.

If you want to do an INSERT, UPDATE or DELETE query, then you only need to create a connection object, since no recordset is created. You can specify a second variable after the SQL statement to return the number of records affected:

Set Conn = Server.CreateObject("ADODB.Connection")
deletedSQL = "DELETE FROM Phonebook WHERE areacode = 555"
Conn.Execute(deletedSQL,deletedRows)
Set Conn = nothing
Response.Write "deleted " & deletedRows & " rows"

Let's take a look at some ways of obtaining data from a SELECT statement. There are many ways to do this and we'll do three of the most common.

Sending Email From ASP

Predictably, there are many different ways of sending email from an ASP. I will briefly outline four ways of doing so: through CDO, through MAPI, through the command-line and through a custom program. Probably the easiest way is to use the CDO (Collaborative Data Objects) COM object. This provides a convenient property-based way of creating an email message.

This section is a little bare-bones currently.

CDO

CDO is the preferred way of sending email through an ASP, if it is installed on your server.

MAPI

MAPI (Mail API) is an interface on top of Microsoft's mail services including Outlook and Exchange. That is to say, using Visual Basic or Visual C++, you could write your own email client using the visual toolkit and the MAPI COM objects to communicate with Exchange Server. It is extremely powerful -- you can do just about anything with email, including write your own "ILOVEYOU" / "Anna Kournikova" etc. virus (please don't, it's not even remotely difficult and you'll probably end up in jail).

Command-line

You can use the WScript.Shell object to run regular DOS commands. Of course, this could lead to security problems unless you are very careful about exactly what code is run. That being said, you could write a program in Java to email the contents of a file. In your ASP, you could write out the data you want emailed to a text file, and then on the command line, pass the text file to your mailer program.

Custom Program

Another way of doing this, if you don't have MAPI installed on your web server, perhaps for security reasons, is to write a dedicated mailer program in VB or C++ that runs on a different server. For each email, you could write out a text file in a particular folder. The mailer program could then monitor this folder through a network drive and then create email messages from the contents of the file. You could also store meta-data about each email or even the entire contents of the mail into a database if you wished.

Other Common COM objects

There are a huge number of COM objects available. The default installation of IIS comes with several, including an AdRotator component for displaying banner ads, a Counter component for doing a hit counter, and one for displaying browser information. There are also many sites on the Internet which let you download COM objects for free or buy them. I can't possibly list them all so I'll give you a few good places to look for them. Suffice it to say, if it's a fairly common task, the chances are that it's already been done. It's definitely worth paying 50 to 100 dollars for a pre-built software component when compared to the cost of a developer's time.

When developing ASP code in VBScript that relies heavily on COM objects with which you may not be entirely familiar, it is often useful to develop in the Visual Basic IDE, if you have this available. First of all, you can use the Object Browser to navigate through the COM object's hierarchy and, second you can take advantage of the IDE's Intellisense feature that prompts you with an auto-completion feature for method names. The only real difference with the code is that you will need to use Set MyObject = Server.CreateObject("ID") instead of Dim MyObject As New ID or Set MyObject = CreateObject("ID"). I'm not sure if there is a better way of doing this out there (I can't stand Visual Interdev) so if you come across a nice IDE for VBScript or JavaScript be sure to let me know.

Extending ASP with COM

This is a pretty big topic. Basically, there are many things that it's just plain impossible to code using VBScript or JavaScript in an ASP. However, it's possible to write your own COM object using Java, C++, Visual Basic to get other functionality. Check Beginning ASP Components or Developing ASP Components which I mention below in the books section.

ASP and XML

Everyone else is getting into XML, it seems, so it should come as no surprise that you can create, manipulate and use XML documents with ASP, using several Microsoft XML COM objects: MSXML.DOMDocument and others. I have found this to be kind of clunky -- it requires many lines of code to do what seem like simple manipulations.

Index Server

Index Server is a largely ignored and often flaky indexing engine that comes along with IIS and MTS as a part of NT Option Pack 4. It runs as a service and ties into the file system. Therefore whenever a file is moved to a monitored folder, it automatically gets indexed. One of the nice benefits of this is your search results are always up-to-date. Index Server can be particularly frustrating because it is difficult to diagnose problems. However, when it is running smoothly it is a very effective tool. I have not had any experience with Index Server 3.0, but it is supposed to be significantly more stable than Index Server 2.0.

There are three basic ways of interacting with Index Server, each providing different levels of flexibility and speed. The HTM/IDQ/HTX method is not an ASP solution at all. However sometimes it can be preferable due to fast results and the fact that no programming is required. The ASP/COM method uses Index Server COM objects to access the search results. The ASP/SQL method allows you to query the index using standard SQL plus some proprietary extensions. I will discuss each of these briefly in turn and provide very minimal working examples.

HTM/IDQ/HTX

This solution consists of three related text files. The first is a plain HTML file that submits to a file with an .idq extension, for Internet Data Query. The HTML file typically contains a form with allows the user to enter text for their search and perhaps select other options, for instance they could search files modified within the last 3 months or which were authored by a particular person. The second file, the IDQ file, simply consists of pre-defined variables which are either given values based on the results of the form or given hard-coded values. The file looks a lot like an old-school Windows .ini file. You can find a complete list of these properties on MSDN. The final page, with a .htx extension, contains an HTML template for outputting the results of the query. You can customize this to fit in with the look and feel of your site. It recognizes some special scripted markup (contained within <% and %> tags but not ASP) to format output. Let's do a very minimal example (of course you will need Index Server running on your server to get this to work), which requires all these to be in the same folder, say /test.

HTML file (search.htm):

<html><head><title>Index Server Test</title></head><body>
<p>Simple search:</p>
<form action="search.idq" method="get">
Text: <input name="Search" type="text">
<input type="submit">
</body></html>

IDQ file (search.idq):

[Query]
# these are comments, starting with #
# don't forget the [Query] section even if it is the only section

# <% input %> gets the value of the input form field.
CiRestriction = <%Search%>

# maximum number of results
CiMaxRecords = 500 

# sorts results by page size, descending order.
CiSort = size[d]

# this must be a full virtual path.
CiTemplate = /test/results.htx

# these are the meta data fields to retrieve.
CiColumns = size, vpath, doctitle, characterization

HTX file (results.htx)

<%if CiMatchedRecordCount eq 0%>
	No results
<%end if%>

<%begindetail%>
	<p><a href="<%EscapeURL vpath%>"><%DocTitle%></a><br>
	Abstract: <%characterization%><br>
	File size: <%size%></p>
<%enddetail%>

ASP/COM

Another way of accessing Index Server's data is through two COM objects, ixsso.query and ixsso.util. All that you need to do is set various properties of the COM objects and call the appropriate methods. For some reason using this method sometimes produces very slow results on my system, so be sure to run some benchmarks if you intend to implement this method. In general, this method is a little more powerful than the HTM/IDQ/HTX method and a little less powerful than the ASP/SQL method.

Here's an example which displays the same results, using the same HTML interface as in the HTM/IDQ/HTX example. By the way, this is a nice example of abstraction. Microsoft implemented different ways to access the content index, alllowing us to use the same HTML search form. We can choose which approach we want to use depending on our particular needs.

Dim queryobj, rs
Set queryobj = Server.CreateObject("ixsso.Query")

' this property is from the HTML form from the previous section 
queryobj.query = Request.Form("Search") 

' these two are hardcoded like in the IDQ file in the previous section
queryobj.sortby = "size[d]" 
queryobj.maxrecords = 500
queryobj.columns = "size, vpath, doctitle, characterization"

Set rs = queryobj.createrecordset("nonsequential")

if rs.eof then
	response.write "No results"
else
	while not rs.eof
		response.write "<p><a href=""" & rs("vpath") & """>" & rs("doctitle") & "</a><br>"
		response.write "Abstract: " & rs("characterization") & "<br>"
		response.write "File size: " & rs("bytes") & " bytes</p>"
		rs.movenext
	wend
end if
rs.close
set rs = nothing
set queryobj = nothing

ASP/SQL

The final method for accessing Index Server is through SQL. You can connect to the index as a SQL database using the MSIDXS OLE DB provider.

Here's an example of this method in action. It should be fairly clear what is going on. The SQL statement will return one record for each document containing the word "ASP", from the /documents folder. Each record contains three columns, including the document's title, virtual path and size in bytes.

Dim Conn, SQL, ResultsRS
SQL = "SELECT DocTitle, vpath, size FROM SCOPE('"/documents"') WHERE CONTAINS(CONTENTS,'ASP') ORDER BY rank DESC"
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "provider=MSIDXS"
Set ResultsRS = Conn.Execute(SQL)
While Not ResultsRS.EOF
	Response.Write "<p><a href=""" & ResultsRS("vpath") & """>"
	response.write ResultsRS("DocTitle") & "</a> -- " 
	response.write ResultsRS("size") & " bytes.</p>"
	ResultsRS.MoveNext
Wend
ResultsRS.Close
Set ResultsRS = nothing
Conn.Close
Set Conn = nothing

You might want to get the total number of results found. One way to do this is with the Recordset object's RecordCount property, but this is really flaky. My suggestion either to use the GetRows method (discussed in the Databases section and UBound, or just simply keep track yourself by incrementing a variable within the while loop. It's probably best to make ORDER BY rank DESC your sort order, since this is how Index Server natively sorts results. This is the fastest way to get results.

In the interests of completeness I'll implement the bare-bones example that I used in the HTML/IDQ/HTX and ASP/COM examples so you can see the similarities:

Dim Conn, SQL, RS
SQL = "SELECT DocTitle, vpath, size, characterization FROM SCOPE('"/"') ORDER BY size DESC"
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open "provider=MSIDXS"
Set RS = Server.CreateObject("ADODB.Recordset")
RS.Maxrecords = 500
Set RS = Conn.Execute(SQL)
If RS.EOF Then
	response.write "No results"
Else
	While Not RS.EOF
		response.write "<p><a href=""" & rs("vpath") & """>" & rs("doctitle") & "</a><br>"
		response.write "Abstract: " & rs("characterization") & "<br>"
		response.write "File size: " & rs("bytes") & " bytes</p>"
		RS.movenext
	Wend
End If
RS.Close
Set RS = nothing
Conn.Close
Set Conn = nothing

Another way to get only the first 500 records as in the other examples you could use RS.GetRows(500) and then use a for/next loop to iterate through the array. You could run into some very slow queries if you have a large content index and you don't limit the number of records returned (especially if you are sorting by arbitrary properties.

There are a few things to watch out for using this method. Note the strange SCOPE syntax. Each folder is searched recursively by default. If you want to restrict the search to only the files within the directory, you could use SCOPE('SHALLOW SEARCH OF "/myfolder"'). If you wish to exclude certain types of files you can use regular SQL. For instance, to exclude .mdb files from the search, add vpath NOT LIKE '%.mdb' to the WHERE clause. Using custom meta-tags is a bit of a hassle. You will need to execute something like SET PROPERTYNAME 'd1b5d3f0-c0b3-11cf-9a92-00a0c908dbf1' PROPID 'author' AS author TYPE DBTYPE_STR beforehand. This is equivalent to adding an entry in the [Names] section of an .idq file.

Check LearnASP.com for more details on using SQL to access Index Server.

Directives

Directives are basically instructions to the ASP engine. They must be on the first line of code on a page. All directives start with a @ (at sign). If you want to specify more than one directive, they must be separated by a space, like this: <% @enablesessionstate=false language=javascript %>

codepage

This directive controls the character set of the ASP script itself (not the output, use Session.CodePage for that).

enablesessionstate

This directive controls whether or not Session variables can be used. The property is True by default, so if you are going to use Session variables you don't need to do anything.

language

Use this directive to specify which scripting language you are using, e.g. <% @language="JavaScript" %>

lcid

This directive controls the locale of the ASP script itself (not the output, use Session.LCID for that).

transaction

This directive is used to tell IIS that the script should be treated as a transaction under Microsoft Transaction Server. All database actions will get executed, or none.

The language directive is used to specify a scripting language other the default language, which is set in IIS. For example, if you placed this line at the top of your ASP page it would instruct the ASP engine to interpret it as Javascript: <% @language="JavaScript" >. It's not altogether a bad idea to specify the scripting language even if you are using the default choice.

Writing Larger Applications

This is really a huge topic and certainly one that could fill a book, so I'll just try to offer some general advice.

The number one thing to aim for has got to be code reuse, although this is one of those things that is always a lot easier said than done. Both VBScript and JavaScript are kind of clunky languages which don't have full feature sets. Therefore you will probably end up writing a lot of code. Try to write functions with maximum generality and place these in include files. I often have half a dozen include files in a given page, which handle different things such as database access, validation and adding common headers and footers to the page. After you have been coding for a while you will notice that you end up writing similar code many times over. Try to write your code so that all of your application-related information is passed in as a parameter and then you will be able to use your functions for multiple projects.

Another major point to consider is to use Server.CreateObject with caution. This is not to say "don't use it" -- just make sure you need each object you create, make sure you set the object to nothing (VBScript) or null (JavaScript) when you are finished with it, and don't store objects in Session or Application variables. Recognize that you only need one ADODB.Connection and one Scripting.FileSystemObject on any page, in most cases. If you're creating an object inside a loop, that's usually a bad sign. It's almost always possible to clear the object, or simply reuse it without actually destroying it.

ASP is best used as a "glue language", that is, a language that is primarily for letting other programmed objects talk to one another. It is not a good language for heavy computations or heavy database access. Therefore if you are writing CPU-intensive code you should consider writing a COM object to encapsulate this sort of logic.

Security-wise, ASP is not a good choice if you have to be sure that other people won't be able to see your source code. There have been a number of bugs reported which allow people to enter strange URLs and see the source to any page. While patches exist I would generally be reluctant to risk putting top-secret logic or data in script.

Try to separate your HTML content from your ASP logic as much as possible. For instance, say you have a feedback form on your site. One way to implement this would be to have all the logic and display in one page (heavy use of pseudo-code!)

if the form hasn't been previously submitted
	display a form with fields for the user to fill out
else (if it was submitted)
	create email (transparently to user) with CDO or MAPI
	display message saying that we got the feedback
end if

This approach could work fine, except you would have the logic intertwined with the HTML for display to the user. This can be a hassle to edit and if you work where the web design and the programming are done by different people you could end up with a hassle. Another approach separates the code and HTML in three different pages.

  1. HTML page with the form, which submits to...
  2. ASP page which creates the email, which does a Response.Redirect to...
  3. HTML page with the acknowledgement message

This way both the HTML pages can be edited practically at will and the mini-application will work. The only thing that cannot be changed is the names of the form fields in the first HTML page. An added advantage of this method, is if the user gets to the acknowledgement page and hits the Reload/Refresh button on their web browser, it will not send another email (since it is just a plain HTML page). With the first approach, it would send a duplicate email unless you wrote code specifically to prevent that.

Links

This is not intended to be a definitive list of ASP sites, Yahoo! and DMOZ (an open directory) have already tried to do this.

Books

There is a lot of literature out there on ASP, some of it good, some of it bad. One of the best places to find reviews of books on the Internet is Amazon. I'd highly recommend going there to at least see what other people have to say. There's always the odd biased review but you can usually get a good impression after looking at the reviews together.

Recommendations

If I had to buy one book to learn ASP with, it would be Beginning Active Server Pages by Wrox. If I had to buy one reference book, it would be ASP in a Nutshell by O'Reilly. If I had to buy one general "kitchen-sink" tome on everything it would be Professional Active Server Pages 3.0 by Wrox.

Beginning ASP Components and Developing ASP Components are directly comparable. If you like the concise O'Reilly style you'll prefer the latter and if you like a longer, tutorial-style approach you'll probably prefer the Wrox book.

Dis-recommendations

These books just didn't do it for me. However, you should probably get a second opinion since everyone has a slightly different learning style and it could be that these books are pitched better for you. Ask a friend, check the reviews on Amazon or go to the bookstore and scan through some pages. Also I would in general steer clear of "Learn X in 24 hours" or "Crash Course in Y" or "Z in 21 days". These type of books are usually poorly structured and don't serve well as references once you have gone through the book.

Non-ASP books that should make you a better programmer

None of these books discuss ASP in particular but they all give you techniques for writing code that are applicable to programming in any language.

Feedback

Please don't hesitate to send any suggestions, comments, questions or corrections (especially corrections!) that you might have. Send email to asp@jeays.net. This page is fairly new (May 2001) so I'd be extremely grateful to hear anything constructive at all.

If you found this particularly useful, please "pay it forward" and write up some information or data that you know and make it freely available on the Internet.

Copyright © 2001 Mark Jeays. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation.

Last modified April 4, 2002