home navigator pointer resources navigator pointer asp tips navigator pointer asp tips (printable)

Archived ASP Tips Page (single printable page)
coding/style | performance | IIS/NT tips | separate page version

Note: this is my old ASP tips page and is no longer supported. See the new Visualize web site..

This part of the site is for any ASP developer who wishes to get the most from their ASP scripting. It presents a number of hints and tips which I have produced as a result of developing various ASP applications over the last few years or so. Most of the tips are of relevance to any platform that supports ASP, whether it be Microsoft ones or those ported to others by third-part vendors (e.g. Chillisoft, Instant ASP).

VBScript is used in the examples, as this is the most popular scripting language chosen on the server-side, but most of the ideas are just as applicable to JScript. The popularity of VBScript will no doubt change in the future with ASP.Net though, with C# being a nicer language and possibly likely to become the chosen language for most developers. Based on what I've seen in beta 2 I can't wait!

Note these tips are not aimed at ASP.Net

Many of the tips assume knowledge of basic ASP scripting and general database connectivity issues, and novice ASP developers should use these tips in conjunction with the numerous basic ASP scripting guides now available on the web. See my ASP external links page as a starting point.

The goals of these pages are:

  • To provide new ASP developers with valuable information on how to write good ASP
  • Provide a useful checklist of hints and tips for writing efficient and maintainable ASP
  • Encourage consistency of coding across web sites and applications
By following these hints and tips, your applications will be more responsive, efficient on server resources and easier to modify and maintain.

This page will be updated regularly, as and when I have a tip that is worth sharing. If you have any other hints/tips which you think would be useful for adding to this page, email me now and I will check the hint/tip and add it to the site.

The page is broken up into the following sections:

IIS5/Windows 2000 tip indicates an IIS5/Windows 2000 only tip

DISCLAIMER: Note these pages are a free resource for anyone wishing to reference them. Although every care is taken to ensure their correctness, the author takes no responsibility for any errors or problems that may occur through their use, or indeed misuse. These pages are copyight of Dave Clarke, Visualize Software Ltd 1997-2000 (all rights reserved).


 


back to top

ASP Coding and Style

Structuring your Scripts

All ASP scripts, other than very trivial ones, are worthwhile structuring into procedures, functions and SSIs (server-side includes). This will aid maintenance and readability considerably. A typical script could have the following structure:


<% Option Explicit   ' No implicit declarations thank you!
                     ' This should appear at the top of all
                     ' scripts to enforce variable declaration
%>
<!--#INCLUDE FILE='YourStandardIncludesHere.inc'--<
<!--#INCLUDE FILE='etc.inc'--<

<%

       ' Now declare any script level constants
Const sMYCONSTANTSTRING = "This remains constant"
Const sMYCONSTANTSTRING2 = "etc..."

       ' Now declare any script level variables
Dim iMyInteger, sMyString, bMyBoolean	   

       ' Now instantiate any script level objects
	   ' (ADO dbConns, dictionary object etc)
	   
       ' connect to database here
Set oDBConn = Server.CreateObject("ADODB.Connection")
oDBConn.Open "TestDSN"

     ' main processing here
	 ' .... etc 

 
      ' now close any database connections and free up objects 
oDBConn.Close
Set oDBConn = Nothing

'***************************

'  Private subroutines/functions here.
 ' Keep variables local where possible

'***************************

          ' have any standard footers if necessary here
<!--#INCLUDE FILE='YourStandardFooterIncludesHere.inc'--<

Obviously, the structure of the script will vary slightly depending on the nature of the script, but this is a good general rule of thumb. All includes (containing useful routines and commonly used code) are marked clearly at the top, it's clear which variables are at script level, and the main processing is near the top, hence easy to find.

Note the symmetry in the code, i.e. create, open, process, then close and free up memory. By keeping symmetry like this, you won't go far wrong on performance and efficiency of server resources.

Finally, I tend to keep my HTML output routines totally separate from the main logic, usually via a display routine at the bottom of the page or in a separate include, thus keeping presentation separate from the business logic.

Variable Naming Conventions

Consistency is the key here. Variable naming conventions are usually left to the developer, and each will have their own preferences as to what "works for them" etc. However, from a team maintenance point of view, it is well worth adopting a standard within teams where possible. My personal preference is the hungarian style notation, which is well established and can be used across a number of languages. The big plus with this approach is that you can immediately tell the type of data that is being stored in each variable, just from seeing its name (rather than having to track down its declaration). As you are likely to be aware, ASP uses Variants and does not explicitly declare variables of particular types. Even though this is true, it is still extremely useful to know what is intended to be stored in the variable, and hence this notation is worth considering for ASP. Take it from me, once you get used to using this approach, it will become a very useful habit!

The notation adopts a lowercase prefix, indicating the data type of the variable concerned, followed by the name of the variable itself, often capitalising each word within it (no spaces). Constants are similar, but typically we only lowercase the prefix, capitalising the rest of the name to clearly distinguish these from normal variables. Below provide some typical examples:

Variable Name Data Type Prefix
iPeopleCount integer i
bEndOfFile boolean b
sName string s
oMyObject Object o
rsMyRecordset Recordset Object rs
iFIXEDSIZE integer constant i

Obviously, this list can be extended (e.g. col for collections, txt for HTML text boxes etc.), but I'll leave that to you.

Use the Application Object

For information that rarely changes and needs application scope (e.g. look-up info, constants), consider putting these into the Application object. Such values will be cached and be available to all scripts within the application, as well as acting as a central point for maintenance at a later stage.

Close and Free up Recordsets/Database Connections after use

A classic bottleneck this one on servers, and one which can rapidly eat resources if not handled correctly. Ensure that all recordsets and database connections are closed correctly after use, and objects are set =NOTHING where relevant. Generally, this should occur on the same page as they are created/opened (keep code symmetry), which will make best use of the database connection pooling on the server (if using ODBC, make sure connection pooling is enabled in the ODBC driver manager - if the option is not present, it will be worth your while upgrading MDAC on the server, see Data Access Components/ODBC drivers). Example code structure:
 Dim dbConn, RS, sSQL
 Set oDBConn = Server.CreateObject("ADODB.Connection")
 oDBConn.Open "TestDSN"
 
 sSQL = "SELECT Field1, Field2 " &_
        "FROM MyTable "
 
 set rsTest = dbConn.Execute(sSQL)
 ' .... check for SQL error and process results here
 ' .... etc
 
 rsTest.Close
 oDBConn.Close
 Set rsTest = Nothing     ' Free up memory
 Set oDBConn = Nothing

Avoid Hard Coding DSNs

Place ODBC DSN or OLEDB connection strings in either include files or in the application object. This ensures they are only stored in one place, aiding maintenance at a later date.

SQL Execution Error Handling

All SQL executions should have some form of error handling, and call a standard error handler in an include file.

Example code could be:


 <!--#INCLUDE FILE='error.inc'--><%
 
 Dim oDBConn, rsTest, sSQL
 Set oDBConn = Server.CreateObject("ADODB.Connection")
 oDBConn.Open "TestDSN"
 
 sSQL = "SELECT Field1, Field2 " &_
        "FROM MyTable "
 
 On error resume next              'catch ODBC/SQL errors in a minute
 Set rsTest = oDBConn.Execute(sSQL)
 On error goto 0                   'error handling back on
 
                                   ' now check for ODBC errors
 IF oDBConn.Errors.Count <> 0 then
     Call MyErrorHandler(oDBConn, sSQL)  ' pass database connection as param
 Else
     '.... process results
     '.... etc
 End If
 
 rsTest.Close
 oDBConn.Close
 Set rsTest = Nothing     ' Free up memory
 Set oDBConn = Nothing
and the actual error handler function itself in the include file can take many forms. As a minimum it could simply output details of the error in the page itself, for example:

Public Sub MyErrorHandler(oDBConn, sSQL)
  Response.Write "An error has occurred.<BR>"
  Response.write "SQLstate=" & oDBConn.Errors(0).sqlstate & "<BR>"
  Response.write "Description=" & oDBConn.Errors(0).Description  & "<BR>"
  Response.write "NativeError=" & oDBConn.Errors(0).NativeError & "<BR>"
  Response.write "SQL=" & sSQL & "<BR>"
  Response.Write "Please telephone the Web Support Team on tel no xxxx"
  On Error Resume Next
  oDBConn.Close
  Set oDBConn = nothing
  On Error goto 0
  Response.End
End Sub
Of course, this routine can be as complicated as you wish. In the past I have written error handlers which initially redirects to a neat user-friendly form. This then gathers some additional user details and informs the web team automatically via email, with error logging, giving full technical details of the error, all hidden from the end-user.

Use MapPath

To aid portability in your scripts avoid using literal file paths in your code and use MapPath instead. There is a small performance penalty for this, as this method requires the web server to retrieve the current server path. Hence, it is recommended to call this once and place the result into a variable for referencing later where possible.

Response.Buffer and Debugging

When debugging asp pages, if you have set the response.buffer = true, you will not always see ASP error messages. Hence, set the response.buffer = false during debugging - this ensures that if the page cannot be processed (because of an ASP coding problem for example), an error message will appear.

See also: Buffering output for speed

Exiting Loops

When looping in ASP, it makes sense to avoid looping more times than is necessary and exit the loop as soon as you can. This can normally be accomplished by having appropriate conditions on the loop itself, for example:

dim i

i=0
do while i < 10 and not rs.eof

    ' process your recordset here...

    rs.movenext    
    i = i + 1
loop
Occasionally, it is sometimes useful to exit a loop early, even if the main loop conditions are not met. VBscript offers the Exit For and Exit Do statements for just this purpose. For example, if we wanted to add some code which exits the loop if some error arises, then we could write this as follows:

i=0
do while i < 10 and not rs.eof

    ' process your recordset here...
    If [some error occured] then
        Exit Do
    end if
    rs.movenext    
    i = i + 1
loop
Generally, it is good coding practice to put the conditions on the loops themselves, as opposed to using the exit statements illustrated above. However, the exit statement is extremely useful for exiting if a special case arises. This avoids the need to have extra boolean flags etc.

ASP Coding and Style - Beware of storing components at session/application level

Storing a component in the session or application object often seems the ideal thing to do, ie we simply set up the object "once" at session or application start up, and this is cached until we need it - no need to keep creating it and destroying it on each page and this should result in excellent performance. This is fine, and is a good idea, providing the component is designed to be used in this way. Many are not however, including those written in Visual Basic 6!

The only components that should be used in this way are those classed as "truly free-threaded" (or "agile" in Microsoft speak). These are written to allow multiple-threads to run safely, concurrently within the component, catering for all the issues that can arise, such as mutual exclusion problems, lockout or deadlock situations and controlling access to shared data.

Hence, only store components at session or application level if you know that the component is truly multi-threaded. Those created with VB6 (or below) will not be and should therefore only be used at the page level.


 


back to top

ASP Coding for Performance

Use .htm Extensions Where Relevant

For sites with very high hit rates, for non-dynamic pages use .htm rather than .asp extensions. The web server can serve these static pages at a more rapid rate, as they do not need parsing before sending to the client browser. This takes some load off the server. If necessary, such pages can be created in batch via some ASP (or other tool) routines, providing a half way house between true dynamic pages and limited, manually edited, static ones. Such routines could be run daily, weekly etc.

Consider using <OBJECT> tags

If you are not sure whether an object is actually going to be used or not in a script, then use the <OBJECT> tag instead of Server.CreateObject(). This will declare an object without actually instantiating it (ie instead it will wait until it is referenced, termed 'lazy evaluation'). Another benefit is they use CLASSIDS, which eliminate name collisions due to their uniqueness.

Dim Statements and Variable use

Declare a number of variables per line, using the Dim statement. This results in less lines to parse. e.g. replace
Dim sMyString
Dim sMyTemp
Dim iCounter
with:
Dim sMyString, sMyTemp, iCounter

Use local variables whenever possible. This not only aids maintenance by localising control, but also speeds up processing since the entire namespace does not have to be searched when the local variable is referenced. Also, copy individual collection values into local variables, if you are going to reference them a number of times (saves multiple lookups). Avoid redimming arrays. Instead, it's probably more efficient to declare the overall max size in advance.

Avoid overusing HTML/ASP Context Switching

Avoid lots of <% and %> context switches between ASP and HTML in your pages. This slows down the parsing and makes code unreadable. It's much better to group your HTML and ASP into fair sized blocks, thus minimising the amount of context switches required per page.

Use SQL for Searching Databases

lthough this appears an obvious one, I still see the odd ASP script around which uses ASP loops to search through ADO recordsets for a particular record(s) to display, when really the SQL query should have done the searching. Avoid looping through recordsets for searching purposes, unless there is a specific reason to do so.

Worse still, avoid requerying databases inside ASP loops to carry out look-up operations. These have a high overhead and 9 times out of ten, are often not required. It's much better to make appropriate use of SQL inner/outer joins, even correlated sub-queries if necessary etc., to carry out such searches efficiently at the database engine level (that's what databases are good at!), leaving the ASP to control final displaying of the results.

Use SQL Stored Procedures

Rather than use pass-through SQL, consider setting up stored procedures to handle common queries. These are more efficient and robust than constructing and executing full SQL statements. Most database engines offer these facilities including DB2, ORACLE (can be combined with PL/SQL) and SQL Server.

Minimise Session Use

Only use sessions where needed. They are essentially glorified "global variables" and as anyone with a background in software engineering will tell you - "globals are bad news". There are a number of reasons for this. Not only do they tightly couple your scripts together, raising all kinds of maintenance issues (we should aim for loosely coupled pages), they also reduce scaleability as they tie a user to that specific server, making the use of web farms and load balancing more difficult. Sessions also require cookies to be enabled on the browser for session id information to be stored. Something we cannot always guarantee. Also, sessions can use a great deal of the server's resources, especially if abused, as a session typically lasts for around 20 minutes (default session timeout), wasting valuable memory, often long after the user has left the site! The worst scenario for this is where objects, recordsets or arrays are stored in a session and are "never" explicitly closed or destroyed. Oh dear... that poor server...

A final thought on this - if you really need to store a recordset or other object in a session (and there are times when this is useful), as a catch all, please ensure that Session_OnEnd() in global.asa has the relevant code to close them and free up memory afterwards. Also, minimise the session timeout to as low a figure as is acceptable, and add the @ENABLESESSIONSTATE=False directive to all pages that do not require session state information (saves web server resources):

The Dictionary Object

This object is often overlooked and can often be an excellent replacement for hand-crafted arrays. It provides an efficient look-up facility, via key/data pairs. Of course, don't forget to explicitly destroy the object after use by setting = nothing. ;-)

Finally, as tempting as it may seem, avoid storing this object at application level. The component is not truly free-threaded, so doing so will result in unpredictable results and data corruption may well result. This is a shame, as in many situations having the dictionary at application level would be very useful. Perhaps Microsoft will fix this in a future release.

See also Beware of storing components at sesson/application level.

Compile Routines into COM objects/DLLs

If you have routines that are used by many scripts, are fairly processor intensive in nature or contain a fair amount of business logic, consider compiling these up into COM objects/ActiveX DLLs, using tools such as VB, Delphi or VC++. These compiled routines will run faster than interpreted ASP and are often easier to maintain. However, do remember that instantiating objects in ASP, do have some overhead, which should be taken into consideration when designing your scripts. Once created however, calling the various methods should be very efficient. As a basic rule, "create once, call many times, destroy (using =nothing) after use."

Script Timout

When an ASP script is run, it is given a fixed amount of time to complete processing before it will time out. The length of time before this happens can be set in two ways, either via the MMC, or by using Script.Timout in the code. By default this is set to 90 (seconds), but can be altered as necessary on the fly in code. As a basic rule of thumb, leave it set to the default in most cases, but on occasions it may be required to increase it slightly (say to 180 seconds) for a large script to complete processing. If this is the case, the script timeout should be set at the beginning of the script concerned, and then reset to the default at the end. Of course, one should probably be writing a component for tasks such as these...

IsClientConnected - avoiding stray tasks

This is useful to use to ensure that the client is still fetching the page that is currently processing. For example, consider the following classic loop which simply goes through all the records in a recordset:

Do While Not rsTest.EOF

     ' process and display each record
     ' etc....
	 
    rsTest.MoveNext
Loop
Let's say there are 5000 records to process. What if the user stops the page before it is fully finished - the script will continue to run, when the user has long gone! The following rectifies the situation:

Do While (Not rsTest.EOF) And (Response.IsClientConnected)

     ' process and display each record
     ' etc....
	 
    rsTest.MoveNext
Loop
This will loop until end of file or the user selects the stop button. Excellent, just what we want. ;-)

Take care with Server Variables

Accessing a server variable for the first time requires a special request to the server, to collect ALL the variables, not just the one you're interested in, so make these requests with care. Subsequent requests are not such a performance overhead.

Know your cursor types

When using recordsets/cursors, ensure you fully understand the types you are requesting. These are documented in the standard ASP Roadmap on-line documentation. The vendor specific documentation regarding specific drivers/DLLs should also be fully understood to get the most from database performance.

Buffering output for speed

Consider buffering the output generated from an ASP page by using response.buffer = true. HTTP and TCP/IP work much more efficiently when sending decent sized chunks of data. Sending tiny chunks is hence expensive.

Having said that, if the page is quite large and buffering is on, the end-user may think the page is responding poorly as they will not see any output in the browser until the page has finished. If this is relevant to your page, do a response.flush to flush the buffer every so often, at appropriate points in the code, to provide some feedback to the user on page progress.

Replace Response.Redirect with Server.TransferIIS5/Windows 2000 tip

It is often useful to redirect to a different page from within your script. In IIS4 and ASP2 you would typically use response.redirect to do this. This would send a response to the browser to instruct it to request the new page - thus the browser has essentially made two page requests to the server.

IIS5/ASP3 improves on this, by providing a server-side approach to redirection, namely server.transfer, by transferring execution immediately to another page directly, avoiding the "extra round-trip" approach in IIS4. Much more efficient.


 


back to top

IIS/NT Tips

Turn off Logging on Selected Directories

Logging page access obviously has some overhead associated with it, so it makes sense to only log those directories you're interested in. You can do this by deselecting the log access option in the MMC (Microsoft Management Console) for the directory concerned.

Running Applications in a Separate Memory Space

When you define an ASP application in the Microsoft Management Console (MMC), there is a "run is separate memory space" check box, which is often overlooked, and allows ASP applications to be run as a separate process to IIS (inetinfo). So why would you want to do this? Typically you would leave this unchecked, thus allowing the application to run in the same space as IIS and process very efficiently. However, by doing this you are potentially allowing a rogue application to overwrite some memory being used by IIS, potentially crashing inetinfo, therefore bringing down the whole web server. Hence, if you have any ASP applications that have not been fully tested, or are known to be "dodgy" in nature (there is always one out there!), then, at the sacrifice of losing a little performance, you should seriously consider running these in their own space. Reliability is crucial in most cases, and this is one simple way of increasing it.

See also: IIS5 Application Settings

Locale Date Settings

If you're lucky enough to be setting up an IIS NT4 server from scratch, then it's important to get settings such as the locale/regional set up correctly very early on, before ASP scripts are developed on them, as they can effect how scripts behave, particularly those which rely on system date/time information. Anyway, to cut a long story short, log on locally as administrator and check that the regional settings are as expected. In particular, check that the Short Date setting is as you require. For example, in the UK, we would typically want the short date format to show as dd/mm/yyyy, but the default is US format with a two digit year (ie m/d/yy). This can create havoc with scripts which use the ASP date() function which uses the locale setting for its formatting (writing your own date component would be one way of tackling this issue).

Make sure that you also change the regional settings for IUSR_machine etc. as well. There are two ways of doing this, either by logging on as each in term (you will need to set or know the passwords), or by searching/editing the registry for sShortDate and modifying them directly (only do this if you are confident with regedit!).

Check also that NT's roaming profiles do not affect the date settings when other users log on to the machine (simply write a short ASP script which displays the result returned from date(), keep refreshing the page while other users log-on to the server, and see if the date format changes). If the users are set up correctly, it shouldn't.

There also appears to be a bug in NT4 SP3 and IIS4. Despite changing all occurrences of ShortDate in the registry, the date format that you specify does not appear to take effect after a fresh reboot. It only works once at least one user has logged onto the machine and off again. Strange... if someone knows the answer to this, please drop me a line....

If you're still having problems, you should also try adding "Session.LCID = 3081" (eg is for UK) in the script. You can also use this approach to use the user's browser language settings captured from the HTTP header info. See this Microsoft Multi-language Support KB article for more details. This also contains the codes for other countries.

Data Access Components/ODBC drivers

It is important to keep tabs on the latest drivers that are used to handle all the backend database connections. For example, later versions of MDAC (Microsoft Data Access Components) introduced improved features, including connection pooling.

As a starting point, keep any eye on the Microsoft data access site at http://www.microsoft.com/data, and consider installing the latest versions of drivers when needed. The new drivers may well fix many of the problems you have been recently having (of course, they may well also introduce some new ones at the same time! Thorough testing is the only safe approach).

Note. Later versions of MDAC moved towards a more standard ANSI SQL syntax, hence you may well find that on upgrading, some of your old MS Access SQL statements that do not follow the ANSI format, will provide unpredictable results. For example:


WHERE fieldname = NULL
would work fine with older drivers, but under the new ones, will probably return an empty recordset. Instead use the correct syntax of:


WHERE fieldname IS NULL

 

MS Access 97 Memory/Performance

MS Access is not the ideal database system on which to build scaleable web based applications. 'True' multi-threaded DBMSs such as SQL Server, Oracle or DB2 are much better suited to medium to large scale development. However, the convenience of MS Access and also the likelyhood of having to 'tap into' existing application data can not be overlooked, hence this makes it highly probable that as some time we will want to connect to this (what is essentially a desktop) DBMS. To be fair, MS Access often holds up very well (on small intranets for example), considering some of the conditions it is often put under.

Tips? Make sure you have up to date drivers (see previous point) which may offer better performing and more robust connection mechanisms. Also, if you are experiencing memory/performance problems through ODBC, try tweaking the following settings in the ODBC data source administrator:

  1. Select relevant Access 97 DSN on the System DSN tab
  2. Select Configure, then Advanced.
  3. Select MaxBufferSize and change the value to 8192 (512 is the default)
  4. Select Threads, and change the value to 20 (3 is the default)
  5. Confirm all the changes and exit the administrator
These new settings should provide you with better performance in most cases. Try increasing them slightly more if necessary, depending upon the resources you have available.

Finally, if you are still using ODBC you should really consider connecting directly using OLEDB. An example connection string could be:

"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=d:\websites\yourdir\yourdb.mdb;"

This is more efficient and reliable than ODBC.

::$DATA Security Fix

To protect your ASP scripts from being downloaded by a user appending ::$DATA to the script name in the URL, install the fix from Microsoft available at: http://www.microsoft.com/security/bulletins/ms98-003.asp. If you are using NT4 SP4 or above, then you should not have a problem, but it's worth checking.

IIS/NT Tips - ASP Application SettingsIIS5/Windows 2000 tip

IIS5 gives you more scope when setting up web applications. In IIS4 you basically had a choice to either run a web application "in process" with inetinfo (IIS process) giving best performance, but at the risk of a "dodgy" application corrupting the IIS process itself. The alternative was to choose "run in separate memory space", which gave better robustness, but at the expense of poorer performance.

IIS5 introduces the term "isolation" levels, which provides better control over your web applications:

  • Low Isolation. As IIS4 "in process". Best performance, but at the expense of the possibility of a rogue application bringing the service down.
  • Medium. The default in IIS5, and is a new IIS5 only level. ASP processes share a single process space, but outside of IIS itself.
  • High. As IIS4 "out of process". Provides the best reliability, at the expense of some performance. Each web application has its own process space.

For average hit rate sites, the IIS5 default is probably the best. Changing a web app's level to "low" will give noticeable improvements on high hit rate sites.

back to top

DISCLAIMER: Note these pages are a free resource for anyone wishing to reference them. Although every care is taken to ensure their correctness, the author takes no responsibility for any errors or problems that may occur through their use, or indeed misuse. These pages are copyight of Dave Clarke, Visualize Software Ltd 1997-2000 (all rights reserved).


 


© Copyright Dave Clarke, 1996-2010

Sponsors