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.
|
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.
Tip 2: Encapsulate your main body in a Main subroutine.
Tip 3: Use server-side includes to maximize code reuse and maintainability.
|
<!#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.
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!
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.
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.
<%
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 theorylet'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 waythey 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 itten 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.