在经典 ASP 中实现 MVC “Using”模式以用于 HTML 帮助器






4.56/5 (9投票s)
通过使用 With 关键字、HTML 标签类和辅助方法,在经典 ASP 中实现 ASP.NET MVC 的 HTML 助手“使用模式”。
引言
ASP.NET MVC 有一种非常有用调用 HTML 助手的方法,称为“使用模式”。这种模式允许开发人员有效地构建易于编程和调用的可重用 HTML 组件。
在本文中,我们将讨论一种适应此模式的方法
<% using(Html.BeginForm(url)) %>
<% { %>
Username: <input type='text' name='name' id='name'>
<br>
Password: <input type='password' name='password' id='password'>
<br />
<input type="submit" value="Submit">
<% } %>
因为这个
<% With Form(url) %>
Username: <input type='text' name='name' id='name'>
<br>
Password: <input type='password' name='password' id='password'>
<br />
<input type="submit" value="Submit">
<% End With %>
这将使我们最终能够将代码结构化,以便尽可能地以“意图级别”进行编码,如所示
With NavBar
With NavItems
put NavItem("Pure", "<a href="https://purecss.npmjs.net.cn/">https://purecss.npmjs.net.cn</a>")
put NavItem("YUI Library", "<a href="http://yuilibrary.com/">http://yuilibrary.com</a>")
End With
End With
包含的演示显示了使用这些简单方法生成复杂布局的便捷性,如这些屏幕截图所示:(前面的摘录直接来自博客布局演示)
背景
在我之前的文章(经典 ASP 中的简化参数化查询类)之后,我想稍微绕道一下,讨论我在 ASP.NET MVC 中一直羡慕的一项功能。通过“使用模式”,可以编写 HTML 助手来封装 HTML,使其具有语义意义,同时允许调用者灵活地在生成的标签内添加任意 HTML。在上面的示例中,这允许调用者创建一个带有任意属性的表单标签,该标签在块执行完成后会自动关闭。
这种功能实际上非常容易在 VBScript 中实现,以便在经典 ASP 中使用。首先,我们将讨论需要大量重复的幼稚方法。其次,我们将考虑一种更简单、更灵活的方法,可以消除这种重复,然后我们将进一步扩展它。
在本文中,我故意提供了多种方法来完成相同的目标。在某些情况下,我直接将 HTML 写入输出流,而在其他情况下,我使用一个或另一个辅助函数来完成相同的目标。关键在于展示此方法固有的灵活性。
基本概念
我们将利用以下几点
- VBScript 的
With
语句创建一个新的执行范围(新的堆栈帧)。 - 当
With
语句完成后,所有临时局部变量都会被销毁。 - VBScript 类具有在类销毁时调用的析构函数。
以上面的表单示例为例,基本步骤是
- 为我们要生成的 HTML 元素创建一个类。
- 创建一个写入开始标签的方法。
- 创建一个写入结束标签的类析构函数。
生成的类如下所示
Class Form_Class
Public Sub Open(attribs)
response.write "<form " & attribs & ">"
End Sub
Private Sub Class_Terminate
response.write "</form>"
End Sub
End Class
如您所见,该类有一个名为 Open()
的方法,它接受一个包含任意标签属性的 string
并写入创建的标签,还有一个写入结束标签的析构函数。但是,内联使用此类将是繁琐的,并且与直接编写 HTML 相比没有优势(甚至可能不那么优雅)。因此,为了使其更易于使用,我们应该创建一个匹配的辅助方法来为我们管理类
Function Form(url)
dim T : set T = new Form_Class
T.Open "method='POST' action='" & url & "'"
set Form = T
End Function
此方法实例化类,使用预定义的属性 string
和传入的 URL 调用 Open()
方法,并返回生成的实例对象。这使得 With
语句能够正常工作。调用代码然后如下所示
<% With Form("someurl.asp") %>
... any HTML/ASP code here ...
<% End With %>
当遇到 With
语句时,系统会根据传入的对象创建一个新的范围。在这种情况下,传入的对象是 Form_Class
的实例,并且其 Open()
方法已被调用。当 With
语句超出范围时,Form_Class
实例将被销毁,并调用其析构函数,该函数输出结束标签。
生成的結果正是預期的
<form method='POST' action='someurl.asp'>
... any HTML/ASP code here ...
</form>
当然,应该指出的是,实际的 HTML 输出**不是**像这些示例中所示的那样格式良好。本文旨在演示概念,而不是提供现成的解决方案。
我们可以为任何我们想要的 HTML 生成类。但是,这会变得很麻烦且容易出错,因为存在大量代码重复,如创建简单表元素的這個示例所示
Class Table_Class
Public Sub Open
response.write "<table>"
End Sub
Private Sub Class_Terminate
response.write "</table>"
End Sub
End Class
Class Table_Row_Class
Public Sub Open
response.write "<tr>"
End Sub
Private Sub Class_Terminate
response.write "</tr>"
End Sub
End Class
Class Table_Header_Class
Public Sub Open
response.write "<th>"
End Sub
Private Sub Class_Terminate
response.write "</th>"
End Sub
End Class
Class Table_Cell_Class
Public Sub Open
response.write "<td>"
End Sub
Private Sub Class_Terminate
response.write "</td>"
End Sub
End Class
Function table()
set table = new Table_Class
table.Open
End Function
Function tr()
set tr = new Table_Row_Class
tr.Open
End Function
Function td()
set td = new Table_Cell_Class
td.Open
End Function
Function th()
set th = new Table_Header_Class
th.Open
End Function
如您所见,这在未来将变得非常麻烦。幸运的是,有一个更优雅的解决方案可以消除许多这种冗余。
更好的方法
通过创建一个通用的类来管理任何 HTML 标签,我们可以将问题简化为一个类和一小组辅助函数。在此过程中,我们可以创建一个类,该类可以创建并返回标签,或者将标签写入输出流。这给了我们更多的灵活性。
我们类本身非常简单。它通过一个 Init()
方法进行初始化,该方法接受两个参数:要生成的标签的名称,以及包含将写入开始标签的属性的 string
。WriteToStream
属性将类的模式从 string
生成更改为 string
输出。SelfClosing
属性用于确定如何关闭标签。Choice()
方法只是一个具有有意义名称的内联 if 函数。
Class HTML_Tag_Class
Private m_name
Private m_attribs
Private m_self_closing
Private m_write_to_stream
Public Sub Init(name, attribs)
m_name = name
m_attribs = attribs
End Sub
Public Property Let WriteToStream(bool)
m_write_to_stream = bool
End Property
Public Property Get OpenTag
dim s : s = "<" & m_name & Choice(Len(m_attribs) > 0, " " & m_attribs, "")
s = s & Choice(m_self_closing, "/>", ">")
OpenTag = s
End Property
Public Property Get CloseTag
CloseTag = Choice(m_self_closing, "", "</" & m_name & ">")
End Property
Public Property Let SelfClosing(bool)
m_self_closing = bool
End Property
Public Sub Open
If m_write_to_stream then response.write OpenTag & vbCR
End Sub
Public Sub Close
If m_write_to_stream then response.write CloseTag & vbCR
End Sub
Public Default Property Get ToString
ToString = OpenTag & CloseTag
End Property
Private Sub Class_Initialize
m_self_closing = false
m_write_to_stream = true
End Sub
Private Sub Class_Terminate
If m_write_to_stream then Close
End Sub
End Class
为了实际管理该类,我们考虑三个辅助函数:两个返回表示内联 HTML string
的对象,第三个返回自动生成和输出 HTML 容器 string
的对象。后者将用于 With
语句。
Function HTMLTag(name, attribs)
dim T : set T = new HTML_Tag_Class
T.Init name, attribs
T.WriteToStream = false
set HTMLTag = T
End Function
Function SelfClosingHTMLTag(name, attribs)
dim T : set T = new HTML_Tag_Class
T.Init name, attribs
T.WriteToStream = false
T.SelfClosing = true
set SelfClosingHTMLTag = T
End Function
Function HTMLContainer(name, attribs)
dim T : set T = new HTML_Tag_Class
T.Init name, attribs
T.WriteToStream = true
T.Open
set HTMLContainer= T
End Function
有了这些函数,我们现在可以为任何我们想要的标签定义方法。例如
Function div(class_name)
set div = HTMLContainer("div", "class='" & class_name & "'")
End Function
Function table(class_name)
set table = HTMLContainer("table", "cellpadding='0' cellspacing='0' border='0'
class='" & class_name & "'")
End Function
Function thead
set thead = HTMLContainer("thead", empty)
End Function
Function tbody
set tbody = HTMLContainer("tbody", empty)
End Function
Function tr
set tr = HTMLContainer("tr", empty)
End Function
Function th
set th = HTMLContainer("th", empty)
End Function
Function td
set td = HTMLContainer("td", empty)
End Function
Function link(text, url)
dim T : set T = HTMLTag("a", "href='" & url & "'")
link = T.OpenTag & text & T.CloseTag
End Function
然后可以这样调用这些函数
With div("first")
With table("pure-table")
With thead
With tr
With th
put "Head 1"
End With
With th
put "Head 2"
End With
End With '/tr
End With '/thead
With tbody
With tr
With td
put link("Google", "<a href="http://www.google.com/">http://www.google.com</a>")
End With
With td
put link("Microsoft", "<a href="http://www.microsoft.com/">http://www.microsoft.com</a>")
End With
End With '/tr
End With '/tbody
End With '/table
End With '/div
虽然阅读 With
语句可能会让人感到恼火,但它仍然比许多其他从代码生成 HTML 的方法更优雅,特别是如果我们足够细心地添加简短的注释以作澄清的话。
演示中的另一个例子
Sub StackedFormDemo
With StackedForm("", "post")
With Fieldset
With ControlGroup
put Label("Username", "name")
put TextBox("name")
End With
With ControlGroup
put Label("Password", "password")
put PasswordBox("password")
End With
With ControlGroup
put Label("Email Address", "email")
put TextBox("email")
End With
With FormControls
put Checkbox("cb")
put Label("I've read the terms and conditions", "cb")
put SubmitButton("Sign in")
End With
End With
End With
End Sub
在这里,函数 StackedForm()
创建了一个带有 Pure CSS 框架类别的表单,这些类别是创建视觉堆叠的表单所必需的,而其他函数则生成相应的框架 HTML。演示中还有一个相应的 AlignedForm()
方法。
当然,您可以按照自己喜欢的方式,使用您喜欢的任何参数来创建这些函数。可能性确实仅限于您的想象力——以及编写解决方案的意愿。
更进一步
但是,上面的方法与直接输出 HTML 相比并没有**那么**好。因此,我们可以将其向前推进一步,以开发真正有意义的组件。我们可以做两件事:创建提供简单 HTML 片段的有意义名称的组件,以及创建输出复杂 HTML 的组件。
非复杂组件
CSS 框架通常具有使用嵌套 div
标签的网格。使用的类名通常针对易于键入进行了优化,但这会牺牲一些含义。我们希望对我们作为开发人员来说更有意义的东西,但仍然输出符合框架的代码。
考虑以下使用 Pure CSS 框架的类:(选择它是因为它在此演示中足够易于使用,但您可以轻松地想象此功能适用于 Bootstrap 等)
Class Pure_CSS_Layout_Class
Public Function Layout()
set Layout = HTMLContainer("div", "id='layout'")
End Function
Public Function Main()
set Main = HTMLContainer("div", "id='main'")
End Function
Public Function Header()
set Header = HTMLContainer("div", "class='header'")
End Function
Public Function Content()
set Content = HTMLContainer("div", "class='content'")
End Function
Public Function Footer()
set Content = HTMLContainer("div", "class='footer'")
End Function
Public Function Row()
set Row = HTMLContainer("div", "class='pure-g'")
End Function
Public Function Col(size)
set Col = HTMLContainer("div", "class='pure-u-" & size & "'")
End Function
End Class
此类封装了 Pure 框架中的基本布局功能,并使用对我们来说更友好的术语进行访问。为了使其更加容易,让我们使用 Tolerable
库中的一个技巧
dim Pure_CSS_Layout_Class__Singleton
Function Pure()
If IsEmpty(Pure_CSS_Layout_Class__Singleton) then
set Pure_CSS_Layout_Class__Singleton = new Pure_CSS_Layout_Class
End If
set Pure = Pure_CSS_Layout_Class__Singleton
End Function
当我们包含文件 lib.Pure.asp 时,这会为我们提供一个名为 Pure
的全局对象,该对象永远不会被意外覆盖。(您可以覆盖其背后的单例,但那样您就是真的在自寻死路了!)
现在我们可以构建 CSS 网格如下:(Pure.Col("1-3")
部分创建了跨越网格三分之一的列)。
<% With Pure.Layout %>
<% With Pure.Main %>
<% With Pure.Header %>
<h1>Hello World</h1>
<% End With %>
<% With Pure.Content %>
<% With Pure.Row %>
<% With Pure.Col("1-3") %>
<p>Arbitrary HTML</p>
<% End With %>
<% With Pure.Col("1-3") %>
<p>Arbitrary HTML</p>
<% End With %>
<% With Pure.Col("1-3") %>
<p>Arbitrary HTML</p>
<% End With %>
<% End With %>
<% End With %>
<% End With %>
<% End With %>
人们可以轻松地想象创建一个 Grid_Class
,它提供 Grid.Row()
和 Grid.Col()
方法,并将任何参数转换为特定 CSS 框架的正确 div
类语法。然后,这将使开发人员能够以最少的/不更改 ASP 代码来替换 CSS 网格。
更复杂的组件
通过让类输出原始 HTML 或原始 HTML 和生成 HTML 的组合,可以生成更复杂的 HTML。以下是我们都曾使用过的组件的示例:Tile
。这只是一个包含两个部分(标题和正文)的框。
Class HTML_Tile_Class
Private m_title
Public Sub Init(title)
m_title = title
End Sub
Public Sub Open
response.write "<div class='tile'>"
response.write "<div class='tile-hdr'>"
With div("tile-title")
response.write m_title
End With
response.write "</div>"
response.write "<div class='tile-body'>"
End Sub
Private Sub Class_Terminate
response.write "</div>"
response.write "</div>"
End Sub
End Class
Function Tile(title)
dim T : set T = new HTML_Tile_Class
T.Init title
T.Open
set Tile = T
End Function
Tile
生成如下
<% With Tile("This is a tile") %>
<p>This is some arbitrary tile content</p>
<% End With %>
输出是更复杂的组件
<div class='tile'>
<div class='tile-hdr'>This is a tile</div>
<div class='tile-body'>
<p>This is some arbitrary tile content</p>
</div>
</div>
可能性确实是无穷无尽的。事实上,在附带的代码中有一个演示,它使用包含的 HTML_Tag_Class
、辅助方法和名为 Blog_Layout_Class
的自定义类,重新创建了 Pure 框架网站上找到的 Blog
布局演示。以下是该类的一个摘录,它展示了我们如何使用这些方法开始以意图级别进行编码。调用此方法时,会生成博客侧边栏。
Public Sub Sidebar
With div("sidebar pure-u-1 pure-u-med-1-4")
With Header
With HeaderGroup
put "<h1 class='brand-title'>A Sample Blog</h1>"
put "<h2 class='brand-tagline'>Creating a blog layout using Pure</h2>"
End With
With NavBar
With NavItems
put NavItem("Pure", "<a href="https://purecss.npmjs.net.cn/">https://purecss.npmjs.net.cn</a>")
put NavItem("YUI Library", "<a href="http://yuilibrary.com/">http://yuilibrary.com</a>")
End With
End With
End With
End With
End Sub
摘要
正如我们所见,在“老派”的经典 ASP 中模拟 MVC 中的“使用模式”是绝对可能的。除了许多年在线教程教授的糟糕编码实践外,没有什么能阻止我们以这种方式构建代码。通过后退一步,重新评估框架和语言,并通过采用更好的编码实践,我们可以释放这个所谓的过时语言的巨大力量。
我的下一篇文章将重点介绍一种优化的数据结构,该结构旨在允许任何方法接受可选参数。这实际上释放了我们老朋友 VBScript 的大量表达能力。事实上,它开启了从强大的 HTML 生成到动态数据存储库等各种可能性。下次见。