code critique - am I creating a Rube Goldberg machine? - asp-classic

I'm making a fair amount of calls to database tables via ADO.
In the spirit of keeping things DRY, I wrote the following functions to return an array of values from a recordset.
Is this hare brained?
I use it mainly for grabbing a set of combo-box values and the like, never for enormous values. Example Usage (error handling removed for brevity):
Function getEmployeeList()
getEmployeeList= Array()
strSQL = "SELECT emp_id, emp_name from employees"
getEmployeeList = getSQLArray( strSQL, "|" )
End Function
Then I just do whatever I want with the returned array.
Function getSQLArray( SQL, delimiter )
'*************************************************************************************
' Input a SQL statement and an optional delimiter, and this function
' will return an array of strings delimited by whatever (pipe defaults)
' You can perform a Split to extract the appropriate values.
' Additionally, this function will return error messages as well; check for
' a return of error & delimiter & errNum & delimiter & errDescription
'*************************************************************************************
getSQLArray = Array()
Err.Number = 0
Set objCon = Server.CreateObject("ADODB.Connection")
objCon.Open oracleDSN
Set objRS = objCon.Execute(SQL)
if objRS.BOF = false and objRS.EOF = false then
Do While Not objRS.EOF
for fieldIndex=0 to (objRS.Fields.Count - 1)
If ( fieldIndex <> 0 ) Then
fieldValue = testEmpty(objRS.Fields.Item(fieldIndex))
recordString = recordString & delimiter & fieldValue
Else
recordString = CStr(objRS.Fields.Item(fieldIndex))
End If
Next
Call myPush( recordString, getSQLArray )
objRS.MoveNext
Loop
End If
Set objRS = Nothing
objCon.Close
Set objCon = Nothing
End Function
Sub myPush(newElement, inputArray)
Dim i
i = UBound(inputArray) + 1
ReDim Preserve inputArray(i)
inputArray(i) = newElement
End Sub
Function testEmpty( inputValue )
If (trim( inputValue ) = "") OR (IsNull( inputValue )) Then
testEmpty = ""
Else
testEmpty = inputValue
End If
End Function
The questions I'd have are:
Does it make sense to abstract all the recordset object creation/opening/error handling into its own function call like this?
Am I building a Rube Goldberg machine, where anyone maintaining this code will curse my name?
Should I just suck it up and write some macros to spit out the ADO connection code, rather than try doing it in a function?
I'm very new to asp so I have holes in its capabilities/best practices, so any input would be appreciated.

There's nothing wrong with doing it your way. The ADO libraries were not really all that well designed, and using them directly takes too many lines of code, so I always have a few utility functions that make it easier to do common stuff. For example, it's very useful to make yourself an "ExecuteScalar" function that runs SQL that happens to return exactly one value, for all those SELECT COUNT(*)'s that you might do.
BUT - your myPush function is extremely inefficient. ReDim Preserve takes a LONG time because it has to reallocate memory and copy everything. This results in O(n2) performance, or what I call a Shlemiel the Painter algorithm. The recommended best practice would be to start by dimming, say, an array with room for 16 values, and double it in size whenever you fill it up. That way you won't have to call ReDim Preserve more than Lg2n times.

I wonder why you are not using GetRows? It returns an array, you will find more details here: http://www.w3schools.com/ado/met_rs_getrows.asp
A few notes on GetRows:
Set objRS = Server.CreateObject ("ADODB.Recordset")
objRS.Open cmd, , adOpenForwardOnly, adLockReadOnly
If Not objRS.EOF Then
astrEmployees = objRS.GetRows()
intRecFirst = LBound(astrEmployees, 2)
intRecLast = UBound(astrEmployees, 2)
FirstField = 0
SecondField = 1
End If
'2nd field of the fourth row (record) '
Response.Write (SecondField, 3)

Yes, it makes sense to factor out common tasks. I don't see anything wrong with the general idea. I'm wondering why you're returning an array of strings separated by a delimiter; you might as well return an array of arrays.

Related

delete an element from an array in classic ASP

Given the following array as an example...
arr(0)(0) = 3
arr(0)(1) = name
arr(0)(2) = address
arr(1)(0) = 7
arr(1)(1) = name
arr(1)(2) = address
arr(2)(0) = 14
arr(2)(1) = name
arr(2)(2) = address
I need to delete the middle element (id=7) from the array. I understand that I need to loop through the array and move each record that isnt to be deleted into a new array. I tried like this...
Dim newArr,i
Redim newArr(Ubound(arr))
For i = 0 to Ubound(arr)
If (CStr(arr(i)(0)) <> 7 ) Then
newArr(i) = arr(i)
End if
Next
When debugging this I can see the if statement work so I know only 2 elements are copied but newArr is empty at the end of this. What am I missing. I am a PHP coder that is new to classic asp and Im used to having array functions that make this kind of thing unnecessary. Any help appreciated. Thank you.
You don't need new array, you can just reassign the items and "crop" the array:
Const removalIndex = 1
For x=removalIndex To UBound(arr)-1
arr(x) = arr(x + 1)
Next
ReDim Preserve arr(UBound(arr) - 1)
This code will remove the array item at index 1 from the main array. If you don't know in advance the index of the item to remove, you can easily find it with a simple loop over the array.
Instead of using array you can give Scripting.Dictionary a try.
It is much more flexible, and has, among others Remove method.
I suggest using Scripting.Dictionary and using it as a List/collection instead, as it allows for insertions and deletions. See here: Lists in VBScript
I don't know the definitive answer, but if I were to take a stab in the dark guess I'd suggest that since the array is two dimensional maybe you have to explicitly refer to it that way?
Dim newArr,i
Redim newArr(Ubound(arr),3)
For i = 0 to Ubound(arr)
If (CStr(arr(i)(0)) <> 7 ) Then
newArr(i)(0) = arr(i)(0)
newArr(i)(1) = arr(i)(1)
newArr(i)(2) = arr(i)(2)
End if
Next
I see some VBScript syntax issues. First:
arr(0)(0) = 3 'ERROR: Subscript out of range
arr(0, 0) = 3 'CORRECT
Next:
ReDim newArr(Ubound(arr)) 'this is 1 dimensional array
newArr(0) = arr(0) 'this will NOT work
newArr(0) = arr(0, 0) 'this will work
And finally: why you convert to String and then compare it to an Integer with:
(CStr(arr(i)(0)) <> 7)

Controlling Empty Arrays in Classic ASP

OK I'm a complete newbie to ASP.
I have a client with different content loading depending on what is passed in an array.
select case lcase(arURL(4))
Sometimes though, arURL(4) might be empty, in them cases I'm getting the following error:
Error running function functionName(), the error was:
Subscript out of range
Does anybody know a way to fix this?
Thanks
OK further code as requested. It is horrible code and I don't mean to cause anybody a headache, so please excuse it. Thanks again ........
function GetContent()
dim strURL, arURL, strRetval
select case lcase(request.ServerVariables("URL"))
case "/content.asp"
strURL = ""
arURL = split(request.querystring("url"), "/")
if request("page") = "" then
select case lcase(arURL(2))
case "searches"
select case lcase(arURL(1))
case "looking"
select case lcase(arURL(3))
case "ohai"
strRetval = "Lorem"
case "blahblah"
strRetval = "Lorem Ipsum"
case "edinburgh"
select case lcase(arURL(4))
case "ohai"
strRetval = "Ipsum"
case "ohno"
strRetval = "Lorem"
end select
case "bristol"
select case lcase(arURL(4))
case "some_blahblah"
strRetval = "LOREM"
case "overthere"
strRetval = "LOREM"
case "blahblah"
strRetval = "LOREM"
end select
case "cambridge"
select case lcase(arURL(4))
case "some_rubbish"
strRetval = "Lorem"
end select
case else
strRetval = " "
end select
case else
strRetval = " "
end select
case else
strRetval = " "
end select
end if
end select
strRetval = strRetval & "<style>h2{border: 0px);</style>"
GetContent = strRetval
end function
You are using value passed over the querystring and split it by "/" character - when the value does not contain "enough" slashes, you will get error and the code will crash.
For example, if the querystring parameter url will be only "/something" then even arURL(2) will fail since the array has only two items. (First one is empty string, second is "something")
To avoid all this mess, best way I can advice is writing custom function that will take array and index as its arguments and return either the item in the given index if exists otherwise empty string:
Function GetItemSafe(myArray, desiredIndex, defValue)
If (desiredIndex < LBound(myArray)) Or (desiredIndex > UBound(myArray)) Then
If IsObject(defValue) Then
Set GetItemSafe = defValue
Else
GetItemSafe = defValue
End If
Else
If IsObject(myArray(desiredIndex)) Then
Set GetItemSafe = myArray(desiredIndex)
Else
GetItemSafe = myArray(desiredIndex)
End If
End If
End Function
(ended up with more generic version, letting the calling code decide what is the default value in case index is out of array range)
Having this, change your code to use the function instead of accessing the array directly.
This line for example:
select case lcase(arURL(2))
Should become this instead:
select case lcase(GetItemSafe(arURL, 2, ""))
Change the rest of those lines accordingly and you'll no longer get errors when the given value won't be valid.
What that error is saying at the most basic level is that you're trying to get information from an array element that doesn't exist, eg arURL may have been declared for 3 elements, but accessing the 4th generates the "subscript out of range" error.
If you're keying on the last element in the array, you might look at the UBound() function, which returns the high index element in an array, eg:
select case lcase(arURL(ubound(arURL))
However, there might be something else going on in the code that would change how you determine which element should be used as the target of the "select case," hence the suggestion to post more of the code.

Changing increment variables causes for loop to go over limit

In my loop I am trying to remove items from a list that have a certain name. I can't do this with a for-each, so I tried a regular for-loop. It seems the for loop is not working as expected. It always goes over the limit. I had to put an if-then to break out of the loop (very ugly solution). I'm trying to find the correct way to accomplish this.
Dim canShowNextTable As Boolean = False
Dim allTablesInTab As List(Of Control) = ctrlFinder.GetTypeOfControls("Table", parentForm.Controls)
Dim totalTables As Integer = allTablesInTab.Count - 1
For i As Integer = 0 To totalTables
If allTablesInTab.Item(i).ID = "CustomerTable" Or _
allTablesInTab.Item(i).ID = "PMTable" Or _
allTablesInTab.Item(i).ID = "TableAListClrnCheck" Or _
allTablesInTab.Item(i).ID = "TableBListClrnCheck" Or _
allTablesInTab.Item(i).ID = "TableCListClrnCheck" Or _
allTablesInTab.Item(i).ID = "TableDListClrnCheck" Or _
allTablesInTab.Item(i).ID = "TableSignature" Then '' If the ID is one of these remove it from list
allTablesInTab.Remove(allTablesInTab.Item(i))
totalTables = totalTables - 1 '' Decrement number of tables to loop through
i = -1 '' Reset counter to prevent going over or premature stopping
End If
If i = 3 AndAlso totalTables = 3 Then '' Since loop continuously goes over limit, use if-then to exit for-loop
Exit For
End If
Next
You need to traverse your loop in reverse because once you remove an item the total count has changed and is no longer correct. Looping in reverse avoids this issue.
For i As Integer = totalTables To 0 Step -1
Also, instead of allTablesInTab.Remove(allTablesInTab.Item(i)) you can use:
allTablesInTab.RemoveAt(i)
Work through this logic on paper or the debugger to properly grasp the concept of what's happening. You might also find this related question useful. It's in C# but the concepts are the same: How to remove elements from a generic list while iterating around it?

Simple encrypt/decrypt functions in Classic ASP

Are there any simple encrypt/decrypt functions in Classic ASP?
The data that needs to be encrypted and decrypted is not super sensitive. So simple functions would do.
4guysfromrolla.com: RC4 Encryption Using ASP & VBScript
See the attachments at the end of the page.
The page layout looks a bit broken to me, but all the info is there. I made it readable it by deleting the code block from the DOM via bowser development tools.
Try this:
' Encrypt and decrypt functions for classic ASP (by TFI)
'********* set a random string with random length ***********
cryptkey = "GNQ?4i0-*\CldnU+[vrF1j1PcWeJfVv4QGBurFK6}*l[H1S:oY\v#U?i,oD]f/n8oFk6NesH--^PJeCLdp+(t8SVe:ewY(wR9p-CzG<,Q/(U*.pXDiz/KvnXP`BXnkgfeycb)1A4XKAa-2G}74Z8CqZ*A0P8E[S`6RfLwW+Pc}13U}_y0bfscJ<vkA[JC;0mEEuY4Q,([U*XRR}lYTE7A(O8KiF8>W/m1D*YoAlkBK#`3A)trZsO5xv#5#MRRFkt\"
'**************************** ENCRYPT FUNCTION ******************************
'*** Note: bytes 255 and 0 are converted into the same character, in order to
'*** avoid a char 0 which would terminate the string
function encrypt(inputstr)
Dim i,x
outputstr=""
cc=0
for i=1 to len(inputstr)
x=asc(mid(inputstr,i,1))
x=x-48
if x<0 then x=x+255
x=x+asc(mid(cryptkey,cc+1,1))
if x>255 then x=x-255
outputstr=outputstr&chr(x)
cc=(cc+1) mod len(cryptkey)
next
encrypt=server.urlencode(replace(outputstr,"%","%25"))
end function
'**************************** DECRYPT FUNCTION ******************************
function decrypt(byval inputstr)
Dim i,x
inputstr=urldecode(inputstr)
outputstr=""
cc=0
for i=1 to len(inputstr)
x=asc(mid(inputstr,i,1))
x=x-asc(mid(cryptkey,cc+1,1))
if x<0 then x=x+255
x=x+48
if x>255 then x=x-255
outputstr=outputstr&chr(x)
cc=(cc+1) mod len(cryptkey)
next
decrypt=outputstr
end function
'****************************************************************************
Function URLDecode(sConvert)
Dim aSplit
Dim sOutput
Dim I
If IsNull(sConvert) Then
URLDecode = ""
Exit Function
End If
'sOutput = REPLACE(sConvert, "+", " ") ' convert all pluses to spaces
sOutput=sConvert
aSplit = Split(sOutput, "%") ' next convert %hexdigits to the character
If IsArray(aSplit) Then
sOutput = aSplit(0)
For I = 0 to UBound(aSplit) - 1
sOutput = sOutput & Chr("&H" & Left(aSplit(i + 1), 2)) & Right(aSplit(i + 1), Len(aSplit(i + 1)) - 2)
Next
End If
URLDecode = sOutput
End Function
I know is a bit late for BrokenLink, but for the record and others like me who were looking for the same.
I found this https://www.example-code.com/vbscript/crypt_aes_encrypt_file.asp.
It needs to install a chilkat ActiveX component on WindowsServer. But this inconvenient becomes convenient when looking resources and processing time.
Its very easy to use, and the given example is pretty clear. To make it your own, just change the "keyHex" variable value and voilá.

ASP Classic - Type mismatch: 'CInt' - Easy question

Having an issue with type conversion in ASP classic.
heres my code:
Set trainingCost = Server.CreateObject("ADODB.Recordset")
strSQL3 = "SELECT cost1 FROM tblMain WHERE (Booked = 'Booked') AND (Paid IS NULL) AND (PaidDate BETWEEN '01/04/" & startyear & "' AND '31/03/" & endyear & "')"
trainingCost.Open strSQL3, Connection
trainingCost.movefirst
totalTrainCost = 0
do while not trainingCost.eof
trainCost = trainingCost("cost1")
If NOT isNull(trainCost) then
trainCostStr = CStr(trainCost)
trainCostStr = Replace(trainCostStr, "£", "")
trainCostStr = Replace(trainCostStr, ",", "")
totalTrainCost = totalTrainCost + CInt(trainCostStr)
end if
trainingCost.movenext
loop
trainingCost.close
when I run this I get the following error:
Microsoft VBScript runtime (0x800A000D)
Type mismatch: 'CInt'
/systems/RFT/v1.2/Extract.asp, line 43
which is "totalTrainCost = totalTrainCost + CInt(trainCostStr)"
Im guessing that the problem is to do with the String value being uncastable to Int in which case is there any way to catch this error? I havent worked with asp classic much so any help would be usefull
cheers
-EDIT-
the type of column cost1 is String as it may contain a number or a sequence of chars eg £10.00 or TBC
You have a couple of choices. You can be proactive by checking ahead of time whether the value is numeric using the IsNumeric function:
If IsNumeric(trainCostStr) Then
totalTrainCost = totalTrainCost + CInt(trainCostStr)
Else
' Do something appropriate
End If
...or you can be reactive by using error catching; in Classic ASP probably easiest to define a function and use On Error Resume Next:
Function ConvertToInt(val)
On Error Resume Next
ConvertToInt = CInt(val)
If Err.Number <> 0 Then
ConvertToInt = Empty
Err.Clear
End If
End Function
Or return 0 or Null or whatever suits you, then use it in your trainCost code.
Note that CInt expects an integer and will stop at the first non-digit, so "123.45" comes back as 123. Look at the other conversions, CDouble, CCur, etc.
Rather than casting to a string, why not use CCur (Cast as Currency) so that your commas and any currency symbols (I think) are effectively ignored while doing arithmetic operations?
Potentially solving the wrong problem, depends on the type of Cost1 within the database but the code is looping through the records to generate a total.
strSQL3 = "SELECT sum(cost1) FROM tblMain WHERE (Booked = 'Booked') AND (Paid IS NULL) AND (PaidDate BETWEEN '01/04/" & startyear & "' AND '31/03/" & endyear & "')"
trainingCost.Open strSQL3, Connection
etc and just read off the value as a total.
I don't see why the RS is being looped to generate a sum when the database can do that work for you. All the conversion work it has generated just looks artifical.
Heh heh. Classic ASP. You have my pity :) Anyway,
On error resume next
And then on the next line, check that it worked.
Though maybe you want CDouble. Is that a function? I can't remember.

Resources