Use of Proper File Extensions
Never put ASP or other server side scripting code in files other than .ASP. Code in non-ASP files (e.g., .inc) exposes the site to several security holes. If using the FrontPage Server Extensions, put include files in the "_private" directory for added security. If there are not any server side scripts in a file, name the file .HTM. The web server will skip ISAPI calls, thus serving up the page more quickly. Global.ASA specifically is as safe as an ASP file. Dead files can lead to places for people to hack. This is especially true for files other then .HTM or .ASP. .LOG files (for example generated by FTP) can provide details of your file structure and file names best not shared with the world. It is strongly recommended that all dead files should be removed from the web server
Type Notation
Use proper Hungarian notation for variables and objects. The purpose of Hungarian notation is to be able to interpret the type and purpose of a variable. For local and public variables use mixed case of an initial lowercase Hungarian prefix followed by at least one uppercase letter. See table of prefixes below:In general programmers should choose and consistently follow either the long or short Hungarian notation as below:
1 letter 3 letter
S str string sLastName, strFirstName
I int Integer iLoopVar, intQuantity
L lng Long lCountofRows, lngRecordsAffected
B bln Boolean bSold, blnFileClosed
D dbl Double dNationalDebt, dblStarsinUniverse
C cur Currency cAmount, curPrice
T dt DateTime tDOB, dtApproved
O obj Objects oRS, objConnection
X x Structures or other xOblique, xFiles
Hungarian notation can also be used with form objects to make forms more clear, although many programmers choose to use all uppercase meaningful names for POST type forms and very short uppercase names for GET type forms as most of the HTML form elements are variations on <INPUT>. S str string sLastName, strFirstName
I int Integer iLoopVar, intQuantity
L lng Long lCountofRows, lngRecordsAffected
B bln Boolean bSold, blnFileClosed
D dbl Double dNationalDebt, dblStarsinUniverse
C cur Currency cAmount, curPrice
T dt DateTime tDOB, dtApproved
O obj Objects oRS, objConnection
X x Structures or other xOblique, xFiles
Prefix Form Component
txt Input Box
chk Checkbox
opt Option/Radio Control
cmd Button
img Image
hdn hidden Input
Use 'c' + all upper case for constants. Constants should be used whenever possible as they have lower overhead then variables. Some expmles of constants:txt Input Box
chk Checkbox
opt Option/Radio Control
cmd Button
img Image
hdn hidden Input
Const conPI = 3.14159
Const conSITENAME = "my site" Meaningful Variable and Object Names Variable names should be meaningful. Combined with the use of Hungarian notation, this leads to clear, self documenting code where the purpose and type of each variable is obvious from inspection.
Clear code will speed up the process of maintenance and 'feature' debugging.
Examples:
Good Bad
strCompanyName com
intCartCount CT
lngCustomerID cID
blnLogedIn ShpC
dblGrossWeight gWt
curGrandTotal t
objCookieCart oCook
Likewise, it is good to use prefixes for filenames of pages with similar functions. For example, all of the customer maintenance pages could start with the prefix 'cust' e.g. custNew.ASP and custUpdate.ASP. strCompanyName com
intCartCount CT
lngCustomerID cID
blnLogedIn ShpC
dblGrossWeight gWt
curGrandTotal t
objCookieCart oCook
Use of Scope
For IIS5 avoid the use of Public except when data needs to be transferred between page invocations use Dim instead to save processing overhead and memory. Use 'option explicit'. Keep variables local (inside a sub or function) whenever possible for speed and efficient memory usage. The speed is gained as the variables will be found within the current scope limiting searching of the entire name space. Declare (and comment) all variables at the top of each scope range. Groups of declarations at the beginning of the scope is more efficient. Remember to initialize values before using them to set their type and avoid unpleasant involuntary type conversion issues. Likewise, it is good practice to explicitly convert one type to another using the provided conversion functions.Application Scope Use arrays to store cached information that changes very infrequently. For example locale information or shipping methods. Since this data can be loaded in Application_OnStart() and is very unlikely to change from DB this gives an immediate performance boost. Be wary however, as very large array objects are copied from application scope to page scope when referenced and this operation can take time. Do not persist the dictionary object , VB collections, property bags and other such objects in application scope. In general though these objects offer a great deal of flexibility and offer handy methods they are:
Not stable in application scope (particularly the dictionary object) and can cause sites to run slowly or crash.
Significantly more resource intensive when compared to arrays. The ratio of stored bytes to overhead bytes is generally poor for the kind of data most frequently persisted in most applications. In general, with the exception of special objects which is specifically designed to run in application scope, objects should not be persisted in application scope. This does not mean that local variables can't be created in order to generate (for example) arrays used to store information retrieved from various COM objects. Many COM objects not designed to be run in application scope (apartment threaded instead of both threaded) can cause thread contention problems and reduce performance. For best result COM objects that must be instantiated in application scope should be done on a "just in time" basis, used and then immediately explicitly disposed (ADO objects are good examples of this.). Remember that VB objects should not be persisted in application scope as they are compiled as Single Threaded Apartments (STA) and thus will exhibit the same scalability difficulties as if Sessions were used (e.g. thread affinity, serialization to that thread, and context switching for each instance).
Sessions are very strongly discouraged IIS4 the default state is sessions are enabled. In IIS5 the default state is that sessions are disabled. Sessions are generally discouraged. Sessions have thread affinity, hence processor affinity. Thus once a session is attached to a thread running on a specific CPU, CPU utilization can become lopsided, defeating the CPU balancing features in IIS.
Sessions can fail when confronted with certain types of proxy servers e.g. AOL.
When a site is scaled by adding servers and using (for example) Cisco Local Director to load balance your site; sessions present a problem. Sessions are machine specific. Information in sessions stored on one machine is not available if Local Director switches an incoming request to a different machine then the original request. If this is kludged around in code by dynamically generating links to point to the specific machine, load balancing is essentially defeated.
Likewise, using the Cisco Local Director sticky bit is defeated by some proxy server implementations, e.g. AOL, so that workaround is not a good one.
Servers in a farm cannot be brought out of service for maintenance without causing loss of sessions.
The memory usage patterns differ between the implementation of sessions and IIS causing resource conflicts.
Sessions enabled on the server use resources even if they are not used.
Session-less sites are more conducive to book marking as the bookmarks will refer to the "site" rather then a specific server. Thus if a server is taken out of service the bookmark will still work.
Data placed in the session scope is not de-allocated until the session is terminated or times out. This results in poor resource allocation.
Session state may be turned off on a page by page basis by embedding:
<% @EnableSessionState = False %>
The performance gain can be significant on pages containing <FRAMESET> elements as internet explorer will allocate a thread or process for each frame in parallel. Having session state enabled forces IIS to respond to the requests serially thus offsetting the advantages IE gave the frame set. Sessions can be disabled on an entire server if desired, this can cause additional resources to be available. Alternatives to sessions include
Form Hidden Fields are also a good method, the disadvantage of which is that they are only persisted when page transitions happen via the posting mechanism.
QueryString variables can also be used to pass information. The disadvantage to passing query strings is that the information passed can not exceed 216 characters including the URL on some browsers and through some proxy servers, and the information passed is very visible to the user. The advantage of passing in a query string is that the passing can happen either by a hyperlink, form post or both.
Other cookies can be generated by Response.Cookie() and retrieved by Request.Cookie(). Be aware, ASP provides no encryption of cookies so the habit of storing credit card information in plain text in a cookie is an anathema.
You can encode data into XML using ADO. The resulting XML stream can be written to the page in between comments, stored in a DB, or written to the client in a series of cookies. The XML can be turn back into a recordset and used in your pages. You can also use an XML DOM object to make and iterate XML documents.
Logical Design Use a function when there is a single result and the the result is meant to be used or tested in line. E.g. The vbScript function trim() is usually used in line and has no effect when used as a statement as the trim function has no output arguments.For routines that have success/failure indicators, converting them to functions that return a Boolean or Integer success or failure indicator allows the code to be called from with in an IF statement, thus making the code more clear.'Sample function declaration, implicitly returning success/failure as a long integer
'Any non-zero number is an error, 0 = Success
Function DoProcessingofOrder()
'This can be used as follows
If DoProcessingofOrder() > 0 Then
'Redirect to somewhere else
End if Use a procedure when multiple output arguments are required and/or no return is desired. Use functions and subs to create re-usable code blocks and to chunk your logic into reasonable blocks of functionality Good chunking of code into subroutines and functions will make the code more maintainable and speed debugging (as you can step over an entire subroutine if it is known to work.) Chunking also allows the clever use of includes to share code across pages while maintaining a single version.
Use of Includes Server side includes are preferred over FrontPage includes as they are faster and more stable. FrontPage includes have additional functionality that can be handy such as stripping out unneeded HTML elements e.g. <BODY> and <HEAD> from HTML includes. For all server side notations e.g. #INCLUDE are processed before any of the script on the page, hence the following code will not have the results one might expect:
If blnLogedIn Then
%><!-- #INCLUDE FILE='IsCust.Asp' --><%
Else
%><!-- #INCLUDE FILE='DoLogin.Asp' --><%
End If
Both files are included and then the script is processed!
Server side includes are a powerful tool for organizing code into libraries, however moderation is urged as each server side include consumes CPU resources as the pages are "composed" by merging the includes into the main document. Some reasonable (1-3) number of includes is OK, too many (>5) can seriously degrade performance. Since it is hard to debug included code and even harder to debug code in nested includes the use of nested includes should be minimized. In some circumstances the interpreter/compiler may have difficulty resolving deeply nested includes properly. In general, here are some good guidelines:
Avoid making huge utility includes, this avoids the amount of code that must be parsed for each page.
Only put code into an include that will be shared between multiple files. The practice of building an entire site using includes to hold all of the code results in poor performance and scalability.
Avoid nesting entirely if possible.
Object References
When possible, refer to objects or members of a collection only once. Each object or collection reference is expensive in terms of computation when compared to referencing the same data in a variable.Slow:
if ObjCustomer.Address2 <> "" then
Response.Write ObjCustomer.Address2
end if
Faster, use the reference on once :
strCustAddress2 = ObjCustomer.Address2
if strCustAddress2 <> "" then
Response.Write strCustAddress2
end if
Faster Still, use the reference only once and use the Len() function to test:
strCustAddress2 = ObjCustomer.Address2
if Len(strCustAddress2) > 0 then
Response.Write strCustAddress2
end if
For example, form and query string collection values stored in the request object should be copied into a variable for use:
For Request objects particularly, this is handy as it makes it so if the name of the control on the form changes, the code need only be modified in one location.
dim strLastName 'Customer Last Name
strLastName = Request.Form("txtLastName")
If possible and the object supports it use <OBJECT> elements instead of Server.CreateObject() as IIS delays creating the object until it is first used, where as CreateObject() causes the object to be created whether is is subsequently used or not. The reason this is not done, is that <OBJECT> tags are very hard to debug and can not be error trapped by the programmer. A low level IIS error is generated instead that causes a 40x error of some sort depending on the machines configuration.
So in practice <OBJECT> tags are only used if 1) the object is internal or 2) there is a high degree of confidence that the object can be created without error. The <OBJECT> tag is usually not used for connection oriented objects such as ADO or objects that reside in MTS.
All objects created in a page should be explicitly disposed. While IIS will "cleanup" allocations as the page passes out of scope, disposing of objects as soon as possible will result in resources being made available sooner.
Particularly for objects residing in MTS or for ADO objects, which are cached by the server, a strategy of "just in time" instantiation is recommended, creating objects shortly before they are needed, using them and then explicitly disposing them. This is effective in minimizing resource use because the "expense" of object creation is moderated by IIS/MTS caching frequently used objects thus proving a pool of frequently used objects that need not be created from "scratch" when needed. In general create object late and dispose of them early.
If an object has a method to open it e.g., .OPEN, the corresponding close method, e.g., .CLOSE should be invoked before it is disposed. This is particularly important for ADO objects. In theory, the ADODB implementation is proof against data corruption caused by sudden de-allocation, however explicit use of close provides for an orderly object shutdown, particularly for objects that are in a transaction or are updateable. The Server Side Variables collection is built dynamically by IIS the 1st time it is referenced on a page. If you can capture value of key server side variables (such as browser type etc) on your page once and then propagate them via another method, you can gain a performance increase. Likewise the MapPath() method is expensive. Consider, capturing the path of the root web in an application variable for re-use on all of your pages. The MapPath() call implicitly causes the Server Variables collection to be built.
Object Validity
Never assume an object is valid.
Test all objects after creation for being nothing:
Set objRS = Server.CreateObject("ADODB.RecordSet")
If objRS is Nothing Then
'Take steps, redirect, etc.
End if
Some cases an application should handle gracefully are:
DB is unavailable
DLL is creatable
Method calls to DLL succeed
Objects are still in scope
Collection items exist
ASP Coding Style
Use 'option explicit' to catch undeclared and misspelled variables. This prevents errors as follows:
dim colObject 'as Integer
colObjects=0
For Each Object in Collection
colObject=colObjects + 1
Next
Notice mixing of singular and plural as variable is misspelled in two places. This results in the wrong count being returned from the loop.
In addition, use of option explicit can cause ASP pages to run faster.
Explicitly using < @LANGUAGE = vbScript %> is not required on web servers as it is always set to that as the default. Explicitly declaring it is an expensive call as ASP must stop and check the default language for scripting calls. If the programmer does not have explicit control over the server on which the pages are running e.g. the site is hosted by a 3rd party, the use of @Language is still recommended as forces the script on the pages to run correctly.
Use good script coding style:
o Indent blocks of code
o Use blank lines around blocks
o Declare one variable per line (faster) and document variables
o Use comments when required to document tricky or unusual code sections
Put ASP delimiters '<' and '>' in the left hand margin to make code blocks easy to spot (except for inline blocks e.g. < = strVar >).
Combine multiple sequential Response.Write lines into one larger single statement when it makes sense to do so. This performance gain can be offset if extensive string concatenations are required.
Slower: (one per line)
Response.Write Name & "<br>"
Response.Write Street & "<br>"
Response.Write City & ", " & State & ", " & Zip & "<br>"
A little faster (de-reference the object) :
With Response
.write Name & "<br>"
.write Street & "<br>"
.write City & ", " & State & ", " & Zip & "<br>"
End With
Faster: (Concatenate strings)
Dim strHTML
strHTML = strHTML & Name & "<br>"
strHTML = strHTML & Street & "<br>"
strHTML = strHTML & City & ", " & State & ", " & Zip & "<br>"
Response.Write strHTML
Faster Still (Use continuation character):
Response.Write Name & "<br>" & _
Street & "<br>" & ", " & _
State & ", " & _
Zip & "<br>"
Even Faster (all on one line):
Response.Write Name & "
" & Street & "
" & ", " & State & ", " & Zip & "<br>"
Avoid extensive string concatenations, especially in loops. The total cost of string concatenations is given by the formula:
Cost = N(N+1)/2
Likewise, use of the ReDim operator should be limited. Resizing an array is done by allocating a new block of memory of the size desired and then copying the bytes to the new array and finally, de-allocating the old array after reassigning the variable pointer to the new array. If an array must be grown dynamically, growing an array by jumps of multiple elements and keeping a variable to serve as the actual number of elements in use may prove more efficient:
Const cGROWBY = 10 'Number of elements to grow by at one time
Dim intArrayMax 'Actual Number of Element in Use
Dim myArray(cGROWBY)
'... processing a data stream whose current value is in myValue
intArrayMax = intArrayMax + 1 'One More Array Element is Needed
if intArrayMax > UBound(myArray) then
ReDim Preserve myArray(UBound(myArray) + cGROWBY)
end if
myArray(intArrayMax) = myValue
'.... and so on
In this example the array is grown by 10 each time, some experimentation should be done to determine the best amount to grow by each time. In addition, the initial size of the array can be tuned to make the number of allocations minimized while not wasting memory on unused elements. Again, the best strategy is to attempt to compute how much memory is required in advance, if possible.
Arrays are the one of the most efficient storage mechanisms, in general they use much less memory then dictionary objects, collections, or property bags. In addition, a clever design using arrays will usually be faster.
Avoid context switching.
Context switching occurs every time the switch is made between vbScript and HTML code blocks. Each context switched requires a significant amount of time and resources on the server.
Following the previous rule extensive use of < =VariableName > should be avoided as well.
Pool or combine small blocks together to make larger blocks of HTML and script.
Avoid switching script types e.g. between vbScript and jScript as this context swap is very expensive as the number of precompiled pages in the cache is limited.
While making pages out of process can provide protection against bad page behavior and be helpful in debugging, running pages in process with IIS can result in performance gains. A careful analysis of trade-offs should be done before deciding if a page or set of pages should be run in process or out of process.
Never assume that data returned from the browser is correct. Always check order numbers, prices, SKUs, etc against to make sure data is correct. For example:
orderhistory.asp?orderid=1234
Does Order 1234 belong to the current customer? It should be checked against . Checking the referring URL is inadequate as that URL may be a) a proxy server "fronting" for 100s of people 2) spoofed 3) wrong for other reasons.
Use of white space and indenting significantly improves the maintainability of the code, while costing almost nothing in resources.
Use the Len() function to test for empty strings. Much faster.
Slow:
If strX <> "" then
Faster:
If len(strX) > 0 then
If you are using IIS 5 use Server.Transfer or Server.Execute rather then Response.Redirect. Response.Redirect generates a client side redirection header while, the other two calls perform server side redirection which is much faster.
For IIS4 (where buffering is off by default) Use Response.Buffer = True on all ASP pages.
Buffering increases performance by decreasing the number of "writes" to the client
Allows the page to be redirected in case of an error.
However, be wary of un-moderated buffering on time consuming pages with large outputs (such as large tables).
In buffered output you can use Response.IsClientConnected after the first Response.Flush to test to see if the client is still there, and abort by using Response.End if not. Warning: for IIS4 and below the IsClientConnected call may return incorrect values unless some information has been flowed to the client.
Strategic use of Response.Flush can keep pages from timing out. For large tables, it may be necessary to additionally end and restart the table. For example:
lngRowCount = 0
Response.Write "<table width='100%'>"
while not rs.eof
lngRowCount = lngRowCount + 1
if lngRowCount Mod 100 = 0 then
Response.Write "</table>"
Response.Flush
Response.Write "<table width='100%'>"
end if
'Write out row
wend
Dead Code/comment Removal
Remove all dead code. ASP is interpreted, so dead code, even commented out makes the page "heavy" e.g. uses more memory in the page cache, takes longer to load and thus is more processor intensive.
Sadly, this rule applies to excessive HTML comments as well as these are flowed to the client browser.
In general comments should be stripped from production code to save "weight" and to avoid reveling secrets about the way the code works.
Form Design Issues
Forms should post to themselves. This has several advantages:
when data entered is incorrect (user data entry error) as the fields on the form can be marked as being incorrect or missing easily.
secondly, the number of redirects are minimized
lastly, all of the business logic related to a single function is contained in the page.
Data entry items on a form should have the correct tab order and be arranged in an order that makes sense to data enter.
Data entry fields should be aligned to each other as much as possible to make the data entry experience as pleasant as possible.
Use client side scripting (JavaScript for maximum compatibility) to validate user input. This has the effect of reducing round trips to the server. However, it should never be assumed on the server side that the data is correct when it is received. Client side validation supports but does not replace good data validation on the server.
Check HTML for Correctness
An automated HTML checking tool should be applied to all pages to make sure that the HTML on the page is correct.
Particularly troublesome items include
Missing form elements
Malformed tables, particularly incorrect spans
Missing end tags for matched elements e.g. missing </tr> or </td>
Incorrect target references for frames
Invalid nesting of tags
Invalid HTML can cause slow loading pages as browsers struggle to interpret "do as I mean, not as I say" code.
It is very helpful to reformat pages so that the following HTML standards are followed:
Use all lowercase HTML tags. This is a change over old advice that called for all uppercase HTML elements. The new standard is based on the new xHTML standard that makes all uppercase takes depreciated. So it would be good to get into the habit of using all lower case elements now.
ASP Code should be title cased
vbScript code should use the correct mixed case as in the sections above
Error Handling
In general, error prevention is much better than error handling. The error trapping mechanism is computationally expensive.
There are certain occasions where explicit error handling is strongly recommended:
When using the file system objects
When invoking 3rd party objects such as controls or applets
When opening ADO connections or recordsets
When setting an error trap using On Error Resume Next make sure to turn it off as soon as possible with On Error Goto 0
Setting an error trap without checking Err.Number after calls is not recommended. Once a page has an error it is usually disadvantageous to continue processing.
ADO
Use GetString(), GetRows(), or disconnected recordsets. These methods reduce the number of trips across the network required to retrieve database data. If your recordset does not need formatting (consider doing that sort of work in the query or in a stored procedure) using GetString cleverly to output tables quickly for tables containing few rows (About 50, if there are many more rows the size of the string variable created becomes an issue, test typical outputs using various techniques to see which is faster in your case).
Response.Write "<table border='1'><tr><td>"
S = rs.GetString( , , "</td><td>","</td></tr><tr><td>", " " )
S = Left( S , Len(S) - 8) 'Remove extra <tr><td>
Response.Write S & "<</table>"
Reduce time that database connections are open. Don't open database connections until immediately before you need them, and close then dispose them as soon as possible. This returns the connection resources to IIS which maintains a pool that can be rapidly be reused.
Use connectionless fire hose cursors when possible: Client Side, No Locks, Not Updateable. In general use the very fewest connection features possible to make the "cheapest" cursors.
Do not re-use ADO command objects in the same page. There is a bug that can cause your pages to crash. Dispose and re-create your command objects or better yet use the techniques below.
If you need to fetch records from multiple queries serially, then use the following technique:
mySQL = "Select * From A;Select * From B" 'Notice the two queries separated by a semi-colon (;)
Create your record set... The recordset object will point to the 1st cursor. Then use
rs.NextRecordset
This moves to the next cursor. You can not go back to make sure you have done all that you need to with the 1st cursor. This technique is much faster and more resource efficient then using multiple recordsets or the same one serially.
Use stored procedures
Stored procedures represent server side business logic and can be used from VB or ASP.
Stored procedures are much faster then constructing queries "on the fly"
Stored procedures can be modified/tested with out recompiling the program or editing the script
How to run stored procedures from VB/ASP
The general strategy for performance is to avoid the command object unless you need to create stored procedures on the fly with dynamic parameters or you are pushing a larger number of parameters at the servers..
Dim lngRecordsAffected
Dim strSQL
Dim objADORS 'ADODB.Recordset
Dim objADOConnection 'ADODB.Connection
'... create and open connection and create recordset object
Instead of creating a command object build a command string that includes the stored procedure and all of its parameters.
strSQL = "usp_DoIt2It " & para1 & "," & para2 'or whatever is required.
For stored procedures that do not return rows:
Add the 'call' verb and braces:
strSQL = "{Call " & strSQL & "}"
objConnection.Execute strSQL, lngRecordsAffected , adCmdStoredProc
'You can check lngRecordsAffected to see how many rows were affected
'For stored procedures that return rows:
Set rs = objCN strSQL, lngRecordsAffected , adCmdStoredProc
0 comments
Post a Comment