在 Classic ASP 中添加可选的键/值方法参数的简单技术





5.00/5 (1投票)
如何在 VBScript 和经典 ASP 中的任何函数中添加可选参数
引言
本文将介绍一种简单快速的数据结构的创建和使用,该结构支持前向键值对,可用作方法参数。我们将介绍该数据结构、促进其使用的方法,以及在 HTML Helper 类中的一个示例实现。
注意:虽然乍一看可能令人困惑,但本文描述的技术功能极其强大,并在我创建的、为 Classic ASP 开发带来理智的全功能 MVC 框架 Sane 中广泛使用。Sane 提供数据库迁移、领域和视图模型、参数化自动映射器、可插拔验证器、可枚举对象等等。可在 github.com/davecan/Sane 获取。KVArray
为 HTML 助手和领域存储库提供支持。您可以在那里搜索 KVArray
,或使用本文底部的搜索链接。我并不期望任何人在生产环境中使用 Sane,但如果您必须维护 Classic ASP,可能会有一些可以借鉴的技术来清理您的代码。尤其是在现代系统中,VBScript 和 Classic ASP 可以比老式的意大利面条式代码强大得多,Sane 应该清楚地证明这一点,但现代语言和框架仍然会超越它。
KVArray:数组的简单变体
在开发 Sane 框架时,我再次被 VBScript 的固有局限性所困扰,即它不支持键/值参数。我真的需要它们提供的灵活性,并因它们的缺失而感到沮丧。我没有(再次)接受这个限制,而是着手寻找解决问题的方法。结果就是 KVArray
,Key Value Array 的缩写。这个简单的变体具有深远的益处:它释放了 ASP 中巨大的力量,并允许我们显著简化代码,这反过来又帮助我们创建更好的应用程序。
KVArray
不过是一个遵循“一个奇怪规则”的数组
KVArray 是一个标准的 VBScript 变体数组,其中元素的总数始终为偶数,每个键包含在偶数位置,其对应的值包含在紧随其后的奇数位置。
现在定义已经完成,它在代码中是这样的
dim kv_array : kv_array = Array("var_1", "value 1", "var_2", "value 2")
就其本身而言,这并不引人注目。“为什么不使用 Dictionary
呢?”你问。两个原因
- 创建字典需要实例化 COM 组件。这可能代价高昂,而我们需要在任何地方使用这种结构。实例化几十个 COM 组件并非最佳选择。
- 出于我们的目的,我们不需要通过传递键的名称来检索值。因此,这种数据结构针对快速前向迭代进行了优化,而不是像
Dictionary
对象那样进行任意键查找。
于是你可能会说,“但这不就是老一套的传数组作为参数的方法,自古以来就一直在用。”嗯,不完全是。那些方法要求方法开发者每次想要允许数组时都重新创建键/值提取过程。这很麻烦,容易出错,也意味着每个方法都可以选择以稍微不同的方式处理数组。通过这种方法,处理是标准化的,方法调用者可以期望数据结构每次都以相同的方式使用。
那么,我们可以用它做些什么呢?这里有一些例子
<%= HTML.LinkTo("This is a simple link", url) %>
<%= HTML.LinkToExt("This link has querystring params", url,
array("var_1", 1, "var_2", "value_2", empty) %>
<%= HTML.LinkToExt("This link is highlighted and opens in a new window",
url, empty, array("class", "highlight", "target", "_blank")) %>
输出如下
<a href='http://www.example.com'>This is a simple link</a>
<a href='http://www.example.com?var_1=1&var_2=value_2'>This link has querystring params</a>
<a href='http://www.example.com' class='highlight' target='_blank'>This link is highlighted and opens in a new window</a>
如您所见,一个主要模式是使用 KVArray
遍历并将键/值对作为相应的 HTML 键/值对附加,用于 URL 查询字符串、HTML 属性等。这些都是简单的示例——我们用 KVArray
可以做的事情远不止生成 HTML。
KVArray
的强大之处在于其专门的辅助方法。主要有两种方法:KeyVal
和 KVUnzip
。KeyVal
遍历 KVArray
并在每一步弹出一个键/值对,而 KVUnzip
创建两个新数组,一个包含键,另一个包含值。还有第三个方法,KVAppend
,它允许我们向现有的 KVArray
追加键和值,但实际上很少使用。本文将讨论 KeyVal
。您可以在 Sane 中的 lib.Collections.asp 中查看方法定义。KVUnzip
的用法在 Order Repository 类中进行了演示,该类使用键动态构建 SQL 以填充 where
子句,然后将查询和值数组传递给 Database 类(通过 DAL.query
),该类使用预处理语句处理查询。
KeyVal()
这是用于操作 KVArray
的主要方法。它在 For...Next
循环中使用,用于从 KVArray
中提取当前的键/值对。For...Next
循环每次必须以 Step 2
递增,这导致索引每次迭代递增 2。在循环中,调用 KeyVal
方法并传递 KVArray
、当前索引,以及两个 ByRef
参数 key
和 val
;它们将包含本次迭代的当前键和值。
Sub KeyVal(kv_array, key_idx, ByRef key, ByRef val)
if (key_idx + 1 > ubound(kv_array)) then err.raise 1, "KeyVal",
"expected key_idx < " & ubound(kv_array) - 1 & ", got: " & key_idx
key = kv_array(key_idx)
val = kv_array(key_idx + 1)
End Sub
一个简单的使用示例
dim keys
keys = Array("key_1", "val_1", "key_2", "val_2", "key_3", "val_3")
dim key, val, idx
For idx = 0 to UBound(keys) Step 2
KeyVal keys, idx, key, val
response.write key & " = " & val & "<br>"
Next
结果是以下输出
key_1 = val_1
key_2 = val_2
key_3 = val_3
正如您所看到的,当循环遍历数组的键/值对时,KeyVal
方法提取当前的键和值,并通过 ByRef
参数返回它们。现在让我们看一些这个“数据结构 + 循环模式 + 辅助方法”模式所释放强大功能的实际示例。
HTML Helper 类
HTML_Helper_Class
提供接受各种参数并生成 HTML 标签字符串的方法。该类遵循一个基本命名模式,将函数的基例与其“扩展”情况区分开来。例如,名为 LinkTo
的方法内部调用 public
方法 LinkToExt
,方法 LinkToIf
内部调用 public
方法 LinkToExtIf
,等等。此模式广泛用于表示接受额外参数(例如一个或多个 KVArray)的函数。基例(LinkTo
)处理大多数场景,而“扩展”情况(LinkToExt
)处理额外的场景。
让我们看看最简单的方法:LinkTo
和 LinkToExt
Public Function LinkTo(link_text, url)
LinkTo = LinkToExt(link_text, url, empty, empty)
End Function
Public Function LinkToExt(link_text, url, params, attribs)
LinkToExt = "<a href='" & url & UrlParams(params) & "' " & _
HtmlAttribs(attribs) & ">" & link_text & "</a>"
End Function
如您所见,LinkTo
方法内部调用 LinkToExt
方法并传递 VBScript 关键字 empty
来表示一个空参数。这使我们能够通过 IsEmpty
轻松检查参数是否存在。
KeyVal
方法用于 HTML 帮助器类的 private
方法,这些方法创建 HTML 属性字符串和 URL 参数字符串。HtmlAttribs
和 UrlParams
函数都接收一个 KVArray
,然后成对(step 2
)遍历数组,将数组和当前索引传递给一个从属函数(分别是 HtmlAttrib
和 UrlParam
),这些从属函数又调用 KeyVal
并返回构建的 HTML 属性 string
或 URL 参数 string
。
Private Function HtmlAttribs(attribs)
dim result : result = ""
if not IsEmpty(attribs) then
if IsArray(attribs) then
dim idx
for idx = lbound(attribs) to ubound(attribs) step 2
result = result & " " & HtmlAttrib(attribs, idx)
next
else ' assume string or string-like default value
result = attribs
end if
end if
HtmlAttribs = result
End Function
Private Function HtmlAttrib(attribs_array, key_idx)
dim key, val
KeyVal attribs_array, key_idx, key, val
HtmlAttrib = Encode(key) & "='" & Encode(val) & "'"
End Function
Private Function UrlParams(the_array)
dim result : result = ""
if not isempty(the_array) then
result = result & "?"
dim idx
for idx = lbound(the_array) to ubound(the_array) step 2
result = result & GetParam(the_array, idx)
'append & between parameters, but not on the last parameter
if not (idx = ubound(the_array) - 1) then result = result & "&"
next
end if
UrlParams = result
End Function
Private Function GetParam(params_array, key_idx)
dim key, val
KeyVal params_array, key_idx, key, val
GetParam = key & "=" & val
End Function
通过创建通用的 KeyVal
方法和上述 HtmlAttribs
方法,我们现在可以编写非常简单的 HTML 生成器函数。例如,以下方法使我们能够从代码中动态创建有序和无序 HTML 列表。方法 ListExt
根据输入数组(普通数组)或 记录集
生成列表。如果传递了 记录集
,则 list_text_field
是要提取并在列表中显示的 记录集
字段的名称。list_attribs
参数是一个 KVArray
,其中包含将转换为 parent
标签上 HTML 属性的键/值对。
辅助方法 UList
、UListExt
、OList
和 OListExt
封装了 ListExt
并简化了列表创建。
'If list is a recordset then list_text_field is required, otherwise list is assumed to be an
'array and each array element becomes the text field in the output list.
'list_attribs is a KVArray of HTML attributes for the parent list tag
Public Function ListExt(parent_tag_name, list, list_text_field, list_attribs)
dim item
dim s : s = Tag(parent_tag_name, list_attribs) & vbCR
Select Case typename(list)
Case "Recordset"
Do Until list.EOF
s = s & "<li>" & CStr(list(list_text_field)) & "</li>" & vbCR
list.MoveNext
Loop
Case "Variant()"
dim i
For i = 0 to ubound(list)
s = s & "<li>" & CStr(list(i)) & "</li>" & vbCR
Next
End Select
s = s & Tag_(parent_tag_name) & vbCR
ListExt = s
End Function
'shortcut for ListExt for unordered lists
Public Function UListExt(list, list_text_field, list_attribs)
UListExt = ListExt("ul", list, list_text_field, list_attribs)
End Function
'shortcut for UListExt() that eliminates the HTML ID for the ul element and only works for arrays
Public Function UList(list)
UList = UListExt(list, empty, empty)
End Function
'shortcut for ListExt for ordered lists
Public Function OListExt(list, list_text_field, list_attribs)
OListExt = ListExt("ol", list, list_text_field, list_attribs)
End Function
'shortcut for OListExt() that eliminates the HTML ID for the ol element and only works for arrays
Public Function OList(list)
OList = OListExt(list, empty, empty)
End Function
如果我们可以生成列表,为什么不动态生成下拉菜单呢?通过我们的新功能,这实际上非常容易。
'If list is a recordset then option_value_field and option_text_field are required.
'If list is an array the method assumes it is a KVArray and those parameters are ignored.
'Side Effect: If list is a recordset, the recordset is at EOF after function returns.
Public Function DropDownList(id, selected_value, list, option_value_field, option_text_field)
DropDownList = DropDownListExt(id, selected_value, list, option_value_field, option_text_field, empty)
End Function
Public Function DropDownListExt(id, selected_value, list, option_value_field, option_text_field, attribs)
If IsNull(selected_value) then
selected_value = ""
Else
selected_value = CStr(selected_value)
End If
dim item, options, opt_val, opt_txt
options = "<option value=''>" ' first value is "non-selected" blank state
select case typename(list)
case "Recordset"
do until list.EOF
If IsNull(list(option_value_field)) then
opt_val = ""
Else
opt_val = CStr(list(option_value_field))
End If
opt_txt = list(option_text_field)
If Not IsNull(opt_val) And Not IsEmpty(opt_val) then
options = options & "<option value='" & Encode(opt_val) & "' " & _
Choice((CStr(opt_val) = CStr(selected_value)), "selected='selected'", "") & _
">" & Encode(opt_txt) & "</option>" & vbCR
End If
list.MoveNext
loop
case "Variant()" 'assumes KVArray
dim i
for i = 0 to ubound(list) step 2
KeyVal list, i, opt_val, opt_txt
options = options & "<option value='" & Encode(opt_val) & "' " & _
Choice((opt_val = selected_value), "selected='selected'", "") & ">" _
& Encode(opt_txt) & "</option>" & vbCR
next
end select
DropDownListExt = "<select id='" & Encode(id) & "' name='" & Encode(id) & "' " & _
HtmlAttribs(attribs) & " >" & vbCR & options & "</select>" & vbCR
End Function
<p>
<%= HTML.Label("States from array, Hawaii selected, onchange event", "state_3") %>
<%= HTML.DropDownListExt("state_3", "HI", states, empty, empty, _
array("onchange", "showChangedValue(state_3)")) %>
</p>
注意:这里的名称 HTML
是在文件 lib.HTML.asp 中使用我在上一篇文章中讨论的单例命名模式定义的函数。这允许您包含一个包含类的文件,并将一个实例注入到全局范围,有效地模拟了其他语言的命名空间功能。例如,此代码位于文件 lib.HTML.asp 的末尾,并且每当包含该文件时都会执行
dim HTML_Class__Singleton
Function HTML()
If IsEmpty(HTML_Class__Singleton) then set HTML_Class__Singleton = new HTML_Class
set HTML = HTML_Class__Singleton
End Function
DropDownList
中的两个空参数对应于未使用的 recordset
参数。如果使用 recordset
,调用如下所示
<p>
<%= HTML.Label("Styled dropdown", "database_3") %>
<%= HTML.DropDownListExt("database_3", 1, rs, "database_id", "name",
aray("class", "styled_dropdown", "onchange", "showChangedValue(database_3)")) %>
</p>
在这种情况下,传递的列表是一个名为 rs
的 recordset
,为每个选项元素的 ID 提取的字段是 database_id
,为每个选项元素的文本提取的字段是 name
。
本文未涵盖此类的更多功能,包括将其用于领域存储库和数据库查询。查看上面链接的 Sane 项目以获取 KVArray
提供的表达能力的更多示例。这是 GitHub 项目中对 KVarray 的搜索,以查看更多示例。