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

互联网编程入门 (ASP.NET 概述和历史)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (15投票s)

2004年11月14日

39分钟阅读

viewsIcon

147442

对互联网技术进行总体概述,包括互联网本身、HTML 和 XML 是什么,使用 Web Forms,CGI/MIME,IIS ISAPI,ASP 以及通过 ODBC32、OLE DB、ADO 和 ASP.NET 创建数据库的前端 HTML。

引言

本文档旨在作为互联网/企业技术的总体概述,帮助新进入该领域的人员快速掌握。本文档的内容安排如下:

本文档最初由我本人于 1997 年研究和撰写。然而,它在今天(约 2005 年)仍然非常相关。我将它免费发布给您,以便今天的初学者 ASP.NET 开发人员能够欣赏和理解 ASP.NET 和数据库连接技术是如何产生的以及它们多年来是如何发展的。

我希望您能从中获得足够的信息,以便开始开发企业级互联网应用程序……

欢迎评论和建议发送至: kevleyski@hotmail.com

Kevin Staunton-Lambert BSCS

互联网和 TCP/IP 快速概述

“互联网”这个名称是授予一个项目和原型系统的,该项目和原型系统最初由美国高级研究计划局 (ARPA) 开发,以研究解决不兼容计算机网络之间通信问题的各种方法。

通过这个项目,开发了两个基础软件标准:

  • 传输控制协议 (TCP),它确保网络上传输的所有数据都能按预期到达。
  • 互联网协议 (IP),它规定了任何计算机系统在互联网上与另一系统通信时必须讲的“语言”。

这些软件标准通常被称为“TCP/IP”(Tea Sea Pea Eye Pea)。然而,更精确的称谓是“TCP/IP 协议套件”,因为该软件还包括其他协议,例如用户数据报协议 (UDP),它用于不要求错误检查的短数据包,如实时视频和音频。

任何连接到互联网的机器都有一个唯一的互联网 (IP) 地址。IP 地址是一个四字节代码(32 位代码,有潜力支持超过 40 亿台机器),它被分配的方式使得属于同一网络的机器具有相同的网络前缀。(这类似于电话号码按地区分组,但分配 IP 地址时,地区无关紧要。)

目前正在开发一个改进的 IP 地址系统,称为 IPng(IP 下一代),它将使用八个字节(128 位)来支持 3x10^38 台机器,这足以有可能控制地球上任何房屋的任何电灯开关,而无需担心地址用完。(我预计如果这项技术真的实现,我们会看到一些有趣的计算机病毒!)

人类通常不擅长记忆 IP 地址,因此它们通常会被赋予一个名称,通常称为主机名、统一资源定位符 (URL) 或统一资源标识符 (URI)。一个这样的名称示例是 https://codeproject.org.cn。URL 和 IP 地址都保存在域名系统 (DNS) 中,DNS 本质上是一系列称为“域服务器”的计算机,它们在互联网上支持这个不断增长的数据库。

万维网 (WWW)

万维网 (WWW) 最初由 Tim Berners-Lee 和 CERN 实验室(位于日内瓦,靠近法国边境)的其他科学家开发,目的是让粒子物理学家能够共享世界各地的信息。

如今,“Web”(通常的说法)被全球数百万用户用来在互联网上传递和组织超媒体(文本/图形/声音等)。估算 Web 的大小几乎是不可能的任务。1996 年 5 月 25 日,“Internet Solutions”估计有 59,628,024 人访问 304,177 个站点;到 1998 年,这些数字可能已翻倍。

Web 的成功是通过创建超媒体文档标准来实现的:

  • 超文本传输协议 (HTTP),所有 Web “浏览器”都用它来与 Web “服务器”通信(有时也称为超文本传输协议)。

  • 超文本标记语言 (HTML),这是 Web 页面作者必须使用的“语言”,用于格式化超媒体文档。(正是这种语言的规范,常常在一些软件生产者之间引发重大争议。)

标记语言

HTML (超文本标记语言)

HTML 是超文本传输协议 (HTTP) 支持的默认脚本语言,用于格式化 Web 文档(格式化标签)和超链接(锚定标签)到 Web 服务器上保存的其他 Web 文档和脚本。

HTML 的语法改编自 SGML(简单通用标记语言或标准通用标记语言 ISO 8878:1986),非常容易学习,并且由于其文件大小小,非常适合在 Web 上使用。(比较一下格式化的 HTML 文档与使用文字处理软件编写的格式化文档的大小。)HTML 文档通常以 ASCII(美国信息交换标准代码)格式保存,这是大多数软件/硬件平台之间的标准。通过在与号 (&) 字符后跟分号 (;) 使用其 HTML 缩写,也可以将国际编码字符(Unicode)添加到文档中。(例如,与号字符的符号标记为 &。)

尽管有大量关于 HTML 的资料,但我们需要查看一些基本的 HTML 结构,因为我们将在接下来的部分中不断遇到它们。

HTML 文档结构和标签

HTML 文档始终具有基本结构,由头部(包含文档标题以及其他元数据,如作者姓名和日期)和正文组成。文档本质上是纯文本,带有格式化标签,这与早期文字处理器(如 Word Perfect for DOS)使用的原则非常相似。HTML 标签定义在小于号 (<) 和大于号 (>) 符号之间,并且总是成对出现*,即一个节的开始标签(例如,<large> 用于放大文本)和一个节的结束标签(例如,</large> 用于恢复正常)。

* 像 Microsoft Internet Explorer 这样的浏览器允许使用懒惰 HTML 和严格 HTML。这允许您省略一些看起来很明显的标签(例如,表格中结束一行的标签 (</tr>))。然而,坚持使用万维网联盟 (W3C)定义的官方严格语法非常重要,这样我们才能在整个 Web 中保持软件的独立性。

以下 HTML 脚本演示了一些格式化标签。

注意缩进的使用,以识别开始标签和结束标签之间的受影响部分;这是有效的 HTML,因为制表符和大于一个字符长度的空格会被忽略。(要强制双倍空格,您需要使用 HTML 缩写 )。

HTML 脚本 详细说明
注释
<!-- 
Comments
-->

标签 <!----> 之间的任何文本都被指定为不属于 HTML 内容。

请勿在注释部分使用连字符 (-) 或“大于号”(>),因为 HTML 浏览器可能会将其误解为注释的结束。

文档版本信息
<!doctype HTML PUBLIC 
       "-//W3C//DTD HTML 3.2//EN"> 
<html>

<!doctype> 指定解释此文档应使用哪个版本的 HTML。在此,我们指定了 万维网联盟 (W3C) 定义的 3.2 版本。

<html> 指定 HTML 文档的开始。

标题

文档头部

<head>
 <title>
   Document Title
 </title>

<head> 表示文档头部部分的开始。

<title> 指定文档的引用名称(用于搜索引擎索引和书签/收藏夹)。

标题

头部元数据

 <meta HTTP-EQUIV="Content-Type"
  CONTENT="text/html; 
  charset=Windows-1251">
 <meta HTTP-EQUIV="Keywords"
  CONTENT="HTML,Tables,Forms">
</head>

<meta> 元数据(隐藏数据)描述了附加信息,例如显示此文档应使用的字符集。

其他元数据包括文档描述、作者、关键字、自动文档刷新/重定向、发布/过期日期等……

</head> 标记文档头部部分的结束。

正文
<body bgcolor=#ffffff
 text=#000000
 link=#0000ff vlink=#909090>
<body> 文档正文部分的开始。背景/文本/超链接和已访问超链接的颜色在此处使用十六进制 RGB (#rrggbb) 表示法定义(#ffffff 表示白色背景)。
表格

表格头部

x z
<table>
 <tr>
  <th>
    Tag
  </th>
  <th align=left>
    Example
  </th>
</tr>

<table> 指定以下行将放入表格中。

<tr> 向表格添加一行

<th> 添加一个名为“Tag”(表头)的列

添加另一个名为“Example”的列,该列的 align 属性设置为 left,而不是默认的居中对齐。

</tr> 指定一行的结束。* 如上所述,某些支持表格的浏览器不需要此标签,但我已将其包含以示完整性。

表格

表格行

1 2 3
4 5 6
7 8 9
<TABLE cellSpacing=0 border=1>
 <TBODY>
  <TR>
   <TD>1</TD>
   <TD>2</TD>
   <TD>3</TD>
  </TR>
  <TR>
   <TD>4</TD>
   <TD>5</TD>
   <TD>6</TD>
  </TR>
  <TR>
   <TD>7</TD>
   <TD>8</TD>
   <TD>9</TD>
  </TR>
 </TBODY>
</TABLE>

添加另一行

<td> 在表格中创建一个单元格(第一列“Tag”),右对齐此单元格中的文本。

标签 <h1> 指定以下代码应按照 HTML 浏览器预设的标题 1 格式进行格式化。

</h1> 标记标题行的结束

</table> 表示表格的结束

换行符
<hr size=3>
<br>
<hr> 放置一个水平规则(线段)

<br> 表示标准的回车换行符

列表
  • 项目符号
  • 无序
  • 列表
  1. 数字
  2. 有序
  3. 列表
<ul>
 <li>Bulleted</li>
 <li>un-ordered</li>
 <li>list</li>
</ul>
这些行演示了一个 HTML 无序项目符号列表。<ul>

列表项(<li></li>)的数量没有限制

通过使用列表标签 <ol></ol> 而不是 <ul></ul>,可以获得自动编号列表(有序列表)。

(valign=top 可防止文本“Lists: ”出现在单元格的中间位置。)

字体

Arial

Roman

Courier

<font color=green 
     face='courier' size=6>
Green
</font>
这些行演示了使用 <font> 标签定义的用户自定义字体。(注意:使用美式拼写。)

此页面底部的示例还演示了如何使用 RGB 十六进制颜色代码 (#RRGGBB) 来指定非标准颜色。

超链接

书签

文档

电子邮件

<a href='http://...#ODBC'></a>
<a 
  href='mailto:
  kevleyski@hotmail.com'>
</a>
<a> 第一个超链接(锚点)将当前文档跳转到指定的 URL(统一资源定位符),第二个生成一封发送给我的新电子邮件。

这两个锚点的 href 部分可以分解为几部分以便理解:

  1. 协议,例如:http:(超文本传输)、mailto:(电子邮件)、ftp:(文件传输)、telnet:(远程登录)、file:(本地文件)
  2. 域名(对于 mailto: name@domain
  3. (第一个示例)文档中的锚点名称(书签),用于跳转到哈希符号(#)之后指定的标记。

Java Applet

<applet id=Object1 codeBase= /examples/  JavaExample/ height=110 width=110 align=baseline code= JavaText.class name=" Object1"> <PARAM VALUE="2910" NAME="_cx"> <PARAM VALUE="2910" NAME="_cy"> 动画文本的简单 Java Applet 示例</applet>

<applet code="JavaText.class" 
    codebase="/examples/JavaText" 
    width="110" height="110">
  <param name="text" value="Java">
</applet>
<applet> 在网页中包含 Java applet(.class 文件)。

<param> 设置要发送到 Java applet 的参数。

ActiveX 控件

<object id=Birthday height=110 width=110 align=baseline border=0 classid=" clsid:8E27C92B-1264-101C-8A2F-040224009C02"> <PARAM VALUE="28" NAME="Day"> <PARAM VALUE="05" NAME="Month"> </object>

<object id="Birthday"
classid="clsid:8E27C92B-
1264-101C-8A2F-040224009C02"
width="110" height="110")
  <param name="Day" value="28">
  <param name="Month" value="5">
</object>
<object> 包含 ActiveX 控件。

(此示例使用了 Microsoft 日历 ActiveX 控件,其国际 class id 为 8E27C92B-1264-101C- 8A2F-040224009C02)

与 Java Applet 类似,参数特定于控件,此处我们定义日期为 5 月 28 日。

层叠样式表 (CSS)

样式 1

样式 2

重塑标题 (h1)

<style>
  .style1 
   {font: bold 28pt Times,serif;}
  .style2 
   {font: italic 14pt Times,serif;}
  h1 
   {font: 8pt Arial; color: blue}
</style>
<p class='style1'>Style 1</p>
<p class='style2'>Style 2</p>
<h1>Re-styled header (h1)</h1>
<style> 样式的使用方式与我们在文字处理器中使用样式来设置各种文本属性(例如标题)的方式相同。

用户定义的样式通过选择一个名称并加上句点 (.) 来设置,预设样式(例如 <h1> / <p> / <cite> / <strong> / <small>)可以通过使用该样式的给定名称来重新定义。

文档页脚
 </body>
</html>
指定正文部分和 HTML 文档的结束

XML(可扩展标记语言)

到目前为止,我们已经研究了 HTML 的语法(语法)。然而,像所有语言一样,我们也应该考虑我们所呈现信息的语义(含义)。

例如,如果文本以粗体标记,可能是因为它更重要,或者只是作者希望它看起来那样。同样,我们可能会使用彩色字体来表示标题,或者再次可能是个人偏好,使页面看起来更具吸引力。

本质上,这个论点的重点是,如果希望文本因其重要性而显示为粗体,那么它应该用逻辑标签<strong>来标记,而不是用物理标签<b>。同样,如果一个句子是标题,那么我们应该考虑使用逻辑标签,如<h1>,而不是通过物理标签(如<font size=6>)来强制应用样式。

如上所述,我们可以覆盖现有的样式并定义我们自己的样式,可以使用层叠样式表。然而,这可能会显得笨拙,例如,不得不重复标签,如<p class='Style1'>,并且因此,对于读者来说,理解起来比根本不使用它要困难。

为了解决这个问题,XML 与 HTML 不同,它不使用预设标签,因此页面的格式完全取决于作者。还引入了嵌套元数据标签,即关于信息的信息。例如,假设我们有一段与此段落相关的信息。

我们可以使用 HTML 如下物理标记它:

<b>Classification number</b> 1:02:01:13:00                 <br> 
<b>Thesaurus entry</b>       XML                           <br> 
<b>Article</b>               Extensible Markup Language    <br> 
<b>URL</b>                   /dissertation/week2/HTML.html <br> 
<b>Bookmark</b>              XML

在 XML 中,我们将考虑信息的语义,例如:

<Article> 
         <Classification>
                         <Level1>                      1             </Level1> 
                         <Level2>                      02            </Level2> 
                         <Level3>                      01            </Level3> 
                         <Level4>                      13            </Level4> 
                         <Level5>                      00            </Level5> 
        </Classification> 
        <Thesaurus>      XML                           </Thesaurus> 
        <Article>        Extensible Markup Language    </Article> 
        <URL>            /dissertation/week2/HTML.html </URL> 
        <Bookmark>       XML                           </Bookmark> 
</Article>

作者随后可以自行编写 XSL(扩展样式语言)样式表(参见 www.w3.org/Style/),该样式表将正确地将页面上标记为“Article”的任何部分格式化回原始所需的 HTML 格式。

Web 表单

表单用于将用户输入从 HTML 文档传递到 Web 服务器。您应该已经相当熟悉表单控件的行为,因为它们广泛用于图形用户界面 (GUI),如 MS Windows、MacOS 和 X-Windows。(Microsoft ActiveX 控件也可以包含在表单中,以提供额外的输入类型(例如,日期格式)和用户输入样式。)

为了更好地理解 Web 服务器如何实际处理表单,请将表单的 action 设置为调用稍后描述的 ISAPI 示例。(尝试使用 method= "POST" 以及默认的“GET”来查看 CGI 查询字符串使用方式的差异。)

HTML 脚本 详细说明
表单处理程序规范
<form 
  action="/examples/ CGIExample/CGIExample.exe">
<form>。此标签指定来自以下输入控件(直到 </form>)的参数将作为参数(通过 action)传递给程序 *ISAPIExample.dll*。(稍后介绍。)
文本输入框

<input id=" Text1" value= "Text Box" name= textbox>

<input type="text" size="20" 
       name="textbox" value="Text Box">
text。此文本输入框用于生成名为 textbox 的参数。参数的初始 value 设置为 Text Box,输入框的 size 为 20 个字符宽。
密码输入框

<input id=Password1 type= password value= Password name= password>

<input type="password" 
  size="20" name="password" value="Password">
password。密码框的实现方式与文本框相同。本质上,区别在于输入到文本框中的任何字符都会显示为星号(*)。
多行(滚动)输入框

<textarea id=Textarea1 name=areabox rows=3> 多行输入框(文本区域)示例 </textarea>

<textarea name="areabox" rows="3" cols="20">
 Example of a multiple line input box (Text Area)
</textarea>
<textarea>。这个多行输入框(或文本区域)生成一个名为 areabox 的参数。

初始值由开始标签和结束标签之间的文本设置,大小由 数(3)和 数(20)表示。

复选框

<input id=" Checkbox1" type= checkbox value=chosen name=check1> 1 <input id=Checkbox2 type= checkbox value= chosen name=check2> 2 <input id= Checkbox3 type= checkbox CHECKED value= chosen name=check3> 3

<input type="checkbox" 
  name="check1" value="chosen">1
<input type="checkbox" 
  name="check2" value="chosen">2
<input type="checkbox" 
  checked name="check3" value="chosen">3
checkbox。这三个复选框定义了 3 个独立的参数,分别名为 check1check2check3

如果复选框被选中,其 value chosen 就是参数的值;如果复选框被选中,则整个参数根本传递给 Web 服务器。

请注意,第三个复选框被指定为初始 checked

选项(单选按钮)

<input id=" Radio1" type=radio CHECKED value=1 name= optiongroup> 1 <input id=" Radio2" type=radio value=2 name= optiongroup> 2 <input id="Radio3" type=radio value=3 name= optiongroup> 3

<input type="radio" 
 checked name="optiongroup" value="1">1
<input type="radio" 
 name="optiongroup" value="2">2
<input type="radio" 
 name="optiongroup" value="3">3
radio。与复选框不同,此单选按钮控件只定义 1 个名为 optiongroup 的参数。此参数的 value 取决于所选的选项,即 1、2 或 3。

请注意,选项 1 是初始选择的选项(checked)。

选项(列表)

<select id=Select1 size=1 name =combobox> <option value=1> Option 1</option> <option value=2> Option 2</option> <option value=3 selected> Option 3</option> <option value=4> Option 4</option> </select>

<select name="combobox" size="1">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option selected value="3">
       Option 3</option>
<option value="4">Option 4</option>
</select>
<select> 此控件定义了一个名为 combobox 的单一参数。其 value 取决于从下拉列表中选择的预设选项。在此示例中,如果用户从列表中选择“Option 2”,则参数 value 为 2。

(注意:如果我们没有在 <option> 标签中指定 value,则值将是字符串“Option 2”)

<select> 标签的 size 参数决定选项是显示为组合框还是列表框,即 1 = 组合框,>1 = 该大小的列表框。

此处初始选项 selected 设置为“Option 3”。

多选(列表)

<select id=Select2 multiple size=4 name= listbox> <option value=1 selected> Option 1</option> <option value=2> Option 2</option> <option value=3> Option 3</option> <option value=4 selected> Option 4</option> </select>

<select name="listbox" multiple size="4">
<option selected value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option selected value="4">Option 4</option>
</select>
multiple 当一个 <select> 控件被指定为 multiple 时,会生成多个参数,所有参数都名为 listbox,具体数量取决于所选的选项。

(注意:一个设置为多选的下拉组合框(即 size=1)会自动变成列表框)

要进行多选,请在按住 Ctrl 键的同时单击鼠标。(或按住 Shift 键选择从先前选定选项开始的一个组。)

按钮

<input id=" Submit1" type=submit value="Submit 1" name=pressed>

<input id=" Submit2" type=submit value="Submit 2" name=pressed>

<input id=" Image1" type=image height=40 width=125 src= "button.gif" align=" bottom" value="Submit 3" border=0 name= pressed>

<input id=Reset1 type=reset value=Reset name=Reset1>

<input type="submit" 
  name="pressed" value="Submit 1">
<input type="submit" 
  name="pressed" value="Submit 2">
<input type="reset">
<input type="image" name="pressed" 
    src="/dissertation/week3/button.gif">
submit 此按钮类型会触发表单的操作,即它将参数传递给 <form> 标签中定义的程序。

此示例演示了如何生成一个附加参数(名为 pressed),以区分是哪个按钮用于提交表单。但是,如果我们只需要一个提交按钮,我们可以使用此语句的简写形式,<input type= "submit">;这还有助于在用户按回车键时提交表单。

image 图像输入类型会提交表单(如上),但按钮面包含图像源而不是值。当用户单击图像时,将传递两个参数,pressed.xpressed.y,以及它们各自的“单击位置”值,而不是像上面的提交按钮那样使用预设的 value

reset 此按钮类型通过将表单控件重置到其各自的初始状态来撤销用户对表单所做的任何更改。

隐藏
<input type="hidden" name="somedata" 
          value="UsefulDataYourAppProcesses">
hidden。使用此项来发送隐藏数据,即不作为表单本身一部分可见的数据,而是用于表单处理,例如识别表单的引用者。有关此项如何常用的示例,请参见本文档的ASP.NET数据绑定部分。
关闭表单块
</form>
</form>。表示表单结束。

CGI:通用网关接口

通用网关接口 (Common Gateway Interface) 是一种创建可执行程序(CGI 脚本)的规范,这些程序可以由 Web 服务器运行以执行动态任务,例如:

  • 安全(例如,用户授权/IP 识别/软件识别)
  • 生成动态 HTML(例如,网站访问计数器、广告(使用元数据刷新标签)、报告日期/时间等)
  • 查询和更新数据库(例如,搜索引擎、报表)。

使用支持标准输出到控制台的语言(如 C/C++、PASCAL、Visual Basic 和Perl(实用提取和报告语言))可以非常轻松地创建 CGI 脚本。Web 服务器通过将程序的输出直接传递给调用它的 Web 浏览器来处理程序,而不是更新用户的控制台(屏幕/客户端窗口)。

Web 服务器通常通过环境变量将数据传递给 CGI 程序(脚本)。CGI 程序可以使用获取操作系统环境变量值相同的方法来查看环境变量(例如,MS-DOS %PATH%)。

当环境变量传递给 CGI 脚本时,它们通常通过两种方法之一发送:GET 和 POST。它们之间的区别在于,使用 GET 方法(默认方法)传递的数据被脚本作为命令变量读取(例如,通过 argcargv[] 读取),而 POST 数据通过读取标准输入进行处理(例如,通过 stdin >> 读取)。

在我们深入研究CGI 脚本之前,我们需要了解一些关于最有用的环境变量的信息,即查询字符串以及MIME(多用途互联网邮件扩展)的使用……

CGI:查询字符串

查询字符串是 Web 浏览器通过URL传递给 CGI 脚本的信息。如果您使用过 Yahoo 等互联网搜索引擎,您可能注意到浏览器地址栏中出现了奇怪的字符(例如,?、&、% 、+),例如:

如果您启动 Yahoo(http://www.yahoo.co.uk/)并搜索“使用 C++ 编写 CGI 脚本”,浏览器将传递以下 URL:

http://search.yahoo.co.uk/search/ukie?p=Writing+CGI+Scripts+using+C%2B%2B&y=y.

此 URL 的作用是触发(调用/执行)位于 http://search.yahoo.co.uk/search 的程序 *ukie*,并将数据 *p=Writing+CGI+Scripts+using+C%2B%2B&y=y* 作为查询字符串传递给该程序。(注意:第一个问号 (?) 不包含在查询字符串中。)

这些附加数据可以分解为两个参数,它们由和号符号 '&' 分隔,它们是:

p=Writing+CGI+Scripts+using+C%2B%2B

第一个参数“p等于原始搜索规范“Writing CGI Scripts using C++”。但是,由于 URL 不支持空格字符,HTML 表单会将空格转换为 + 号;因为 + 号表示空格,所以两个 + 号(在 C++ 中)被转换为 HTML 格式 %2B(+ 号的十六进制 ASCII 等效值)。(注意:从 URL 而不是 HTML 表单传递给 CGI 脚本的数据以 %20(ASCII 空格字符)而不是 + 号传递,为什么?)

y=y

第二个参数“y等于搜索空间选项标志,该标志是通过使用单选按钮控件设置的,用于选择“所有站点”(y=y)或“仅限英国和爱尔兰站点”(y=u)。

CGI:HTTP MIME 头部(多用途互联网邮件扩展)

对于能够处理纯文本、HTML 格式文本和图形图像等多种信息类型的软件,如 Web 浏览器和电子邮件,我们需要包含一些附加信息,指示我们希望如何处理内容。到目前为止,我们还没有被要求包含此信息,因为我们假定扩展名为 *'.HTM'* 或 *'.HTML'* 的文件传递给 Web 浏览器的内容自然会被处理为 HTML 文档。然而,CGI 程序被服务器作为原始数据而不是文档传递,因此为了让 Web 浏览器知道如何处理来自 CGI 脚本的数据,我们需要传递一个额外的消息,称为 MIME 头部。

在我们的示例中,我们将在 Web 服务器和浏览器(客户端)之间传递 HTML 格式的文本。这需要以下纯文本 MIME 头部:

Content-Type: text/html <carriage return>
<carriage return>

没有此信息,Web 浏览器会将我们的 CGI 脚本的任何输出解释为纯文本,并会忽略数据或将格式化标签视为普通文本。MIME 头部必须是传递给浏览器的任何信息的首行,并且它们必须单独一行并后跟一个空行。(因此有两个回车符。)有关 MIME 头部信息的更多内容,您应该参考HTTP/1.1 规范

CGI 示例

CGI “脚本”可以使用各种编程语言编写(请参阅后面的PERL示例),但为了与本文档中的代码兼容,我们将使用 C++ 进行脚本编写。

CGI C++ 程序非常容易创建,多用户文件共享和通信等技术完全由 Web 服务器处理,因此它们类似于传统的 C++ 控制台程序(即简单的 DOS 或 UNIX 程序)。

以下 C++ 代码片段只是将 Web 服务器传递的环境变量 QUERY_STRING 传回给调用它的 Web 浏览器(客户端)……

// Include the standard C++ classes and Input/Output Stream classes

#include <stdlib.h> 

#include <iostream.h>


void main()
{
    char *EnvVar = getenv("QUERY_STRING");
    // Standard DOS/UNIX environment variable command

    if (EnvVar == NULL) 
        EnvVar = "No+Parameters+Passed";
        // If there is no query string

        // set the parameter to No Parameter Passed

        
    cout << "Content-Type: text/html\n\n";
    // Write HTTP MIME Header

    cout << "<html>";
    // Write HTML start tag

    cout << "Query String: " << EnvVar;
    // Place the query_string parameter

    // to the console output stream (cout)

    cout << "</html>";
    // Write HTML end tag

}

当我们直接从 DOS(或 UNIX)运行此程序时,该程序将预期的纯文本文档输出回控制台,但是当同一个程序通过 Web 服务器运行时,纯文本代码被视为 HTML 代码,并且环境变量 QUERY_STRING 被作为 HTML 文档传回 Web 浏览器。

IIS/ISAPI

Internet 服务器应用程序编程接口 (Internet Server Application Programming Interface) 可以被比作 CGI 的高级形式。CGI 的工作原理是在客户端(Web 浏览器)请求时在服务器上执行程序。

这种原理有一个主要缺陷,因为每次调用 CGI 程序都需要其自身的*独立*程序实例,因此需要服务器上的独立内存空间。因此,如果 50 个客户端都在访问(命中)服务器,则需要 50 个独立的 CGI 程序实例,并且每个 CGI 环境变量都需要传递给每个分配的内存空间。这对服务器资源来说是一个沉重的负担,也是一种低效的利用。

然而,ISAPI 程序基于动态链接库 (DLL) 的原理,这些 DLL 在实例之间*共享*。不过,其缺点是编程变得更加复杂,因为我们需要在应用程序中实现多线程。然而,在我们的示例中,我们将使用 Microsoft Foundation Classes,它隐藏了两个类中的多线程工作:CHttpServerCHttpServerContext

出于我们的目的,我们将编写官方称为 ISAPI 扩展的 CGI 类型应用程序,但是这项技术还有另一个方面称为 ISAPI 过滤器,用于拦截信息在 Web 服务器上传递的过程。这允许我们执行诸如使用日志记录、用户身份验证和安全等任务。

NSAPI(Netscape 服务器应用程序编程接口)顾名思义,是 Netscape Web 服务器的编程接口。然而,Netscape 打算更改其服务器架构以使用 ISAPI。(Microsoft 的 ActiveX 技术。)

(ISAPI 扩展 DLL 与执行 CGI 脚本的方式完全相同,即作为超链接或从表单操作。)

ISAPI 扩展

在与 ISAPI 相关的七个 MFC 类中,我们将使用两个特定的 MFC(Microsoft Foundation)类来创建 ISAPI 扩展 DLL。它们是:

  • CHttpServer - 所有 ISAPI 扩展的基类。此类基本上处理我们 DLL 与 Web 服务器之间的连接。
  • CHttpServerContext - 我们将数据传递到其中以创建虚拟动态 Web 页面的类对象。(类似于使用 cout << 从 CGI 传递数据。)

在使用 CGI C++ 脚本时,我们需要手动处理 QUERY_STRING 环境变量以确定传递给脚本的参数;在使用 ISAPI C++ 编程时,我们可以使用一种更简单的方法来完成此任务,称为解析映射……

ISAPI 解析映射(使用 MFC)

解析映射 (Parse Map) 是一个 MFC(Microsoft Foundation Class)宏,用于将函数绑定(映射)到传递给我们的 DLL 的查询字符串中指定的参数。描述解析映射最简单的方法是通过演示。

解析映射在 CISAPIExampleExtension 类中声明,该类派生自 CHttpServer。例如……

// ISAPIExample.h

// Define the class (which inherits CHttpServer),

// declaring the PARSE_MAP and our functions

class CISAPIExampleExtension : public CHttpServer
{
public:
        // Declare PARSE_MAP

        DECLARE_PARSE_MAP()

        // Prototypes for our functions

        void Example1(CHttpServerContext* pCtxt);
        void Example2(CHttpServerContext* pCtxt, LPCTSTR Param1, 
             LPCTSTR Param2, LPCTSTR Param3, LPCTSTR Param4, 
             LPCTSTR Param5, LPCTSTR Param6, LPCTSTR Param7, 
             LPCTSTR Param8, LPCTSTR Param9, LPCTSTR Param10);
};

// Parse Map Definition, Used to process

// parameters specified by in the Query String

BEGIN_PARSE_MAP(CISAPIExampleExtension, CHttpServer)

// Handle Example1 that takes no parameters

ON_PARSE_COMMAND(Example1, CISAPIExampleExtension, ITS_EMPTY)

// Handle Example2 that takes upto 10 parameters

// (all parameters are defaulted to have no value

// if they are not submitted)

ON_PARSE_COMMAND(Example2, CISAPIExampleExtension, 
         ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR 
         ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("line1= line2= line3= line4= 
         line5= line6= line7= line8= line9= line10=")

// Set the default to Example1 (i.e. execute Example1

// when the query string is empty)

DEFAULT_PARSE_COMMAND(Example1, CISAPIExampleExtension)

END_PARSE_MAP(CISAPIExampleExtension)

基本上,此解析映射将根据传递给 DLL 的查询字符串执行以下操作:

ISAPIExample.dll - 未指定查询字符串,因此将执行不带参数的默认函数 Example1(由 DEFAULT_PARSE_COMMAND 定义)。

  • ISAPIExample.dll?Example1 - 将执行函数 Example1。
  • ISAPIExample.dll?Example2 - 无操作(失败),函数 Example2 需要参数。
  • ISAPIExample.dll?Example2?line1=Hello&line2=World - 参数 line1 和 line2 被传递给函数 Example2。该函数实际上接受 10 个文本参数 *line1..line10*,但这些参数通过在每个参数后加上等号 (=) 并给出默认值 NULL 来定义为可选参数。如果这些参数未定义为可选,则除非在查询字符串中提供了所有十个参数,否则函数将失败。(注意:某些早期版本的某些 Web 浏览器不支持查询字符串中使用两个问号。)
  • ISAPIExample.dll?Example2?line13=Fail - 无操作(失败),函数 Example2 未映射名为 *line13* 的参数。

数据库连接(ODBC、OLE DB 和 ADO)

数据库管理系统 (DBMS),如 MS SQL Server、MySQL 和 Oracle,旨在为软件开发者省去编写自己的代码来执行多用户文件处理、索引/搜索(查询)和数据安全等任务的麻烦。

ODBC 是 Microsoft 开发的一个标准,用于在数据库(如 SQL 数据库或通过 MS Jet Engine 支持的简单文本文件)和遵循 ODBC 规则的应用程序(如 Crystal Reports)之间架起桥梁。对于软件开发者来说,这个通用标准在系统可移植性方面特别有用。例如,一个应用程序可以被编写为同时处理电子表格和更新数据库,基本上使用相同的编码原则。另一个例子是,一个应用程序被编写为操作 Excel 工作簿中的数据,并将其升级到 Oracle 数据库,而无需对该软件应用程序进行任何更改;同样,如果软件应用程序被更改,例如从 MS Windows 环境更改为 Apple Macintosh 环境,则无需更改数据库。

ODBC 的基础是基于 SQL Access Group (SAG) 生成的一个开放标准,该标准基于众所周知的关系数据库结构化查询语言 (SQL),您应该对此很熟悉。

在开始使用 ODBC、OLE DB 和 ADO 之前,我们需要了解一些关于建立数据库连接的原理。

数据源名称 (DSN) 和连接字符串

要将数据库注册到 ODBC 管理器,我们需要创建一个唯一的 Data Source Name (DSN) 条目。DSN 条目因不同的 ODBC 驱动程序而异,这些驱动程序的详细信息将与您的数据库服务器一起提供。

创建 DSN 条目后,我们可以使用任何 ODBC 应用程序连接到数据库来测试连接。

连接是通过向 ODBC 管理器传递一个连接字符串来完成的,该字符串包含 DSN 和登录详细信息等信息。连接到我的示例 MS SQL 数据库的连接字符串是:

ODBC;DSN=InternetPAL;UID=sa;PWD=;

然后,ODBC 管理器会处理此字符串并填充缺失的信息,并呈现完整的 ODBC 连接字符串……例如:

ODBC;DSN=InternetPAL;UID=sa;PWD=;APP=ODBC 
   Test Program;WSID=KEVS;LANGUAGE=us_english;DATABASE=InternetPAL
ODBC 连接字符串参数
ODBC; 默认 **ODBC** 连接字符串(实际上是为了向后兼容未来的连接而提供)
DSN=InternetPAL **D**ata **S**ource **N**ame
UID=sa; **U**ser **ID**entifier(“sa”是默认的 SQL 服务器系统管理员 ID)
PWD=; **P**assword(无需密码)
APP=ODBC Test Program; **APP**lication Name
WSID=KEVS; **W**ork**S**tation **ID**entifier
LANGUAGE=us_english; 语言
DATABASE=InternetPAL 数据库名称

使用 MFC,建立 ODBC 连接非常容易。基本上,我们只需要创建一个数据库对象(CDatabase)并将相关详细信息传递给对象的类成员 Open……

CDatabase m_database;
m_database.Open("", FALSE, TRUE, "ODBC;", FALSE);
CDatabase::Open 参数
"" DSN 名称:“”(NULL),因为我们正在使用 ODBC 连接字符串
FALSE

Exclusive: 我们想要共享访问而不是独占访问(事实上,独占访问无论如何都不受支持!)

TRUE Read Only: 我们只查询数据,因此不需要写访问数据库
"ODBC;" 连接字符串:此处我们发送默认字符串
FALSE Load Cursor Library: 我们不需要光标库(默认设置)

这是完成的示例函数,它从 ODBC 对话框生成连接字符串并连接到数据库……

// Open ODBC Connection (opens default connection string 'ODBC;')

void CODBCTestDoc::OpenOdbc()
{
    m_strConnect = "ODBC;";
    // Set the default connection string,

    // i.e. no database name/login details

    BeginWaitCursor();
    // Pop up hourglass mouse pointer

    // to show that we are busy connecting


    // Attempt to send connection string to the ODBC manager

    BOOL bRet;
    try
    {
        bRet = m_database.Open("", FALSE, TRUE, m_strConnect, FALSE);
    }

    catch (CDBException* pe) // Catch ODBC excpetion if there was a problem

    {
        AfxMessageBox(pe->m_strError);
        // Present user with ODBC Error in a message box

        EndWaitCursor();
        // Change mouse pointer back to normal

        pe->Delete();
        // Clear up exception pointer memory space

        return;
        // Exit function

    }

    EndWaitCursor(); // Change mouse pointer back to normal


    // If connection returned OK, then open a recordset

    if (bRet)
    {
        m_strConnect = m_database.GetConnect();
        // User has selected a new connection string


        CDocument::SetTitle(m_strConnect);
        // Set current document title to this connection string


        OpenRecordset();
        // Call function to open a recordset

    }
}

数据库记录集

一旦我们连接到数据库,我们就可以创建一个记录集(在数据库世界中也传统地称为数据行集)。

有几种类型,它们在使用上有各种优缺点……

**动态集 (Dynasets)** - 允许双向滚动(MoveNext / MovePrevious)。可以通过发出 CRecordset::RefreshRowset 来查看数据内容更改。(有时称为键集驱动)

**快照 (Snapshots)** - 类似于相机原理,即拍摄数据的照片。仍然允许双向滚动,但直到记录集物理关闭然后重新打开后才能更新数据。

**动态 (Dynamic)** - 类似于动态集原理,但记录排序顺序的更改可能会影响其他用户。(DBMS 不广泛支持)

**仅向前 (Forward Only)** - 记录集只能从头到尾滚动,并且只能读取。这具有显著的速度优势,但我们需要关闭并重新打开记录集才能重新开始。

记录集(CRecordset 对象)可以很容易地通过以下方式生成……

CRecordset m_pRecordset; // Create recordset object

m_pRecordset = new CRecordset(&m_database);
// Point recordset to our database object

m_pRecordset->Open(CRecordset::dynaset, 
      "select ... from [Sites]", CRecordset::readOnly);

以及我们的存储过程调用……

m_pRecordset->Open(CRecordset::dynaset, 
                    "{CALL ODBCTest}", CRecordset::readOnly);
CRecordset::Open 参数
CRecordset::dynaset 记录集类型(即 ::dynaset / ::snapshot / ::dynamic 或 ::forwardOnly)
"select ... from [Sites]"

"{CALL ODBCTest}"

用于生成记录集的 SQL 语句。注意:SQL 语句必须返回行才能使 Open 完成,即您不能传递 CREATE TABLE 等语句,因为它们不返回数据行集。

CRecordset::readOnly 记录集选项(无写入)

这是完成的示例函数,它从 m_strQuery 中保存的查询字符串创建动态集。

// Open recordset

void CODBCTestDoc::OpenRecordset()
{

    // Create a new recordset object a pass the SQL query to it

    CRecordset m_pRecordset;
    m_pRecordset = new CRecordset(&m_database);
    try
    {
        if (m_sp == 0) 
        // m_sp is our flag to determine if we are sedning

        // a call to a stored procedure or an SQL statement

            m_pRecordset->Open(CRecordset::dynaset, 
                    m_strQuery, CRecordset::readOnly);
                    // Standard SQL Query

        else
            m_pRecordset->Open(CRecordset::dynaset, 
                 "{CALL " + m_strQuery + "}", CRecordset::readOnly);
                 // Stored Procedure Call

    }

    catch (CDBException* pe) 
    // Catch ODBC excpetion if there was a problem

    {
        m_bConnected = FALSE;
        // Set flag used test for an opened recordset as closed

        pe->Delete();
        // Clear up exception pointer memory space

        return;
        // Exit function

    }

    // Calculate the number of records in the recordset

    if (!m_pRecordset->IsBOF())
    // N.B. If the record set is before

    // the Beginning Of File then the file is empty

    {

        while(!m_pRecordset->IsEOF())
        // Iterate through the recordset until the End Of File is reached

            m_pRecordset->MoveNext();
    }

    m_nRowCount = m_pRecordset->GetRecordCount() + 1;
    GetFieldSpecs(); // Get field names and sizes

    UpdateAllViews(NULL); // Update all open documents (i.e. other connections)

    m_bConnected = TRUE; // Set flag used test for an opened recordset as open

}

一旦我们拥有了一个可操作的记录集对象,我们就可以考虑对其进行操作。下表包含 CRecordset 对象的一些基本属性,您应该在研究附录示例之前熟悉它们……

**数据成员**
m_pRecordset->m_nFields
m_pRecordset->m_nParams
m_nFields:字段数

m_nParams:参数数(用于存储过程参数)

**测试**
m_pRecordset->IsOpen()

m_pRecordset->IsBOF()
m_pRecordset->IsEOF()

m_pRecordset->IsDeleted()

IsOpen:用于测试记录集是否已打开

IsBOF/EOF:测试是否在第一条记录之前(BOF)或最后一条记录之后(EOF)

IsDeleted:用于确定记录集是否已被删除或自上次刷新以来已更改(例如,可能被其他用户更改)

**添加/修改记录**
m_pRecordset->CanUpdate()……如果可以……

m_pRecordset->Edit()……或…….

m_pRecordset->AddNew() ……由……完成

m_pRecordset->Update()

m_pRecordset->Delete()

如果记录集可以更新,则返回 true(非零)。

在当前记录之后编辑或创建新记录。(注意:需要 Update 来最终确定条目)

删除当前记录(注意:记录光标会自动重置到 BOF,即记录集顶部)

**滚动**
m_pRecordset->CanScroll() ……如果可以……

m_pRecordset->MoveFirst()
m_pRecordset->MovePrev()
m_pRecordset->MoveNext()
m_pRecordset->MoveLast()

记录集滚动(第一条记录、上一条记录、下一条记录、最后一条记录)
**刷新**
m_pRecordset->Requery() 重新运行 SQL 查询以更新记录集(注意:记录光标会自动重置到 BOF,即记录集顶部)
m_pRecordset->Close() 关闭记录集

ActiveX 和组件对象模型 (COM)

组件对象模型 (COM) 是 Microsoft Windows 和 Digital Equipment 操作系统中可执行文件和动态链接库 (DLL) 的行业标准通信机制。在 COM 之前,希望程序之间进行通信的开发人员可以选择使用各种临时标准,例如 DDE(动态数据交换)、OLE(对象链接和嵌入)和 VBX(Visual Basic 扩展)。ActiveX 本质上是派生自这些临时标准的标准,用于描述以下 COM 组件。

ActiveX 控件

本质上,控件(例如 窗体控件)用作应用程序输入和输出数据的接口。ActiveX 控件源自 OLE 控件 (OCX) 和 VBX 的原理,以促进在应用程序之间支持控件的代码的标准化重用,特别是用于万维网。

对于软件开发人员来说,它具有平台无关性的好处。在 Borland Delphi 或 Visual Basic 等各种环境中进行开发时,可以重用控件,并且编译后的组件可以在 Apple 和 UNIX 平台上使用。(然而,这项技术需要 Microsoft 产品,例如 MS Internet Explorer,这在软件市场引起了一些争议)。使用 ActiveX 控件的另一个好处是,可以捕获其状态并将其存储在 ActiveX 文档中。文档可以存储在 ActiveX 文档服务器上,以便在联机时使用,或者我们可以使用 MS Word 97 等任何启用了 ActiveX 的软件脱机使用它们。

注意:ActiveX 控件可以使用 MFC(Microsoft Foundation Classes)编写,但最终用于 Web 的控件应使用 ActiveX 模板库 (ATL) 编写,因为这些库经过专门设计,可将这些控件的代码大小减至最小。

Active Server Pages (ASP)

这些本质上是常规的 HTML 文档,其中包含嵌入式脚本代码段,这些代码段可由 ISAPI 扩展 DLL (asp.DLL) 处理。嵌入式脚本可以使用各种方法编写(例如 JavaScript、JScript、PERL),但默认方法(在我看来是最简单的)是使用 Visual Basic Scripting (VBScript)。(VBScript 由作为 COM/ActiveX 组件本身的脚本引擎处理。)

Active Server Pages 执行的所有任务都可以使用更高效的 ISAPI DLL 方法进行硬编码,但与其“重新发明轮子”相比,考虑使用 ASP 的以下附加好处是值得的:

会话管理 - 万维网是一个无状态环境。当用户从一个网页移动到另一个网页时,他们输入的任何信息(可能是在表单中)都会丢失,即前一个页面的状态不会被保留。任何(比如我)花费时间填写表单却被告知缺少传真号码并看到一个空白表单的人都知道这有多么令人头疼。

快速开发 - Active Server Pages 易于编码,并且具有解释执行的额外好处,因此不需要重新编译,这在开发阶段节省了大量时间(然而,因此它们不像 ISAPI DLL 那样高效)。ISAPI DLL 是共享文件,因此任何链接到它们(例如 Web 服务器)的程序都需要关闭并重新启动。此操作可能需要一分钟左右的时间,这本身就令人烦恼,但是,如果 Web 服务器正用于其他目的,则需要在不繁忙时进行。对于像 Microsoft 这样的大型跨国公司来说,这种情况会非常罕见。由于 Active Server Pages 未编译,我们可以随时更改它们而不会影响服务器。

下面的示例(AXExample.asp)显示了一个 Active Server Page,它通过添加 QUERY_STRING 环境变量中传递的任何数字来执行与 CGIExample 和 ISAPIExample 类似的任务。

<%
    '''' Request the entire query string

    Query = Request.QueryString()

    '''' Lists the input parameters into an array

    NumParameters = 1

    '''' Create storage for to hold parameters (50 should be sufficient)

    Dim Param(50)

    '''' Request first parameter

    Param(NumParameters) = Request.QueryString("num1")
    do while Param(NumParameters) <> ""
        '''' Calculate the sum total

        total = total + Int(Param(NumParameters))

        NumParameters = NumParameters + 1

        '''' Request the next parameter from the query string (num#)

        Param(NumParameters) = 
          Request.QueryString("num" + CStr(NumParameters))
    loop
    
    NumParameters = NumParameters - 1

    '''' Calculate the average (Avoid division by zero)

    if NumParameters > 0 then average = total / NumParameters
%>

<html>

    <body bgcolor=#ffffff>
        <p ><font size=5 color=#800080><b><i>
            Basic example of an ActiveX Server Page
        </i></b></font></p>
        <font size=2>

            This page has been generated by the web server from the page 
            <b>AXExample.asp</b>. This script splits out 
            the QUERY_STRING into it's various parameter components 
            and lists the server environment variables. This script also 
            demonstrates <i>state</i> on a web page 
            by calling itself with the original QUERY_STRING + 
            the additional parameter submitted with the form on this page.
        </font>
        <hr>
        <font color=#000080 face='Arial'><strong>

            Command line used to invoke this page
        </strong></font><br><br>
        <small>
            AXExample.asp?<%=Query%>
        </small><hr><font color=#000080 face='Arial'>
          <strong>QUERY_STRING Parameters split apart</strong></font>

        <form action='AXExample.asp' ID="Form1">

        <table ID="Table8">
            <tr><th>Parameter</th><th>Value</th><th>SubTotal</th></tr>

<%
    for f = 1 to NumParameters
%>
            <tr>
              <td>
                 num<%=f%>
              </td>

              <td>
                <input type=text name='num<%=f%>' 
                          value='<%=Param(f)%>' ID="Text1">
              </td>
            </tr>
<%
    next
%>

            <tr>
            <td><td><input type=text 
                   name='num<%=f%>' ID="Text2"></td>
            <td><input type=submit value='<<< Add parameter' 
                    ID="Submit1" NAME="Submit1"></td></tr>

            </table>

        </form>
        <font color=RED><b>Total 
           = <%=total%></b></font> (Average = <%=average%>)
    </body>
</html>

Active Data Objects (ADO)

ADO 使用了我们之前在 ODBC 部分中遇到的类似原理。然而,其底层结构基于 COM(如今是 .NET),而不是 ODBC 使用的标准动态链接库。

使用 ActiveX Data Objects 获得的最终结果类似于 ISODBCExample 所说明的原理。尽管此方法不如 ISAPI 方法高效(由于解释器的开销),但对于实现不支持大量用户的数据库很有用。

下面的示例(ADOExample.asp)演示了如何连接到我们的示例数据库并从中生成报告。

<%
    '''' Create connection object to InternetPAL database

    Set Conn = Server.CreateObject("ADODB.Connection")
    Conn.Open "dsn=InternetPAL;uid=sa;pwd=;"

    '''' Create Recordset object to the stored procedure ISAPIInterface

    Set RS = CreateObject("ADODB.Recordset")
    Set RS = Conn.Execute("ISAPIInterface 'ListArticles', '1'")

    '''' Get database position parameter from the query string

    pos = Request.QueryString("pos")
    if Request.QueryString("button") = "First" then pos = 0
    if Request.QueryString("button") = "Previous" then pos = pos - 15
    if Request.QueryString("button") = "Next" then pos = pos + 15
    if Request.QueryString("button") = "Last" then

    pos = 0
    do while not RS.EOF
        pos = pos + 1
        RS.MoveNext
    loop
    
    RS.MoveFirst
    pos = pos - 7

    end if
    query = Request.QueryString()
%>

<html>
    <body bgcolor=#ffffff>
        <p ><font size=5 color=#800080><b><i>
            Basic example of an ActiveX Data Objects
        </i></b></font></p>

        <font size=2>
            This page has been generated by the web server from 
            the page <b>ADOExample.asp</b>. Command line used 
            to invoke this page: ADOExample.asp?<%=Query%>
        </font>
        <table ID="Table8">

            <tr>
                <th>
                </th>
<%

    '''' Create table header with recordset field names

    for i = 0 to RS.Fields.Count - 1

%>
                <th align=left><font color=#800000 face="Arial" size=2><u>
                    <%=RS(i).Name %>

                </u></th>
<%
    next

    '''' Move to offset record

    for offset = 0 to pos - 9

        if Not RS.EOF then RS.MoveNext

    next

    '''' If the record position is not at the begin show

    '''' that there are previous records with a + symbol

    if pos > 0 then
%>

                </tr>
                <tr><td><i>+</i></td></tr>

                <tr>

<%
    end if

    '''' List 15 records from the open recordset object

    f = 0
    for t = 1 to 15
        if Not RS.EOF then
            f = f + 1
%>
                <tr><td></td></tr><tr>
<%
            for i = 0 to RS.Fields.Count - 1
%>
                <td align=left><font face="Arial" size=1><%=RS(i)%></td>

<%
            next
%>
            </tr>
<%
            RS.MoveNext
        end if
    next

    '''' If there are more recordset then display + symbol

    if not RS.EOF then
%>
                <tr><td><i>+</td></tr>
<%
    end if

    '''' Close the recordset object

    RS.Close

    '''' Close the connection object to the database

    Conn.Close
%>

        </table>

        <form action="ADOExample.asp" ID="Form1">

            <input type="submit" name="button" value="First" ID="Submit1">
            <input type="submit" name="button" value="Previous" ID="Submit2">
            <input type="hidden" name="pos" value="<%=pos%>" 
                      ID="Hidden1"><b><%=pos%></b>

            <input type="submit" name="button" value="Next" ID="Submit3">
            <input type="submit" name="button" value="Last" ID="Submit4">

        </form>

    </body>

</html>

ADO/PERL 示例:使用 ADO 从 PERL CGI 脚本连接到数据库

虽然可以将我们的 C++ 示例 (CGIExampe) 扩展到访问数据库。有一种更简单的脚本语言称为 PERL(Practical Extraction and Report Language),它在处理数据方面特别擅长。它最初由 Larry Wall 开发,并使用了一些我们在各种 C++ 示例中遇到的熟悉结构。下面的示例脚本从我们的 示例数据库生成报告。

# Include the OLE (i.e. ActiveX) libraries
use OLE;

# Write MIME Header for HTML document
print "Content-type: text/html\n\n";

print "<html>\n";

# Create ADO connection object
$Conn = CreateObject OLE "ADODB.Connection";
$Conn->Open("InternetPAL");

# Create ADO recordset object
$RS = $Conn->Execute("ISAPIInterface 'ListEntries', '1'");

# Write field names in table header
print " <tr>\n";
$Count = $RS->Fields->count;
for($i = 0; $i < $Count; ++$i)
{
    print " <th>", $RS->Fields($i)->name , "</th>\n";
}
print " </tr>\n";

# Populate table with all records
while(!$RS->EOF)
{
    print " <tr>\n";
    for ( $i = 0; $i < $Count; $i++ )
    {
        print " <td>", $RS->Fields($i)->value, "</td>\n";
    }
    print " </tr>\n";
    $RS->MoveNext;
}

# Close ADO Recordset and Connection
$RS->Close;
$Conn->Close;

print " </table>\n";
print "</html>\n";

ASP.NET(和 .NET Framework)

上面描述的“传统”ASP 与 ADO 模型有一个小问题,那就是代码是解释执行的。这种方法有一个主要缺点,那就是在大型企业规模部署时效率不高。

开发人员曾试图绕过解释代码的一些问题,例如缓存结果等,但现在 Microsoft 已对其 ASP 模型进行了重大修订,因此嵌入式源代码现在已编译。

实现这一目标的方式相当不错,即原始源 .aspx 页面被编译为通用 IL(中间语言),暴露了 .NET Framework。这使得开发人员现在可以轻松地链接和运行他们可能拥有的任何其他托管代码源。

这是一个有用的副作用,我们不再局限于 Visual Basic Scripting 和 Jscript,可以使用任何托管代码源,例如常规 C# 代码可以直接传输到 .aspx 页面。

例如

<html>
    <script language="C#" runat="server">

int nNumber = 10 * 16;
    </script>

    <body>
        Example of a C# variable nNumber=<%=nNumber%>
    </body>
</html>

一个小的缺点是部署每个 .aspx 程序的速度稍慢,也就是说,IIS 在首次命中时重新编译/链接源的过程不像解释模型那样快速,但长期好处远远超过了这种轻微的延迟。

ASP.NET 示例:使用 ASP.NET 连接到 MySQL 数据库

这是一个控制台 C# 应用程序的示例,该应用程序使用免费的 MySql ADO .NET 连接器连接到 MySQL。

using System;
using MySql.Data.MySqlClient;

public class Kevs
{
    protected MySqlConnection sqlConn;

    private bool Open(string strDNS)
    {
        bool bRetVal = false;
        try
        {
            sqlConn = new MySqlConnection(strDNS);
            sqlConn.Open();
            bRetVal = true;
        }
        catch (Exception e)
        {
        }
        return bRetVal;
    }

    private void Close()
    {
        sqlConn.Close();
    }

    private void ExecSQL(string strSQL)
    {
        try
        {
            MySqlCommand sqlCmd = new MySqlCommand(strSQL);
            sqlCmd.Connection = sqlConn;
            sqlCmd.ExecuteNonQuery();
        }
        catch (Exception e)
        {
            System.Console.WriteLine(e.Message);
        }
    }

    public static void Main()
    {
        Kevs obj = new Kevs();
        if (obj.Open("Database=InternetPAL;Data " + 
                     "Source=localhost;User Id=sa;Password="))
        {
            obj.ExecSQL("AddYourSQLCommandHere', 0, 0)");
            obj.Close();
        }
    }
}

将此代码转换为在 Internet 上作为 ASP.NET 页面运行非常简单,几乎可以进行复制和粘贴。

一种更具可重用性的方法,以便您可以在其他项目和其他 .ASPX 页面中使用您的代码,那就是导入您的托管代码库...例如

<%@ Import namespace="Your.Managed.Code" %>

(或直接从托管编译库 .dll

<%@ Assembly src="YourManagedCode.dll" %>

ASP.NET 数据绑定

ASP.NET 中有许多新功能超出了本文档的范围,但值得一提的是数据绑定。数据绑定允许您像具有某种内部保存状态的页面一样编写单个 .aspx 页面。在下面的示例中,我们创建一个普通的 ASP 列表框控件,并使用 <%# yourBindingVariable%> 对此列表框的选定项进行“绑定”。(在示例中,列表框对象属性 picker.SelectedItem.Text 是绑定变量。)

<html>
    <script language="C#" runat="server">

// Name: kev1.aspx

// Desc: Kev's C# ASP.NET hi ya

// Author: KJSL

// Copyright (c) Kev 2004


void kev_Click(Object sender, EventArgs e)
{
    Page.DataBind();
}
    </script>

    <body>
        <form runat="server" ID="Form1">
            <asp:DropDownList id="picker" runat="server">
                <asp:ListItem>Kev</asp:ListItem>

                <asp:ListItem>Rach</asp:ListItem>
                <asp:ListItem>Someone else</asp:ListItem>
            </asp:DropDownList>
            <asp:button Text="Submit" OnClick="kev_Click" runat="server/">
        </form>

        G'day <%# picker.SelectedItem.Text%>
    </body>
</html>

用于此目的的方法相当直接,一种了解数据绑定工作原理的简单方法是研究服务器为浏览器客户端生成的 JavaScript:(诀窍当然是提供 Web 表单中的隐藏值。)

<html>
    <body>

        <form method="post" action="example.aspx" id="ctl00">
<div>
<input type="hidden" name="__VIEWSTATE" 
  value="/wEPDwUKMTI5MzYyOTg5Nw9kFgQCAg9kFgICAQ8PDxYCHgtfIURhdGFCb3Vu
         ZGdkZGQCAw8VAQNLZXZkZHUwCgrQcgJvNGiS9QZW7G3q1Ebm" ID="Hidden1"/>
</div>

    <select name="picker" id="picker">
    <option selected="selected" value="Kev">Kev</option>

    <option value="Rach">Rach</option>
    <option value="Someone else">Someone else</option>

</select>
    <input type="submit" name="ctl01" value="Submit" ID="Submit1"/>
    </form>

    G'day bound_variable_gets_put_in_here
    </body>
</html>

----

附录 A:ISAPI 示例代码

这是函数 Example1 和 Example2(用于 ISAPIExample.h)的实现。

// ISAPIExample.cpp

#include <afx.h>

// Include standard MFC classes

#include <afxisapi.h>

// Include the ISAPI MFC classes

#include "ISAPIExample.h"

// Include our parse map and function prototypes (see above)


// Create the extension object (this is required

// by the web server to establish a handle to our DLL)

CISAPIExampleExtension theExtension;

// Example1: Takes no parameters

void CISAPIExampleExtension::Example1(CHttpServerContext* pCtxt)
{

    StartContent(pCtxt);
    // Passes the default HTML MIME header to our virtual HTML page

    WriteTitle(pCtxt);
    // Include the default HTML title (You should over-ride

    // this function if you wish to change the title)


    *pCtxt << 
           " <body bgcolor=#ffffff>\n";
    *pCtxt << 
           " <p ><font size=5 color=#800080>
           <b><i>Basic example of ISAPI (Internet 
           Server Application Programming Interface)</i>
           </b></font></p>\n";
    *pCtxt << 
           " <font size=2>This page has been generated by the 
           C++ dynamic link library <b>ISAPIExample.dll</b> 
           or <b>ISAPIExample.dll?Example1</b>.
           </font><br><hr>\n";
    *pCtxt << 
           " <form action=/examples/ISAPIExample/ISAPIExample.dll?
           Example2 method=POST ID="Form1">\n";
    *pCtxt << " <table ID="Table3">\n";
    *pCtxt << 
           " <tr><td><input type=text name=line1 
           ID="Text1"></td></tr>\n";
    *pCtxt << 
           " <tr><td><input type=text name=line2 
           ID="Text2"></td></tr>\n";
    *pCtxt << 
           " <tr><td><input type=text name=line3 
           ID="Text3"></td></tr>\n";
    *pCtxt << 
           " <tr><td><input type=submit 
           value='Submit to ISAPIExample.dll?Example2' 
           ID="Submit1" NAME="Submit1">\n";
    *pCtxt << " </table>\n";
    *pCtxt << " </form>\n";
    *pCtxt << " </body>\n";

    EndContent(pCtxt);              // Close HTML page

}

// Example2: Takes up to 10 named parameters

void CISAPIExampleExtension::Example2(CHttpServerContext* pCtxt, 
     LPCTSTR Param1, LPCTSTR Param2, LPCTSTR Param3, LPCTSTR Param4, 
     LPCTSTR Param5, LPCTSTR Param6, LPCTSTR Param7, LPCTSTR Param8, 
     LPCTSTR Param9, LPCTSTR Param10)
{
    StartContent(pCtxt);
    WriteTitle(pCtxt);

    CString Param[11] = {Param1, Param2, Param3, Param4, Param5, 
            Param6, Param7, Param8, Param9, Param10}, Params = "";
    char NumString[20], ParamConversion[20], 
         TotConversion[20], *AvgConversion = "";

    // Create query string

    int f = 0;
    while (Param[f] > " " && f < 10)
    {

        itoa(f + 1, NumString, 10);
        Params = Params + "line" + NumString + "=" + Param[f] + "&";
        f++;

    }

    *pCtxt << " <body bgcolor=#ffffff>\n";
    *pCtxt << 
           " <p ><font size=5 color=#800080>
           <b><i>Example 2: ISAPI function that takes 
           several parameters</i></b></font></p>\n";
    *pCtxt << 
           " <font size=2>This page has been generated by the C++ 
           dynamic link library <b>ISAPIExample.dll?Example2?" 
           << Params 
           << "</b>.</font><br><hr>\n";
    *pCtxt << 
           " <form action=/examples/ISAPIExample/ISAPIExample.dll?
           Example2 method=POST ID="Form2">\n";
    *pCtxt << " <table ID="Table4">\n";
    *pCtxt << 
           " <tr><th>Parameter</th>
           <th>Value</th><th>SubTotal
           </th></tr>\n";

    // Add up the values of the parameters

    f = 0;
    int total = 0;
    while (Param[f] > " " && f < 10)
    {

        itoa(f + 1, NumString, 10);
        itoa(atoi(Param[f]), ParamConversion, 10);
        total = total + atoi(Param[f]);
        itoa(total, TotConversion, 10);
        *pCtxt << " <tr><td>line" 
               << NumString << "</td><td><input 
               type=text name='line" << NumString << "' 
               value='" << Param[f] 
               << "' ID="Text4"></td><td><i>" 
               << TotConversion 
               << "</i></td><td><small>(Extracted " 
               << ParamConversion 
               << " from parameter)</small></td></tr>";
        f++;

    }

    // Add another extra parameters (if there are less than 10 parameters)

    if (f < 10)
    {

        itoa(f + 1, NumString, 10);
        *pCtxt << 
            " <tr><td></td><td><input 
            type=text name='line" << NumString 
            << "' ID="Text5"></td><td><input 
            type=submit value='<<< Add parameter' ID="Submit2" 
            NAME="Submit2"></td></tr>\n";

    }

    *pCtxt << " </table>\n";
    *pCtxt << " </form>\n";
    itoa(total, TotConversion, 10);
    itoa(total / f, AvgConversion, 10);
    *pCtxt << " <font color=RED><b>Total = " 
           << TotConversion << "</b></font> 
           (Average = " << AvgConversion << ")\n";
    *pCtxt << " </body>\n";

    EndContent(pCtxt);
}

附录 B:使用 ODBC 示例代码从 ISAPI 连接到数据库

此页面描述了实现示例 ISAPI 扩展 DLL 所涉及的步骤。

现在我们已经熟悉了 ODBC 和 ISAPI 的基本概念,我们可以将两者结合起来创建一个程序,该程序可以从 SQL 数据库生成动态 HTML 代码。

此示例中缺失的一个主要问题是如何更新数据库,而不仅仅是查询它。这可以通过 RFX 宏实现。

ISAPI/ODBC 示例:记录字段交换 (RFX)

RFX 宏(另请参见 parse maps)用于将数据库行集字段数据(和存储过程参数)绑定(映射)到我们派生自 CRecordset 的对象中的数据成员。

为了演示 RFX,我将使用一个派生自 CRecordset 的示例类 CISODBCExample

该类包含三个数据成员,它们是 CString 对象的数组,这些对象已被赋予以下角色:

m_Field [6] - 用于将当前数据库记录中的 6 个数据字段存储到我们数组的 6 个下标 (0..5) 中。

m_StoredProcParamm_Pass [6] - 用于将 6 个参数 (0..5) 传递给由 m_StoredProcParamas 定义的存储过程。

class CISODBCData : public CRecordset
{

public:

    // Array of data fields to map to the database

    CString m_Field[6];

    // Array use to pass parameters to a stored procedure in the database

    CString m_StoredProcParam;
    CString m_Pass[6];

    // The mapping changes at runtime,

    // so say that we will use the DYNAMIC MFC macro

    DECLARE_DYNAMIC(CISODBCData)

    // Constructor used to pass a pointer from

    // our CDatabase object to our CRecordset object

    CISODBCData(CDatabase* pDatabase = NULL);

    // Functions used to override the default CRecordset functions

    virtual CString GetDefaultConnect(); // Overide the default connection string

    virtual CString GetDefaultSQL(); // Overide the default SQL for our Recordset


    // Function called to exchange data (contains RFX macros)

    virtual void DoFieldExchange(CFieldExchange* pFX);

};

// This macro is placed in the implementation file to denote dynamic binding

IMPLEMENT_DYNAMIC(CISODBCData, CRecordset)

ISAPI/ODBC 示例:初始化 CISODBCExample 对象。

必须使用字段数(6)和参数数(6)分别覆盖两个 CRecordset 数据成员 m_nFieldsm_nParams。这是在初始化公共 CRecordset 数据库数据成员 'pdb' 的构造函数中完成的。我们还使用空字符串初始化我们定义的数据成员。

// Constructor which is used to link to our CDatabase object

CISODBCData::CISODBCData(CDatabase* pdb) : CRecordset(pdb)
{

    // Initialise the array of data fields to map to the database

    m_Field[0] = _T("");
    m_Field[1] = _T("");
    m_Field[2] = _T("");
    m_Field[3] = _T("");
    m_Field[4] = _T("");
    m_Field[5] = _T("");

    // Number of fields (N.B. This is an overridden CRecordset data member)

    m_nFields = 6;

    // Set our recordset type Click here

    // for a description of the snapshot type

    m_nDefaultType = snapshot;

    // Stored Procedure parameter passing variables

    m_StoredProcParam = _T("");
    m_Pass[0] = _T("");
    m_Pass[1] = _T("");
    m_Pass[2] = _T("");
    m_Pass[3] = _T("");
    m_Pass[4] = _T("");
    m_Pass[5] = _T("");

    // Number of parameters

    // (N.B. This is an overridden CRecordset data member)

    m_nParams = 6 + 1;

}

// Overide the Default ODBC connection string

// (opens database InternetPAL)

CString CISODBCData::GetDefaultConnect()
{

    return _T("ODBC;DSN=InternetPAL;UID=sa;PWD=");

}

// Default SQL statement (calls stored procedure ISAPIInterface)

CString CISODBCData::GetDefaultSQL()
{

    // CALL stored procedure. N.B. Require one ? for each parameter passed

    return _T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}");

}

ISAPI/ODBC 示例:使用 RFX 宏将数据库数据绑定到我们的数据成员

为了将这些数据成员绑定到数据库字段,我们在 DoFieldExchange 函数中使用 RFX 宏。例如:

RFX_Text(pFX, _T("Field1"), m_Field[1]); // Exchange textual data

RFX_TEXT 将名为 Field 1 的数据库行集字段绑定到我们的数据成员 m_Field[1]。此宏为我们所做的是帮助更新数据,具体取决于哪个数据成员已更改。当我们移动到记录集中的另一条记录时,可能使用 RS.MoveNext,当调用 DoFieldExchange 函数时,m_Field[1] 中的 CString 会使用数据库字段“Field1”中的文本数据进行更新。同样,当我们更改数据成员 m_Field[1] 的内容时,它会更新数据库。

如果交换的数据不是文本,则不能使用 RFX_TEXT 宏,我们必须使用其他 RFX 宏之一,具体取决于所需的数据类型。

根据数据库字段类型使用的宏 与 C++ 数据成员类型交换数据
RFX_Int int
RFX_Long long
RFX_Bool bool (true / false)
RFX_Single float
RFX_Double double
RFX_Binary CByteArray
RFX_LongBinary CLongBinary(用于大型二进制对象 (BLOB),例如位图)
RFX_Date CTime
// RFX Data exchange

void CISODBCData::DoFieldExchange(CFieldExchange* pFX)
{
        // RFX Bindings for our 6 stored procedure parameters

        pFX->SetFieldType(CFieldExchange::param);
        RFX_Text(pFX, _T("Report"), m_StoredProcParam);
        RFX_Text(pFX, _T("Pass1"), m_Pass[1]);
        RFX_Text(pFX, _T("Pass2"), m_Pass[2]);
        RFX_Text(pFX, _T("Pass3"), m_Pass[3]);
        RFX_Text(pFX, _T("Pass4"), m_Pass[4]);
        RFX_Text(pFX, _T("Pass5"), m_Pass[5]);
        RFX_Text(pFX, _T("Pass6"), m_Pass[6]);

        // RFX Bindings for the 6 fields in our recordset

        pFX->SetFieldType(CFieldExchange::outputColumn);
        RFX_Text(pFX, _T("Field1"), m_Field[1]);
        RFX_Text(pFX, _T("Field2"), m_Field[2]);
        RFX_Text(pFX, _T("Field3"), m_Field[3]);
        RFX_Text(pFX, _T("Field4"), m_Field[4]);
        RFX_Text(pFX, _T("Field5"), m_Field[5]);
        RFX_Text(pFX, _T("Field6"), m_Field[6]);
};

ISAPI/ODBC 示例:连接到示例 ISAPI 扩展 DLL

一旦我们建立了 CRecordset 类,我们就可以直接将其包含在派生自 CHttpServer 的 ISAPI 类中。此示例显示了示例数据库 'InternetPAL' 中存储过程 ISAPIInterface 生成的行集。使用相同的存储过程,我们可以通过将包含 6 个字段的表单传递给该过程来添加/删除记录和更新数据库。

以下 ISAPI 扩展代码包含十一个函数,其中四个可通过解析映射直接访问。

  1. 默认 - 关于框。(ISODBCExample.dll
  2. 打开记录集 - 连接到 InternetPAL 数据库,从存储过程 ISAPIInterface 创建记录集并执行显示表函数。(ISODBCExample.dll?OpenRecordset)
  3. 关闭记录集 - 关闭记录集和数据库连接。(ISODBCExample.dll?CloseRecordset)
  4. 显示 - 此函数根据传递给它的参数 'Option' 的值表现不同。
  • First - 显示第一条记录。(ISODBCExample.dll?Show?Option=First)
  • Prev - 显示上一条记录。(ISODBCExample.dll?Show?Option=Prev)
  • Next - 显示下一条记录。(ISODBCExample.dll?Show?Option=Next)
  • Last - 显示最后一条记录。(ISODBCExample.dll?Show?Option=Last)
  • Current - 显示当前记录(默认选项)。(ISODBCExample.dll?Show)
  • Table - 以表格视图显示记录集。(ISODBCExample.dll?Show?Option=Table)
  • Form - 以表单视图显示当前记录。(ISODBCExample.dll?Show?Option=Form)
  • Edit - 以编辑表单显示当前记录。(ISODBCExample.dll?Show?Option=Edit)
  • New - 显示一个空白记录以添加到数据库。(ISODBCExample.dll?Show?Option=New)。
  • Delete - 删除当前记录(并显示第一条记录)。(ISODBCExample.dll?Show?Option=Delete)
  • Locate - 显示 OID(对象 ID)= 参数选项的记录。(ISODBCExample.dll?Show?Option=1 ... ,2,5,20)
// Header file: ISODBCExample.h

#include "ISODBCData.h"             // Include database class

class CISODBCExample : public CHttpServer
{
public:

    CISODBCExample();        // ISAPI Thread Constructor

    ~CISODBCExample();       // ISAPI Thread Destructor (closes database)


    CDatabase db;            // InternetPAL Database Object SQL Database

    TR Field1,  rs;          // Current database Stored Procedure Recordset Object

    CString Previous_repNo;  // Current Stored Procedure report variable

    int DBView;              // Current database view (1 = Table / 2 = Form View)

    char DataState;          // Current state of the data (1 = Open / 2 = Closed)


    void Default(CHttpServerContext* pCtxt);
    void OpenRecordset(CHttpServerContext* pCtxt, 
         LPCTSTR repNo, LPCTSTR param);
    void CloseRecordset(CHttpServerContext* pCtxt);
    void ShowTable(CHttpServerContext* pCtxt);
    void WriteTitle(CHttpServerContext* pCtxt);
    void Show(CHttpServerContext* pCtxt, LPCTSTR option);
    void PutRecord(CHttpServerContext* pCtxt);
    void EditRecord(CHttpServerContext* pCtxt);
    void NewRecord(CHttpServerContext* pCtxt);
    void UpdateDB(CHttpServerContext* pCtxt, LPCTSTR Field1, 
         LPCTSTR Field2, LPCTSTR Field3, LPCTSTR Field4, 
         LPCTSTR Field5, LPCTSTR Field6);
    void AddToDB(CHttpServerContext* pCtxt, LPCTSTR Field1, 
         LPCTSTR Field2, LPCTSTR Field3, LPCTSTR Field4, 
         LPCTSTR Field5, LPCTSTR Field6);
    void DeleteFromDB(CHttpServerContext* pCtxt);

    // Declare the parse map

    DECLARE_PARSE_MAP()
};
// Implementation file: ISODBCExample.cpp

#include <afx.h>         // Include the standard MFC classes

#include <afxisapi.h>    // Include the ISAPI MFC classes

#include <afxdb.h>       // Include the MFC Database classes

#include "ISODBCExample.h"     // Include ISAPI extension header


// Handle to our extension (required by the web server)

CISODBCExample theExtension;

// PARSE MAP, Maps the web browsers URL: ISODBCExample.dll?

//<Function>&<Params> to the equivelent

// ISODBCExample.DLL Functions

BEGIN_PARSE_MAP(CISODBCExample, CHttpServer)

    // URL: ISODBCExample.dll?OpenRecordset

    //&<Recordset> --> OpenRecordset(Recordset)

    ON_PARSE_COMMAND(OpenRecordset, CISODBCExample, ITS_PSTR ITS_PSTR)
    ON_PARSE_COMMAND_PARAMS("Recordset=ListEntries Pass1=1")

    // URL: ISODBCExample.dll?ShowRecord&<option> --> Show(Option)

    ON_PARSE_COMMAND(Show, CISODBCExample, ITS_PSTR)
    ON_PARSE_COMMAND_PARAMS("Option=Current")

    // URL: ISODBCExample.dll?CloseRecordset --> CloseRecordset()

    ON_PARSE_COMMAND(CloseRecordset, CISODBCExample, ITS_EMPTY)
    // Close recordset


    // URL: ISODBCExample.dll?UpdateDB&<Pass1>

    // &<Pass2>&<Pass Parameters ...> -->

    // UpdateDB(Pass Parameters ...)

    ON_PARSE_COMMAND(UpdateDB, CISODBCExample, 
       ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
       // Update recordset

    ON_PARSE_COMMAND_PARAMS("Pass1= Pass2= Pass3= Pass4= Pass5= Pass6=")

    // URL: ISODBCExample.dll?AddToDB&<Pass1>

    //&<Pass2>&<Pass Parameters ...> -->

    // AddToDB(Pass Parameters ...)

    ON_PARSE_COMMAND(AddToDB, CISODBCExample, 
       ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR ITS_PSTR)
       // Add new record to recordset

    ON_PARSE_COMMAND_PARAMS("Pass1= Pass2= Pass3= Pass4= Pass5= Pass6=")

    // URL: ISODBCExample.dll?Default --> Default()

    ON_PARSE_COMMAND(Default, CISODBCExample, ITS_EMPTY)

    // URL: ISODBCExample.dll *No parameters* --> Default()

    DEFAULT_PARSE_COMMAND(Default, CISODBCExample)

END_PARSE_MAP(CISODBCExample)

// Thread Constructor

CISODBCExample::CISODBCExample()
{
    DBView = 1;
}

// Thread Destructor (Close the recordset

// and database connection if left open)

CISODBCExample::~CISODBCExample()
{
    if (DataState == 1)
    {
        rs.Close();
        db.Close();
    }

}

// DEFAULT, Displays a basic HTML about page

// for the ISODBCExample.DLL program

void CISODBCExample::Default(CHttpServerContext* pCtxt)
{
    StartContent(pCtxt);
    *pCtxt << _T("<body bgcolor=#ffffff>\n");
    *pCtxt << _T(" <p ><font size=5 color=#800080>
                         <b><i>Example of ISAPI and 
                         ODBC</i></b></font></p>\n");
    *pCtxt << _T(" ISODBCExample: ISAPI Server Extension 
                         which uses ODBC32, Copyright Kevin Staunton-Lambert 
                         1997<br><br>\n");
    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
} 

// OPEN RECORDSET, Connects to the InternetPAL SQL database

// (via ODBC), and sends previous data and the report function

// parameter to the stored procedure ISAPIInterface

void CISODBCExample::OpenRecordset(CHttpServerContext* pCtxt, 
                                 LPCTSTR repNo, LPCTSTR param)
{
    StartContent(pCtxt);
    if (!DataState)
    {
        try
        {
            db.OpenEx(_T("DSN=InternetPAL;UID=sa;PWD="), 
                         CDatabase::noOdbcDialog);
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

        rs.m_StoredProcParam = (CString)repNo;
        rs.m_Pass[1] = param;

        // Open the recordset

        try
        {
            if (rs.Open(AFX_DB_USE_DEFAULT_TYPE, 
                _T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}") ,NULL))
            {
                DataState = 1;
                // Let other functions know that

                // the recordset is sucessfully open

                Show(pCtxt, "Table");
                // Initially display the recordset in 'Table' view

            }

        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }
    // If there is a recordset is open, close it then try

    // opening the recordset again (using recursion)

    else
    {
        CloseRecordset(pCtxt);
        OpenRecordset(pCtxt, repNo, "1");
    }
    
    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);

    // Update the global report identifier

    Previous_repNo = repNo;
}

// CLOSE RECORDSET, If a recordset is open this function closes

// the recordset and database connection

void CISODBCExample::CloseRecordset(CHttpServerContext* pCtxt)
{
    StartContent(pCtxt);
    if (DataState == 1)
    {
        DataState = 0;
        try
        {
            rs.Close();
            db.Close();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }
    else
        *pCtxt << "Error: Recordset already closed\n";

    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
}

// Overide the default CHttpServerContext::WriteTitle

void CISODBCExample::WriteTitle(CHttpServerContext* pCtxt)
{
    *pCtxt << _T("<head><title>ISODBC Example
                        </title></head>\n");
}

// SHOW TABLE, Displays a recordset as a table

// (Uses current recordsets memory space)

void CISODBCExample::ShowTable(CHttpServerContext* pCtxt)
{
    CODBCFieldInfo Field;
    int i = 1, f = 1;
    CString TempField, TempOID;
    TempOID = rs.m_Field[1];

    // Generate table header

    *pCtxt << _T("<table border=\"0\" ID="Table8"><thead>
                        <tr><th></th><b>\n");
    for (f = 2; f <= (int)rs.m_nFields; f++)
    // Hide first coulmn (OID) from table

    {
        // Attempt to get the Recordsets field names (Show any ODBC exceptions)

        try
        {
            rs.GetODBCFieldInfo(f - 1, Field);
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

        // Show Recordset's field names

        *pCtxt 
          << _T("<TH align=LEFT><font color=#800000 
                       face=\"Arial\" size=2><U>") 
          << _T(Field.m_strName) << _T("</U></th>\n");
    }

    // Scroll back 10 records to show records at top of page.

    // (Show '+' if not at top of recordset)

    for (f = 1; f < 10 && !rs.IsBOF(); f++)
    {
        try
        {
            rs.MovePrev();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    if (!rs.IsBOF())
        *pCtxt 
          << _T("<tr><td><i>+</td></tr>\n");
    else
    {
        try
        {
            rs.MoveNext(); // Prevents the first record being displayed twice

        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    // Generate table rows (Display only 25 records from the current recordset)

    while (i < 25 && !rs.IsEOF())
    {
        *pCtxt 
          << _T("</tr></b></thead><font face=Arial size=1>\n");
        // Highlight current record (or records if the recordset

        // doen not have unique object ID's)

        if (TempOID == rs.m_Field[1])
            *pCtxt 
              << _T("<TR BGcolor=#00FFFF><td><b>></td>\n");
        else
            *pCtxt << _T("<tr><td></td>\n");

        // Show Recordset's data fields

        for (f = 2; f <= (int)rs.m_nFields; f++)
        // Hide first coulmn (OID) from table

            *pCtxt 
              << _T("<td align=left><font face=\"Arial\" size=1>") 
              << _T(rs.m_Field[f]) << _T("</td>\n");
        *pCtxt << _T("</tr>\n");

        try
        {
            rs.MoveNext();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        i++;
    }

    // Show '+' if there are more records to come

    if (!rs.IsEOF()) *pCtxt 
      << _T("<tr><td><i>+</td></tr>\n");
    *pCtxt << _T("</font></table>\n");

    // Place user back where they were in the recordset

    try
    {
        rs.MoveFirst();
    }
    catch(CDBException* e)
    {
        *pCtxt << e->m_strError;
        return;
    }

    while (!rs.IsEOF() && rs.m_Field[1] != TempOID)
    {
        try
        {
            rs.MoveNext();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }
}

// PUT RECORD, Displays a record in 'Form' view

void CISODBCExample::PutRecord(CHttpServerContext* pCtxt)
{
    CODBCFieldInfo Field;
    *pCtxt << _T("<body bgcolor=#ffffff>\n");

    *pCtxt << _T("<table ID="Table9">\n");
    for (int f = 2; f <= (int)rs.m_nFields; f++)
    {
        // Attempt to get the Recordsets field names (Show any ODBC exceptions)

        try
        {
            rs.GetODBCFieldInfo(f - 1, Field);
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

        // Show fields (Hide NULL fields)

        if (Field.m_strName > "")
        {
            // Show field name

            *pCtxt << _T("<tr><td align=right>
                                <font color=#800000 face=\"Arial\" size=2>")
                   << _T(Field.m_strName) << _T("\n");

            // Show data field

            *pCtxt << _T(": </td><td align=left>
                                 <font color=#000000 face=\"Arial\" size=2>")
                   << _T(rs.m_Field[f]) << _T("</td></tr>\n");
        }
    }
    *pCtxt << _T("</font></table>\n");
}

// EDIT RECORD, Displays a record in 'Edit' mode

void CISODBCExample::EditRecord(CHttpServerContext* pCtxt)
{
    CODBCFieldInfo Field;
    // Create an ODBC field information object

    char* TempStr = "";
    // Temporary variable used when converting integers to strings


    *pCtxt << _T("<form action='/examples/ISODBCExample/
                        ISODBCExample.dll?UpdateDB' 
                        method=post ID="Form1">\n");

    // Pass OID to the stored procedure but keep it hidden from user

    *pCtxt << _T("<input name='Pass1' type=hidden value='") 
           << _T(rs.m_Field[1]) 
           << "' ID="Hidden1"><table ID="Table10">\n";

    for (int f = 2; f <= (int)rs.m_nFields; f++)
    {
        // Attempt to get the Recordsets

        // field names (Show any ODBC exceptions)

        try
        {
            rs.GetODBCFieldInfo(f - 1, Field);
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

        // Show fields (Hide NULL fields)

        if (Field.m_strName > "")
        {
            // Show field name

            *pCtxt << _T("<tr><td align=right>
                                <font color=#800000 face=\"Arial\" size=2>") 
                  << _T(Field.m_strName) << _T(":</td>\n");
            // Show input box HTML: INPUT name=Pass<FieldNo>

            // type=text value=<data field>


            *pCtxt << _T("<td><input name='Pass");
            itoa(f, TempStr, 10); // Convert field number to string

            *pCtxt << _T((CString)*TempStr);
            *pCtxt << _T("' type=text value='");
            *pCtxt << _T(rs.m_Field[f]) 
                   << _T("'></td></tr>\n");
        }

    }
    // Show a 'submit' button that POST's the updated form

    // to the ISODBCExample.dll?UpdateDB function

    *pCtxt << _T("</font><tr><td>
                        <input type=submit value='Update Record'>
                        </td></tr></table></form>\n");
}

// NEW RECORD, Displays a record in 'New' record mode

void CISODBCExample::NewRecord(CHttpServerContext* pCtxt)
{
    CODBCFieldInfo Field;
    char* TempStr = "";

    *pCtxt << _T("<body bgcolor=#ffffff>\n");

    *pCtxt << _T("<form action='/examples/ISODBCExample/
                        ISODBCExample.dll?AddToDB' method=post>\n");

    // Copy Field1 as Pass1 field. (Unique object ID's

    // are automatically generated by the SQL Database)

    *pCtxt 
         << _T("<input name='Pass1' type=hidden value='") 
         << _T(rs.m_Field[1]) << _T("'><table>\n");

    for (int f = 2; f <= (int)rs.m_nFields; f++)
    {
        // Attempt to get the Recordsets field names

        // (Show any ODBC exceptions)

        try
        {
            rs.GetODBCFieldInfo(f - 1, Field);
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        // Show fields (Hide NULL fields)

        if (Field.m_strName > "")
        {
            // Show field name

            *pCtxt 
              << _T("<tr><td align=right>
                    <font color=#800000 face=\"Arial\" size=2>") 
             << _T(Field.m_strName) << _T(": ");
            // Show input box HTML: INPUT name=Pass<FieldNo>

            // type=text value=NULL (New data)

            *pCtxt << _T("<input name='Pass");
            itoa(f, TempStr, 10);
            *pCtxt << _T((CString)*TempStr);
            *pCtxt << _T("' type=text></td></tr>\n");
        }
    }
    // Show a 'submit' button that POST's the new form

    // to the ISODBCExample.dll?AddToDB function

    *pCtxt 
      << _T("</font><tr><td><input 
                   type=submit value='Update Record'></td></tr>
                   </table></form>");
}

// SHOW RECORD, Multi purpose function which

// controls various aspects of a recordset

void CISODBCExample::Show(CHttpServerContext* pCtxt, LPCTSTR option)
{
    StartContent(pCtxt);
    *pCtxt << _T("<body bgcolor=#ffffff>\n");
    *pCtxt 
      << _T(" <p ><font size=5 color=#800080>
                    <b><i>Example of ISAPI and ODBC</i>
                    </b></font></p>\n");
    *pCtxt 
      << _T(" <font size=2>This page has been generated 
                    by the ISAPI extension DLL <b>ISODBCExample.dll</b>. 
                    This program allows viewing and updation of articles found 
                    in the InternetPAL database.</font><br><hr>\n");

    CString opt = option, TempOID = rs.m_Field[1];
    if (DataState)
    {
        // Record Navigation

        // Move to the first record (Update the table/form display)

        if (opt == "First")
        {
            try
            {
                rs.MoveFirst();
            }
            catch(CDBException* e)
            {
                *pCtxt << e- ID="Text1">m_strError;
                return;
            }
            switch (DBView)
            {
                case 2: PutRecord(pCtxt); break;
                default: ShowTable(pCtxt); break;
            }
        }
        // Move to the previous object ID (Update the table/form display)

        else if (opt == "Prev")
        {
            if (DBView == 1)
            {
                while (!rs.IsBOF() && rs.m_Field[1] == TempOID)
                {
                    try
                    {
                        rs.MovePrev();
                    }
                    catch(CDBException* e)
                    {
                        *pCtxt << e->m_strError;
                        return;
                    }
                }
            }
            ShowTable(pCtxt);
        }
        else
        {
            try
            {
                rs.MovePrev();
            }
            catch(CDBException* e)
            {
                *pCtxt << e->m_strError;
                return;
            }
            PutRecord(pCtxt);
        }
    }

    // Move to the next object ID (Skip over table

    // groups) (Update the table/form display)

    else if (opt == "Next")
    {
        if (DBView == 1)
        {
            while (!rs.IsEOF() && rs.m_Field[1] == TempOID)
            {
                try
                {
                    rs.MoveNext();
                }
                catch(CDBException* e)
                {
                    *pCtxt << e->m_strError;
                    return;
                }
            }
            ShowTable(pCtxt);
        }
        else
        {
            try
            {
                rs.MoveNext();
            }
            catch(CDBException* e)
            {
                *pCtxt << e->m_strError;
                return;
            }
            PutRecord(pCtxt);
        }
    }

    // Move to the last record (Update the table/form display)

    else if (opt == "Last")
    {
        try
        {
            rs.MoveLast();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        switch (DBView)
        {
            case 2: PutRecord(pCtxt); break;
            default: ShowTable(pCtxt); break;
        }

    }
    // Update the display (Recordset position does not change)

    else if (opt == "Current")
    {
        switch (DBView)
        {
            case 2: PutRecord(pCtxt); break;
            default: ShowTable(pCtxt); break;
        }
    }

    // Recordset view

    // Change to 'Table' view

    else if (opt == "Table")
    {
        DBView = 1;
        ShowTable(pCtxt);
    }
    // Change to 'Form' view

    else if (opt == "Form")
    {
        DBView = 2;
        PutRecord(pCtxt);
    }
    // Change to 'Edit' mode

    else if (opt.Left(4) == "Edit")
    // For some reason simply testing for "Edit"

    // seems to fail after the first use, why ?

    {
        EditRecord(pCtxt);
    }
    // Change to 'New' record mode

    else if (opt.Left(3) == "New")
    // Again, simply testing for "New" seems to fail after the first use.

    {
        NewRecord(pCtxt);
    }
    // Delete record

    else if (opt.Left(6) == "Delete")
    // Again, simply testing for "Delete" seems to fail after the first use.

    {
        DeleteFromDB(pCtxt);
    }
    // Locate a record (Match object ID with the option parameter)

    else
    {
        try
        {
            rs.MoveFirst();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        int Found = 0;
        while (!rs.IsEOF() && !Found)
        {
            if (rs.m_Field[1] == opt)
            {
                switch (DBView)
                {
                    case 2: PutRecord(pCtxt); break;
                    default: ShowTable(pCtxt); break;
                }
                Found = 1;
            }
            else
            {
                try
                {
                    rs.MoveNext();
                }
                catch(CDBException* e)
                {
                    *pCtxt << e->m_strError;
                    return;
                }
            }
        }
        if (!Found) *pCtxt 
          << "Could not match a record with Object ID: " 
          << opt << _T("\n");
        }

        // Show if the recordset pointer is at the first or last record

        if (rs.IsBOF()) *pCtxt << "<i>(First Record)</i>\n";
        if (rs.IsEOF()) *pCtxt << "<i>(Last Record)</i>\n";
    }
    else
        *pCtxt << "Error: Recordset not open\n";

    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
}

// UPDATE DataBase, Maps the submitted 'update record'

// HTML form to the data set, or sends the data to the stored

// procedure UpdateDB <Global Recordset>


void CISODBCExample::UpdateDB(CHttpServerContext* pCtxt, 
     LPCTSTR Field1, LPCTSTR Field2, LPCTSTR Field3, 
     LPCTSTR Field4, LPCTSTR Field5, LPCTSTR Field6)
{
    StartContent(pCtxt);

    // Attempt to update the recordset directly

    if (rs.CanUpdate())
    {
        try
        {
            rs.Edit();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        if (Field1 > "") rs.m_Field[1] = Field1;
        if (Field2 > "") rs.m_Field[2] = Field2;
        if (Field3 > "") rs.m_Field[3] = Field3;
        if (Field4 > "") rs.m_Field[4] = Field4;
        if (Field5 > "") rs.m_Field[5] = Field5;
        if (Field6 > "") rs.m_Field[6] = Field6;
        try
        {
            rs.Update();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    // Otherwise try sending the data to the 'UpdateDB'

    // stored procedure for the current recordset being used

    else
    {
        CISODBCData Temp_rs;
        Temp_rs.m_StoredProcParam = "Update" + (CString)Previous_repNo;
        Temp_rs.m_Pass[1] = Field1;
        Temp_rs.m_Pass[2] = Field2;
        Temp_rs.m_Pass[3] = Field3;
        Temp_rs.m_Pass[4] = Field4;
        Temp_rs.m_Pass[5] = Field5;
        Temp_rs.m_Pass[6] = Field6;
        try
        {
            Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE, 
                 _T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}"), 
                 NULL);
            Temp_rs.Close();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

    }

    // Requery the recordset and show the updated data


    rs.Requery();
    Show(pCtxt, Field1);
    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
}

// ADD TO DataBase, Maps the submitted 'new record' HTML

// form to the data set, or sends the data to the stored

// procedure AddToDB <Global Recordset>


void CISODBCExample::AddToDB(CHttpServerContext* pCtxt, 
     LPCTSTR Field1, LPCTSTR Field2, LPCTSTR Field3, 
     LPCTSTR Field4, LPCTSTR Field5, LPCTSTR Field6)
{
    // Attempt to add to the recordset directly

    if (rs.CanUpdate())
    {
        try
        {
            rs.AddNew();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
        if (Field1 > "") rs.m_Field[1] = Field1;
        if (Field2 > "") rs.m_Field[2] = Field2;
        if (Field3 > "") rs.m_Field[3] = Field3;
        if (Field4 > "") rs.m_Field[4] = Field4;
        if (Field5 > "") rs.m_Field[5] = Field5;
        if (Field6 > "") rs.m_Field[6] = Field6;

        try
        {
            rs.Update();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    // Otherwise try sending the data to the 'AddToDB'

    // stored procedure for the current recordset being used

    else
    {
        CISODBCData Temp_rs;
        Temp_rs.m_StoredProcParam = "Add" + (CString)Previous_repNo;
        Temp_rs.m_Pass[1] = Field1;
        Temp_rs.m_Pass[2] = Field2;
        Temp_rs.m_Pass[3] = Field3;
        Temp_rs.m_Pass[4] = Field4;
        Temp_rs.m_Pass[5] = Field5;
        Temp_rs.m_Pass[6] = Field6;

        try
        {
            Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE, 
                  _T("{CALL ISAPIInterface (?, ?, ?, ?, ?, ?, ?)}") ,NULL);
            Temp_rs.Close();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    // Requery the recordset and show the updated data

    // (N.B. Cannot use ShowRecord() because object ID is unknown)

    try
    {
        rs.Requery();
    }
    catch(CDBException* e)
    {
        *pCtxt << e->m_strError;
        return;
    }
    while (Field2 != rs.m_Field[2] && 
           Field3 != rs.m_Field[3] && !rs.IsEOF())
    {
        try
        {
            rs.MoveNext();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }
    switch (DBView)
    {
        case 2: PutRecord(pCtxt); break;
        default: ShowTable(pCtxt); break;
    }
    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
}

// DELETE FROM DataBase, Attempts to delete a record from

// the recordset, or deletes the record using

// the DeleteFromDB stored procedure

void CISODBCExample::DeleteFromDB(CHttpServerContext* pCtxt)
{
    StartContent(pCtxt);

    // Attempt to delete the record from the recordset directly

    if (rs.CanUpdate())
    {
        try
        {
            rs.Delete();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }

    }
    // Otherwise try the delete stored procedure

    // for the current recordset being used

    else
    {
        CISODBCData Temp_rs;
        Temp_rs.m_StoredProcParam = "Delete" + (CString)Previous_repNo;
        Temp_rs.m_Pass[1] = rs.m_Field[1];
        try
        {
            Temp_rs.Open(AFX_DB_USE_DEFAULT_TYPE, 
                         _T("{CALL ISAPIInterface (?, ?)}") ,NULL);
            Temp_rs.Close();
        }
        catch(CDBException* e)
        {
            *pCtxt << e->m_strError;
            return;
        }
    }

    // Requery the recordset (User is placed

    // at the first record in the recordset)

    try
    {
        rs.Requery();
    }
    catch(CDBException* e)
    {
        *pCtxt << e->m_strError;
        return;
    }
    switch (DBView)
    {
        case 2: PutRecord(pCtxt); break;
        default: ShowTable(pCtxt); break;
    }
    *pCtxt << _T("</body>\n");
    EndContent(pCtxt);
}

附录 C:示例中使用的示例 MS SQL 数据库

这是重现本文档中各种 ODBC/ADO 示例连接的示例数据库所需的 Transact-SQL。

/****** Object:  Stored Procedure dbo.GetNextClassification ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.GetNextClassification') 
           and sysstat & 0xf = 4)
    drop procedure "dbo"."GetNextClassification"
GO

/****** Object:  Stored Procedure dbo.ISAPIInterface ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ISAPIInterface') and sysstat & 0xf = 4)
    drop procedure "dbo"."ISAPIInterface"
GO

/****** Object:  Stored Procedure dbo.ListAllKeywords ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListAllKeywords') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListAllKeywords"
GO

/****** Object:  Stored Procedure dbo.ListArticles ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListArticles') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListArticles"
GO

/****** Object:  Stored Procedure dbo.ListIndexes ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListIndexes') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListIndexes"
GO

/****** Object:  Stored Procedure dbo.ListMatches ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListMatches') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListMatches"
GO

/****** Object:  Stored Procedure dbo.ListRelated ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListRelated') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListRelated"
GO

/****** Object:  Stored Procedure dbo.ListRelatedKeywords ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListRelatedKeywords') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListRelatedKeywords"
GO

/****** Object:  Stored Procedure dbo.ListURLs ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.ListURLs') and sysstat & 0xf = 4)
    drop procedure "dbo"."ListURLs"
GO

/****** Object:  Stored Procedure dbo.Search ******/
if exists (select * from sysobjects where 
           id = object_id('dbo.Search') and sysstat & 0xf = 4)
    drop procedure "dbo"."Search"
GO

/****** Object:  Stored Procedure dbo.SearchSounds ******/
if exists (select * from sysobjects where id = object_id('dbo.SearchSounds') 
                                                  and sysstat & 0xf = 4)
    drop procedure "dbo"."SearchSounds"
GO

/****** Object:  Stored Procedure dbo.ClassCode ******/
if exists (select * from sysobjects where id = object_id('dbo.ClassCode') 
                                               and sysstat & 0xf = 4)
    drop procedure "dbo"."ClassCode"
GO

/****** Object:  Table dbo.Entries ******/
if exists (select * from sysobjects where id = object_id('dbo.Entries') 
                                              and sysstat & 0xf = 3)
    drop table "dbo"."Entries"
GO

/****** Object:  Table dbo.Indexes ******/
if exists (select * from sysobjects where id = 
           object_id('dbo.Indexes') and sysstat & 0xf = 3)
    drop table "dbo"."Indexes"
GO

/****** Object:  Table dbo.Thesaurus ******/
if exists (select * from sysobjects where 
   id = object_id('dbo.Thesaurus') and sysstat & 0xf = 3)
    drop table "dbo"."Thesaurus"
GO

/****** Object:  Table dbo.URLs ******/
if exists (select * from sysobjects where id = object_id('dbo.URLs') 
                                          and sysstat & 0xf = 3)
    drop table "dbo"."URLs"
GO

/****** Object:  Table dbo.Entries ******/
CREATE TABLE "dbo"."Entries" (
    "OID" "int" NOT NULL ,
    "Name" varchar (100) NOT NULL ,
    "URL" "int" NOT NULL ,
    "Bookmark" varchar (255) NULL ,
    CONSTRAINT "PK___3__10" PRIMARY KEY  CLUSTERED 
    (
        "OID"
    ),
    CONSTRAINT "NoDuplicateNames" UNIQUE  NONCLUSTERED 
    (
        "Name"
    ),
    CONSTRAINT "OID" CHECK (OID >= 100000000 and (OID <= 999999999))
)
GO

/****** Object:  Table dbo.Indexes ******/
CREATE TABLE "dbo"."Indexes" (
    "OID" "int" IDENTITY (1, 1) NOT NULL ,
    "Name" varchar (100) NOT NULL ,
    "Base_Entry_OID" "int" NOT NULL ,
    "Top_Entry_OID" "int" NOT NULL ,
    CONSTRAINT "PK___2__10" PRIMARY KEY  CLUSTERED 
    (
        "OID"
    ),
    CONSTRAINT "NoDuplicateIndexNames" UNIQUE  NONCLUSTERED 
    (
        "Name"
    ),
    CONSTRAINT "BaseEntry" CHECK (Base_Entry_OID >= 100000000 
                and (Base_Entry_OID <= 999999999)),
    CONSTRAINT "TopEntry" CHECK (Top_Entry_OID >= 100000000 
                and (Top_Entry_OID <= 999999999))
)
GO

/****** Object:  Table dbo.Thesaurus ******/
CREATE TABLE "dbo"."Thesaurus" (
    "OID" "int" IDENTITY (1, 1) NOT NULL ,
    "Entry_OID" "int" NOT NULL ,
    "String" varchar (30) NOT NULL ,
    CONSTRAINT "PK___1__10" PRIMARY KEY  CLUSTERED 
    (
        "OID"
    ),
    CONSTRAINT "EntryOID" CHECK (Entry_OID >= 100000000 
                and (Entry_OID <= 999999999))
)
GO

/****** Object:  Table dbo.URLs ******/
CREATE TABLE "dbo"."URLs" (
    "OID" "int" IDENTITY (1, 1) NOT NULL ,
    "URL" varchar (255) NOT NULL ,
    CONSTRAINT "PK___4__10" PRIMARY KEY  CLUSTERED 
    (
        "OID"
    ),
    CONSTRAINT "NoDuplicateURLs" UNIQUE  NONCLUSTERED 
    (
        "URL"
    )
)
GO

/****** Object:  Stored Procedure dbo.ClassCode ******/
--- Split the class code (Entry object ID) into the various facets by colons

CREATE PROCEDURE ClassCode @Code int = 0 as

  --- Convert @Code to string and add colons a appropirate points

  DECLARE @ClassCode varchar(13)
  SELECT @ClassCode = CONVERT(varchar(9), @Code)
  SELECT @ClassCode = SUBSTRING(@ClassCode, 1, 1) + ":" + 
         SUBSTRING(@ClassCode, 2, 2) + ":" + 
         SUBSTRING(@ClassCode, 4, 2) + ":" + 
         SUBSTRING(@ClassCode, 6, 2) + ":" + 
         SUBSTRING(@ClassCode, 8, 2)

  --- Return new string

  SELECT @ClassCode
GO

/****** Object:  Stored Procedure dbo.GetNextClassification ******/
--- Returns the next valid classification number in a particular index

CREATE PROCEDURE GetNextClassification @index int = 0AS

  --- Find the band range for the index specified in @index

  DECLARE @Base int
  DECLARE @Top int
  DECLARE @OID int
  SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID 
    FROM Indexes 
    WHERE OID = @index

  --- Calculate the next availible OID by selecting all records in order

  SELECT @OID = (OID + 1) FROM Entries WHERE Entries.OID 
   >= @Base AND Entries.OID <= @Top ORDER BY OID

  --- Return the @OID found

  SELECT @OID
GO

/****** Object:  Stored Procedure dbo.ISAPIInterface     ******/
-- This procedure is used by the ISAPI extension (ISODBCExample.dll)

-- as a fixed entry point into the InternetPAL database

CREATE PROCEDURE ISAPIInterface @Action varchar(20) = "DataErr", 
       @Pass1 varchar(40) = "", @Pass2 varchar(40) = "", 
       @Pass3 varchar(40) = "", @Pass4 varchar(40) = "", 
       @Pass5 varchar(40) = "", @Pass6 varchar(40) = "", 
       @Pass7 varchar(40) = "", @Pass8 varchar(40) = "" AS

  --- If list of entries in a particular index is required...

  IF @Action LIKE "%List%"
  BEGIN

    --- Find index banding boundaries

    DECLARE @Base int
    DECLARE @Top int
    SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID 
      FROM Indexes 
      WHERE OID = CONVERT(int, @Pass1)

    --- Return articles that fit in this banding

    SELECT Thesaurus.OID, Entries.OID AS "Classification", 
           String AS "Term", Name AS "Article", URLs.URL AS "URL", 
           Bookmark, NULL, NULL
      FROM Entries, URLs, Thesaurus
      WHERE Entries.OID >= @Base AND Entries.OID <= @Top 
            AND Entries.URL = URLs.OID 
            AND Thesaurus.Entry_OID = Entries.OID
      ORDER BY Entries.OID
  END

  --- If record has been updated...

  IF @Action LIKE "%Update%"
  BEGIN

    --- Update the thesaurus entry

    UPDATE Thesaurus
      SET
        String = @Pass3
      WHERE OID = CONVERT(int, @Pass1)

    --- Find the current entry OID for this term

    DECLARE @OID int
    SELECT @OID = Entry_OID
      FROM Thesaurus
      WHERE OID = CONVERT(int, @Pass1)

    --- Find the current URL OID for this entry

    DECLARE @URL int
    SELECT @URL = URL
      FROM Entries
      WHERE OID = @OID

    --- Update the URL

    UPDATE URLs
      SET
        URL = @Pass5
      WHERE OID = @URL

    --- Update the entries table accordingly

    UPDATE Entries
      SET   
        OID = CONVERT(int, @Pass2),
        Name = @Pass4,
    Bookmark = @Pass6
      WHERE OID = @OID
  END

  --- If record is being added to the database... (N.B. This is only

  --- an example for completeness and should NOT be used!)

  IF @Action LIKE "%Add%"
  BEGIN

    --- Add the URLs

    INSERT URLs
      VALUES (@Pass5)

    --- Get URL OID

    SELECT @URL = OID
      FROM URLs
      WHERE URL = @Pass5

    --- Add the entries

    INSERT Entries
      VALUES (CONVERT(int, @Pass2), @Pass4, @URL, @Pass5)

    --- Add the thesaurus entry

    INSERT Thesaurus
      VALUES (CONVERT(int, @Pass2), @Pass3)
  END

  --- If record is being deleted from the database... 

  --- (N.B. This is only an example for completeness

  ---                       and should NOT be used!)

  IF @Action LIKE "%Delete%"
  BEGIN

    --- Remove thesaurus entry

    DELETE FROM Thesaurus
      WHERE OID = Convert(int, @Pass1)
  END
GO

/****** Object:  Stored Procedure dbo.ListAllKeywords ******/
-- List all the terms in the thesaurus

CREATE PROCEDURE ListAllKeywords AS
    SELECT String From Thesaurus 
      GROUP BY String 
      ORDER BY String
GO

/****** Object:  Stored Procedure dbo.ListArticles ******/
--- List the Articles's (Required by AddURL.asp)

CREATE PROCEDURE ListArticles AS
  SELECT OID, Name
  FROM Entries ORDER BY Name
GO

/****** Object:  Stored Procedure dbo.ListIndexes ******/
--- Lits all indexes (Indented depending on position in hierachy)

CREATE PROCEDURE ListIndexes AS
  SELECT Indexes.OID, "Indent" =
  case
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 9, 1) > "0" 
         then ". . . . . . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 8, 1) > "0" 
         then ". . . . . . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 7, 1) > "0" 
         then ". . . . . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 6, 1) > "0" 
         then ". . . . . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 5, 1) > "0" 
         then ". . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 4, 1) > "0" 
         then ". . . . . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 3, 1) > "0" 
         then ". . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 2, 1) > "0" 
         then ". . . " + Indexes.Name
    when substring(CONVERT(char(9), Indexes.Base_Entry_OID), 1, 1) > "0" 
         then Indexes.Name
  end
  FROM Indexes ORDER BY Base_Entry_OID
GO

/****** Object:  Stored Procedure dbo.ListMatches ******/
--- List all matches for a specific query string (ignoring those

--- that are excluded by index or query string)

CREATE PROCEDURE ListMatches @Entry varchar(150) = "", 
       @Exclude varchar(150) = "", @Restrict int = 1 AS

  --- Find which index band to restrict the entries into

  DECLARE @Base int
  DECLARE @Top int
  SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID 
    FROM Indexes 
    WHERE OID = @Restrict

  --- If we are not excluding then match with @Entry only

  if @Exclude = ""
  BEGIN
    SELECT "Hyperlink"=
           case
             when MAX(URLs.URL) = "mailto:" then MAX(URLs.URL + Bookmark)
             when MAX(URLs.URL) = "ftp:" then MAX(URLs.URL + Bookmark)
             when MAX(Bookmark) > "" then MAX(URLs.URL + "#" + Bookmark)
             else MAX(URLs.URL)
          end,
          "Indent" =
          case
            when substring(CONVERT(char(9), MAX(Entries.OID)), 9, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) + 
                 " <font size=2><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 8, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) + 
                 " <font size=2><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 7, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + 
                 " <font size=3><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 6, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + 
                 " <font size=3><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 5, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + 
                 " <font size=4><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 4, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + 
                 " <font size=4><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 3, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + 
                 " <font size=5><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 2, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + 
                 " <font size=5>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 1, 1) > "0" then 
                 SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + 
                 " <font size=6><b>" + MAX(String) + 
                 " </b><i>" + Entries.Name + 
                 "</i></font>"
          end
      FROM Thesaurus, Entries, URLs
      WHERE (Entries.OID >= @Base AND Entries.OID <= @Top) 
       AND (@Entry Like "%" + String + "%" OR String Like "%" + 
       @Entry + "%" OR @Entry Like "%" + Name + "%" OR Name Like "%" + 
       @Entry + "%") AND Thesaurus.Entry_OID = Entries.OID 
       AND Entries.URL = URLs.OID
      GROUP BY Name
      ORDER BY MAX(Entries.OID)
  END

  --- If instread we are are excluding then match with @Entry and don't match @Exclude

  else

  BEGIN
    SELECT "Hyperlink"=
           case
             when MAX(URLs.URL) = "mailto:" then MAX(URLs.URL + Bookmark)
             when MAX(URLs.URL) = "ftp:" then MAX(URLs.URL + Bookmark)
             when MAX(Bookmark) > "" then MAX(URLs.URL + "#" + Bookmark)
             else MAX(URLs.URL)
          end,
          "Indent" =
          case
            when substring(CONVERT(char(9), MAX(Entries.OID)), 9, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) + 
                      " <font size=2><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 8, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 8, 2) + 
                      " <font size=2><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 7, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + 
                      " <font size=3><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 6, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 6, 2) + 
                      " <font size=3><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 5, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + 
                      " <font size=4><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 4, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 4, 2) + 
                      " <font size=4><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 3, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + 
                      " <font size=5><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 2, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + ":" + 
                      SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 2, 2) + 
                      " <font size=5>" + MAX(String) + " </b><i>" 
                      + Entries.Name + "</i></font>"
            when substring(CONVERT(char(9), MAX(Entries.OID)), 1, 1) > "0" 
                 then SUBSTRING(CONVERT(varchar(9), MAX(Entries.OID)), 1, 1) + 
                      " <font size=6><b>" + MAX(String) + 
                      " </b><i>" + Entries.Name + 
                      "</i></font>"
          end
      FROM Thesaurus, Entries, URLs
      WHERE (Entries.OID >= @Base AND Entries.OID <= @Top) 
       AND (@Entry Like "%" + String + "%" OR String Like "%" + 
       @Entry + "%" OR @Entry Like "%" + Name + "%" OR Name Like "%" + 
       @Entry + "%") AND (@Exclude NOT Like "%" + String + "%" 
       AND String NOT Like "%" + @Exclude + "%" AND @Exclude NOT Like "%" 
       + Name + "%" AND Name NOT Like "%" + @Exclude + "%") AND 
       Thesaurus.Entry_OID = Entries.OID AND Entries.URL = URLs.OID
      GROUP BY Name
      ORDER BY MAX(Entries.OID)
  END
GO

/****** Object:  Stored Procedure dbo.ListRelated ******/
-- List all articles that fall into index given (Order by name)

CREATE PROCEDURE ListRelated @IndexOID int = 1 AS

  --- Find the indexes band range

  DECLARE @Base int
  DECLARE @Top int
  SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID 
    FROM Indexes 
    WHERE OID = @IndexOID

  --- Return articles in this banding

  SELECT "Hyperlink"=
         case
           when URLs.URL = "mailto:" then URLs.URL + Bookmark
           when URLs.URL = "ftp:" then URLs.URL + Bookmark
           when Bookmark > "" then URLs.URL + "#" + Bookmark
           else URLs.URL
         end
         , "Indent" =
         case
           when substring(CONVERT(char(9), Entries.OID), 9, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 8, 2) + 
                     " <font size=2><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 8, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 8, 2) + 
                     " <font size=2><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 7, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + 
                     " <font size=3><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 6, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 6, 2) + 
                     " <font size=3><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 5, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + 
                     " <font size=4><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 4, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 4, 2) + 
                     " <font size=4><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 3, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + 
                     " <font size=5><b>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 2, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + ":" + 
                     SUBSTRING(CONVERT(varchar(9), Entries.OID), 2, 2) + 
                     " <font size=5>" + Entries.Name + 
                     "</b></font>"
           when substring(CONVERT(char(9), Entries.OID), 1, 1) > "0" 
                then SUBSTRING(CONVERT(varchar(9), Entries.OID), 1, 1) + 
                " <font size=6><b>" + Entries.Name + 
                "</b></font>"
         end
    FROM Entries, URLs
    WHERE Entries.OID >= @Base AND 
          Entries.OID <= @Top AND Entries.URL = URLs.OID
    ORDER BY Entries.OID
GO

/****** Object:  Stored Procedure dbo.ListRelatedKeywords ******/
-- List the keywords that fall into a particular index (Order by name)

CREATE PROCEDURE ListRelatedKeywords @IndexOID int = 1 AS
  --- Find the index band range

  DECLARE @Base int
  DECLARE @Top int
  SELECT @Base = Base_Entry_OID, @Top = Top_Entry_OID 
                 FROM Indexes WHERE OID = @IndexOID
  
  --- List thesaurus terms in this range

  SELECT "Hyperlink"=
         case
           when URLs.URL = "mailto:" then URLs.URL + Bookmark
           when URLs.URL = "ftp:" then URLs.URL + Bookmark
           when Bookmark > "" then URLs.URL + "#" + Bookmark
           else URLs.URL
         end
         , Thesaurus.String
    FROM Entries, Thesaurus, URLs
    WHERE Entries.OID >= @Base AND Entries.OID <= @Top AND 
          Entries.OID = Entry_OID AND Entries.URL = URLs.OID
    ORDER BY Thesaurus.String, Entries.Name
GO

/****** Object:  Stored Procedure dbo.ListURLs ******/
--- List the URL's (Required by AddURL.asp)

CREATE PROCEDURE ListURLs AS
  SELECT OID, URL
  FROM URLs ORDER BY URL
GO

/****** Object:  Stored Procedure dbo.Search ******/
--- List indexes that are related to the phrase 

---    given in @Entry, but not those given in @Excluded

CREATE PROCEDURE Search @Entry varchar(150) = "", @Exclude varchar(150) = "" AS
  SELECT MAX(Indexes.OID), 
         "Indent" =
         case
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 9, 1) > "0" 
                then ". . . . . . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 8, 1) > "0" 
                then ". . . . . . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 7, 1) > "0" 
                then ". . . . . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 6, 1) > "0" 
                then ". . . . . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 5, 1) > "0" 
                then ". . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 4, 1) > "0" 
                then ". . . . . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 3, 1) > "0" 
                then ". . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 2, 1) > "0" 
                then ". . . " + MAX(Indexes.Name)
           when substring(CONVERT(char(9), MAX(Indexes.Base_Entry_OID)), 1, 1) > "0" 
                then MAX(Indexes.Name)
         end
    FROM Thesaurus, Indexes 
    WHERE (@Entry Like "%" + String + "%" OR String Like "%" + @Entry + 
           "%" OR @Entry Like "%" + Name + "%" OR Name Like "%" + @Entry + "%") 
           AND (@Exclude NOT Like "%" + String + "%" AND NOT String Like "%" + 
           @Exclude + "%") AND Indexes.Base_Entry_OID <= Thesaurus.Entry_OID 
           AND Indexes.Top_Entry_OID >= Thesaurus.Entry_OID 
    GROUP BY Indexes.OID
    ORDER BY MAX(Indexes.Base_Entry_OID)
GO

/****** Object:  Stored Procedure dbo.SearchSounds ******/
-- List all thesaurus terms that 'sound like' the words given in the phrase '@Entry'

CREATE PROCEDURE SearchSounds @Entry varchar(150) = "" AS
    SELECT String From Thesaurus 
      WHERE DIFFERENCE(@Entry, String) = 4 OR @Entry Like "%" + String + "%" 
                                           OR String Like "%" + @Entry + "%"
      GROUP BY String 
      ORDER BY String
GO
© . All rights reserved.