65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (9投票s)

2014年3月31日

CPOL

8分钟阅读

viewsIcon

19107

downloadIcon

347

通过使用 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 写入输出流,而在其他情况下,我使用一个或另一个辅助函数来完成相同的目标。关键在于展示此方法固有的灵活性。

基本概念

我们将利用以下几点

  1. VBScript 的 With 语句创建一个新的执行范围(新的堆栈帧)。
  2. With 语句完成后,所有临时局部变量都会被销毁。
  3. VBScript 类具有在类销毁时调用的析构函数。

以上面的表单示例为例,基本步骤是

  1. 为我们要生成的 HTML 元素创建一个类。
  2. 创建一个写入开始标签的方法。
  3. 创建一个写入结束标签的类析构函数。

生成的类如下所示

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() 方法进行初始化,该方法接受两个参数:要生成的标签的名称,以及包含将写入开始标签的属性的 stringWriteToStream 属性将类的模式从 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 生成到动态数据存储库等各种可能性。下次见。

© . All rights reserved.