|
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).
The user makes a request for an ASP through a web browser.
The server receives the request and executes any server-side
includes. The result of this is an in-memory ASP file.
The server then parses the ASP file and executes it, resulting in an
in-memory HTML file.
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.
"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 "<this> & <that>" (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 then
' 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.
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 VBScript syntax is a little easier to learn unless you are a
seasoned C/C++ or Java programmer.
JavaScript is case-sensitive, which can be very annoying, especially
when you're trying to remember how the method names of COM objects
are capitalized.
Most of the ASP-related documentation and literature is in VBScript.
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).
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:
Clear (method): clears all error information.
Description (property): contains the error text, i.e. "type
mismatch".
Number (property): contains the error number, i.e. some
number other than zero if an error has occured.
Raise (method): can be used to generate an error
programmatically. Useful for debugging or custom errors.
Source (property): name of the file in which the error
occured.
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:
ADO (ActiveX Data Objects) is Microsoft's highest-level layer
on top of SQL and the process of connecting to a database.
DAO (Database Access Objects) is Microsoft's older layer on
top of databases.
DSN (Data Source Name) is a pointer (not in the C sense) to a
database.
ODBC (Open DataBase Connectivity) is yet another layer on top
of database operations.
OLE (Object Linking and Embedding) is Microsoft's precursor
to COM. OLE DB is an OLE application that is a layer on top of
databases.
SQL (Structured Query Language) is the standard language for
querying, altering and creating databases.
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.
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 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
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.
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.
HTML page with the form, which submits to...
ASP page which creates the email, which does a Response.Redirect
to...
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.
|