This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND


This article assumes you're familiar with VBScript and ASP
Download the code (19KB)

Top Ten ASP Tips
Roger Jack

Working with ASP? You'll appreciate this look at some tips and tricks of Active Server programming.
I recently developed a corporate intranet application that replaced an existing database application. It was my first experience with Active Server Pages (ASP). Even though I have years of experience with C++ and Visual Basic®, I was surprised and frustrated by the many new types of problems I encountered. Many of these problems are common to most, if not all, ASP applications.
       I want to share with you the tips that I learned about writing effective and maintainable ASP pages. I tried to make the code samples in this article as simple and straightforward as possible. For instance, I have omitted error handling in some cases.

Tip 1: Always use Option Explicit and variable naming conventions.
       This tip is one of the simplest, but also among the most important. If you have programmed in Visual Basic, you probably know about Option Explicit. Option Explicit forces you to declare all variables before using them. If you don't declare Option Explicit, you can create nasty bugs by simply misspelling a variable name.
       In the world of ASP there are two types of Option Explicit statements: client-side and server-side. Template.asp (see Figure 1) contains an example of both types. The first Option Explicit, near the top, is for the server-side code, while the Option Explicit below the Client-Side Functions comment is for client-side code. It is important to use both types of Option Explicit in your ASP code.
       There is only one explicit data type in VBScript: a variant. Since a variant can represent many different data types, you should keep track of what data type you put into the variant. Otherwise, it is all too easy to forget what type of data is really in the variable. Microsoft suggests you use a form of Hungarian notation for VBScript. For example, intQuantity represents a quantity that is an integer data type. You can find more information about the convention by searching for "VBScript Coding Convention" in the Visual InterDev™ online help.
       By the way, I created template.asp for you to use as a starting point for your new ASP pages.

Tip 2: Encapsulate your main body in a Main subroutine.
       Global variables can lead to problems that are difficult to debug. It is very easy, for instance, to accidentally access a global variable from a function when you think you are using a local variable. All variables that you declare in ASP outside of functions are of global scope to the ASP.
       To avoid incidental global variables, you can create a Main subroutine as the entry point for the ASP. In Figure 1, a Main subroutine is declared near the top of the page. Any variables you declare within Main are local to it, so you significantly reduce the number of global variables. Main is then called from within the page as the starting point for the page.
       Sometimes global variables are necessary, so I created a Global Variables section near the top of Template.asp. You can put all of your global variable declarations there.

Tip 3: Use server-side includes to maximize code reuse and maintainability.
       Server-side includes (SSI) allow you to reuse code by including the same code in multiple files. SSIs are similar to C/C++ #include files. The VBScript engine inserts the code into the ASP file before any other compiling or interpreting is done. For example, if you had an SSI called ClientValidationInc.asp, it would be included in an ASP with the following statement:


 <!—#includes virtual="ASPTips/ClientValidationInc.asp"—>
See "Writing ASP Scripts" in the Visual InterDev online help for more details on the syntax of the SSI statement.
       SSIs are useful for inserting functions, global variables, or global constants. You can use SSIs to insert constants and functions related to page attributes (color, font, and so on) that are common to all ASP pages in your application. In this way, you can easily make changes to the client-side interface presented by your ASP pages.
       There are two types of code that SSIs can insert into an ASP: server-side code and client-side code. Server-side code is fairly straightforward—just add the SSI to your ASP and use the inserted code. Client-side code can also be included easily, but may degrade performance. A basic goal of ASP programming is to return only the code necessary to the client because the network is relatively slow and you need to minimize network traffic. For instance, you could create an SSI with several related client-side functions. But suppose you don't need all of the functions in the SSI for your particular ASP. How do you avoid returning the unnecessary functions to the client?
       I have found a useful technique for avoiding this problem. First, create a client function and embed it in a server function. Second, create a global boolean to indicate whether the function has already been included. Third, test and update the boolean whenever the function is included. This allows you to control whether the function gets inserted and prevents it from being inserted more than once. InsertHandleClientErrorFunction in Figure 2 shows an example of this technique. To use the function, add the SSI to your ASP page and call InsertHandleClientErrorFunction from the client-side script area of your ASP page. This inserts the function into your client-side code. You can now call the HandleClientError subroutine anywhere within your client-side code.
       You can also use this technique to handle dependencies between functions within an SSI. For instance, IsAlphaNumeric calls InsertIsAlphaFunction and InsertIsDigitFunction to insert the functions that it needs in Figure 2. This also hides the implementation details of IsAlphaNumeric.

Tip 4: Use On Error Resume Next carefully.
       On Error Resume Next is the only mechanism available to handle runtime errors. As you may know, the On Error Resume Next statement causes the VBScript interpreter to jump to the next line if there is a runtime error. This allows you to handle the error more gracefully.
       However, if you place On Error Resume Next so that it has global scope or is in your Main subroutine, you can accidentally disable all runtime checking and introduce difficult-to-find bugs. This is because it affects all the code following the statement, including all function calls. Suppose you had the following code in your Main subroutine:


 Option Explicit
 On Error Resume Next
 Dim strExample
 strExampl = "Test String"
Since the assignment statement has misnamed it, the variable strExample will never be assigned the value Test String and you won't get a runtime error!
       To avoid this problem, it is best to encapsulate On Error Resume Next in a function that really needs error checking. This limits the scope of the On Error Resume Next statement to that function and the functions called within it. The IsValidTime function in Figure 2 demonstrates this technique.

Tip 5: Use Response.Write for debugging.
       You can use the Response.Write method to send any HTML back to the client. I use it primarily for sending debug information back to the client screen so that I can detect bugs. The DebugOutput subroutine below shows a simple function that you can use for debugging. The Main subroutine calls DebugOutput to send "Unexpected option selected" to the client.


 Main
 Sub Main
     DebugOutput "Unexpected option selected"
 End Sub
 
 Sub DebugOutput(strDebugMsg)
     Response.Write(strDebugMsg & "<BR>")
     Response.Flush
 End Sub
       Notice that DebugOutput is calling Response.Flush, which is necessary if buffering is turned on (Response.Buffer = True). Otherwise, if you wrote code that caused an infinite loop, you would not see the output from DebugOutput on the client side. You could also modify DebugOutput to use a global debug flag to indicate whether debugging is turned on or off. You could then turn off the DebugOutput when you want to put your code into production. Since ASP is interpreted, you have to be careful not to degrade system performance with too many calls to DebugOutput when you move code to production. This could happen even though the DebugOutput routine does nothing because of the time spent pushing and popping the stack.
       Although this article was written with Internet Information Server (IIS) 3.0 in mind, it is worth noting that IIS 4.0 (currently codenamed K2) provides new script debugging features that will make your life a lot easier. IIS 4.0 should be available on the Web by the time you read this article.

Tip 6: Know the differences between Natural and Artificial form field arrays.
       You can create two types of field arrays. I call them Natural and Artificial. Natural field arrays use arrays available implicitly in HTML. For example, Form Field Array Screens (see Figure 3) shows pages generated by FormField1.asp and FormField2.asp (see Figure 4 and Figure 5), respectively. The following code in FormField1.asp is related to creating a Natural array of text fields:


 <%
         For intRow = 1 To TOTAL_ROWS
 %>
             <tr>
                 <td align="center">
                     <input type="text" size="3" name="txtData" 
                         value="N<%= intRow%>"></td>
             </tr>
 <%      Next %>
On the client side, this will generate three rows of text fields with the same name, txtData.
       The following code from FormField2.asp manipulates the Natural field array. Notice that the For Each statement is used and that there is no need for any type of counter.

 <%
         For Each strData In Request.Form("txtData")
 %>
             <tr>
                 <td align="center"><%= strData%></td>
             </tr>
 <%      Next %>
You can also index directly into a Natural field array and get a count of elements. For example, the following code retrieves the last element of the txtData field array:

 <%
 Dim intTotalElements
 IntTotalElements = Request.Form("txtData").Count
 Dim strData
 strData = Request.Form("txtData")(intTotalElements) %>
       With Artificial field arrays, you must create a unique name for each element and manually track the total number of elements used. The following is the code in FormField1.asp that creates the Artificial field array:

 <%        For intRow = 1 To TOTAL_ROWS %>
             <tr>
                 <td align="center">
                     <input type="text" size="3" 
                         name="txtData<%= intRow%>" 
                         value="A<%= intRow%>"></td>
             </tr>
 <%        Next %>
         <input type="hidden" name="hdnTotalRows" 
             value="<%= TOTAL_ROWS %>">
As you can see, the intRow variable is used to create a unique name for each field. On the client side, the fields would be named txtData1, txtData2, and txtData3. I created an additional field, hdnTotalRows, to hold the total number of rows. The following code from FormField2.asp manipulates the Artificial field array:

         Dim intTotalRows
         intTotalRows = CInt(Request.Form("hdnTotalRows"))
         Dim intRow
         For intRow = 1 To intTotalRows
             strData = Request.Form("txtData" & intRow)
 %>
             <tr>
                 <td align="center"><%= strData%>
                 </td>
             </tr>
 <%        Next %>
       It should be clear by now that Natural field arrays are easier to use on the server side. But they are harder to use on the client side because there is no convenient way to directly access fields. You can use a For Each…Next statement on the Form.Elements collection, but you can't access the Natural field array based on an index or unique name. If you try to access the field based on the name, you can only retrieve the first field of the array. As a general rule, if you need client-side access to the field array, then use Artificial field arrays. Otherwise, you should use Natural field arrays.

Tip 7: Explicitly convert form fields to avoid problems.
       Form fields are always passed as strings to the server side and will remain strings unless you explicitly convert them. In other words, since VBScript only supports the variant data type, you have to be careful that you explicitly convert your fields if you want something other than a string. The form fields pages in Figure 3 shows what kind of problems you can have if you are not careful with conversion. When the checkbox is turned off, no conversion is used and, as a result, "2" + "2" = "22". If the checkbox is turned on, then 2 + 2 = 4.
       Here is the section of code from Figure 5 that handles the addition:


 If blnUseConversion Then
     intOperand1 = CInt(Request.Form("txtOperand1"))
     intOperand2 = CInt(Request.Form("txtOperand2"))
 Else
     intOperand1 = Request.Form("txtOperand1")
     intOperand2 = Request.Form("txtOperand2")
 End If
 Dim intSum
 intSum = intOperand1 + intOperand2
If blnUseConversion is False, then intSum equals "22" because the VBScript engine treats the operands as strings that are concatenated. If blnUseConversion is True, then the form fields are explicitly converted and intSum equals 4. Although I admit this example is contrived, I have seen several difficult-to-find bugs that were the result of not explicitly converting fields. In general, it is better to always explicitly convert your form fields.

Tip 8: Use session variables to assist in page redirection.
       You can use session variables for data that must persist across multiple ASP pages. For instance, you can have a user enter a user ID and password and then use session variables to maintain the information across other ASP pages. You can do similar things with hidden fields or cookies, but session variables stay primarily on the server side. This makes them more efficient because it reduces network traffic and client-side processing.
       There are a couple of potential pitfalls related to using session variables. First, session variables can get deleted automatically when a session times out. You can control this timeout with the Session.Timeout property. Second, you can use the Session.Abandon method at any time to manually delete session variables. This is necessary if you want to manually recover the memory allocated on the server for the session variables.
       Session variables do use HTTP cookies for their implementation, so they do have some of the physical downsides of explicit cookie use: a tiny amount of net traffic, and a few users complaining about cookies. They also use some memory on the server. On the whole, though, session variables are a lot easier to use than cookies.
       Well, that's enough theory—let's look at a useful application of session variables. I often use session variables to pass data to a destination page during page redirection. Suppose you have two pages, a user-input page and a database-update page. The database-update page displays a success message if the database updated successfully. Now assume that validation requires access to the database. If validation is unsuccessful, you can pass all the data back to the previous page by copying all the fields to session variables and redirecting to the user-input page. The following code shows an example:


 Sub Main
     'Data validation here
     •••
     Failed data validation
     CopyFormFieldsToSession
     Response.Redirect "UserInput.asp"
 End Sub
 
 Sub CopyFormFieldsToSession
     Dim strFV
     For Each strFV In Request.Form
         Session(strFV) = Request.Form(strFV)
     Next
 End Sub
       As an alternative to session variables, you can also pass back variables by appending a query string to Response.Redirect. For example, the following code passes to UserInput.asp a variable called Name that is set to Joe:

 Response.Redirect "UserInput.asp?Name=Joe"
This works fine when you want to pass a few fields, but the query string must be less than 1,024 characters, so you have to be careful. If you have a lot of data, session variables are a better way to go. You'll see another example of page redirection and session variables in Tip 10.

Tip 9: Client side or server side?
       There are two places that you can include user-input validation: the client side or the server side. It is important to clearly understand what goes where. I use these rules to put the validation in the right place:
       Rule 1: Put as much validation as you can on the client side.
       Rule 2: If the user-input validation requires database access, then that validation should be done on the server side.
       The justification for Rule 1 is that response time is much better for the user if the validation is done on the client side. Suppose you have an ASP page that requires the user to input a Social Security number and get back information about the person that is assigned that number. On the client side, you should validate that the user-entered number uses the format of ###-##-####. There is no need to submit the page if the validation fails. On the server side, validate that the Social Security number is in the database. If it is not, display an error through redirection (see Tip 8) or some other mechanism.

Tip 10: Write a standard error-handling page for ADO and other types of errors.
       There are two types of errors: user errors and system errors. User errors are typical validation errors that the user inputs mistakenly. For example, the user may enter a quantity of 100 when the maximum permitted quantity is 99. ClientValidationInc.asp in Figure 2 is an SSI that contains functions for detecting and managing user errors on the client side.
       System errors can be caused by software or hardware errors. For example, a SQL select statement might contain invalid syntax. These types of errors must be handled in a reasonable way—they should not cause a runtime abort. ErrorInc.asp in Figure 6 is an include file that contains functions for handling system errors. The functions are both high and low level, so you can pick and choose the level of abstraction that is right for you. You don't need to do any error checking for most of the function calls because they don't return if there is a serious error.
        Figure 7 shows a sample page that uses some of the ErrorInc.asp functions. If you select a function that causes an error to be displayed, you will see a screen that looks something like Figure 8. This screen contains information concerning the SQL executed and the form variables entered, which is significantly better for the user than having a runtime abort with line number information. It also provides vital information for the developer to make it easier to debug the hardware or software problems. Of course, if you don't like the error information that I display, you can create your own error page. The point to remember is that you need to encapsulate your error handling in some reasonable way.
       The ErrorAbort option in Figure 7 calls the ErrorAbort subroutine that displays a simple message. The ExecuteSQL and ExecuteSQL w/Error options allow you to pass a SQL string to be executed. AddNewRecord and AddNewRecord w/Error options allow you to use the ADO's AddNewRecord function. The code to execute these options is in Figure 9. The calls to ExecuteSQL and AddNewRecord will not return if the functions fail. As a side benefit, if you use the transactions available through the ADO Connection object, you don't have to worry about calling the RollbackTrans method because the transaction will automatically roll back when the function in ErrorInc.asp redirects to the error page. I didn't write a complete set of functions to provide ADO handling, but you could certainly implement such functions as necessary.

Conclusion
       Well, there you have it—ten tips for ASPs. There are many other tips that I could have included, but these are the ones I thought were most important. I wish I had these tips available to me before I started writing ASP. My colleagues and I had to discover these tips through trial and error. Fortunately, you won't have to!

From the January 1998 issue of Microsoft Interactive Developer.