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






4.47/5 (15投票s)
2004年11月14日
39分钟阅读

147442
对互联网技术进行总体概述,包括互联网本身、HTML 和 XML 是什么,使用 Web Forms,CGI/MIME,IIS ISAPI,ASP 以及通过 ODBC32、OLE DB、ADO 和 ASP.NET 创建数据库的前端 HTML。
引言
本文档旨在作为互联网/企业技术的总体概述,帮助新进入该领域的人员快速掌握。本文档的内容安排如下:
- 互联网概述
- HTML 和 XML 描述
- Web Forms
- CGI/HTTP/MIME 的基本了解
- ISAPI
- ODBC
- ActiveX
- ASP (Active Server Pages)
- OLE DB/ADO
- ASP.NET
- 一些示例
本文档最初由我本人于 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 浏览器可能会将其误解为注释的结束。 | |||||||||
文档版本信息 | <!doctype HTML PUBLIC
"-//W3C//DTD HTML 3.2//EN">
<html>
|
| |||||||||
标题 文档头部 |
<head>
<title>
Document Title
</title> |
| |||||||||
标题 头部元数据 |
<meta HTTP-EQUIV="Content-Type"
CONTENT="text/html;
charset=Windows-1251">
<meta HTTP-EQUIV="Keywords"
CONTENT="HTML,Tables,Forms">
</head> |
其他元数据包括文档描述、作者、关键字、自动文档刷新/重定向、发布/过期日期等……
| |||||||||
正文 | <body bgcolor=#ffffff
text=#000000
link=#0000ff vlink=#909090> |
<body> 文档正文部分的开始。背景/文本/超链接和已访问超链接的颜色在此处使用十六进制 RGB (#rrggbb) 表示法定义(#ffffff 表示白色背景)。 | |||||||||
表格
表格头部
|
<table>
<tr>
<th>
Tag
</th>
<th align=left>
Example
</th>
</tr>
|
添加另一个名为“Example”的列,该列的
| |||||||||
表格 表格行
|
<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> |
添加另一行
标签
| |||||||||
换行符 | <hr size=3>
<br> |
<hr> 放置一个水平规则(线段)
| |||||||||
列表
|
<ul>
<li>Bulleted</li>
<li>un-ordered</li>
<li>list</li>
</ul>
|
这些行演示了一个 HTML 无序项目符号列表。<ul> 列表项( 通过使用列表标签 ( | |||||||||
字体 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(统一资源定位符),第二个生成一封发送给我的新电子邮件。这两个锚点的
| |||||||||
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 文件)。
| |||||||||
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 控件,其国际 与 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> 样式的使用方式与我们在文字处理器中使用样式来设置各种文本属性(例如标题)的方式相同。用户定义的样式通过选择一个名称并加上句点 (.) 来设置,预设样式(例如 | |||||||||
文档页脚 | </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 type="text" size="20"
name="textbox" value="Text Box"> |
text 。此文本输入框用于生成名为 textbox 的参数。参数的初始 value 设置为 Text Box ,输入框的 size 为 20 个字符宽。 |
密码输入框
|
<input type="password"
size="20" name="password" value="Password"> |
password 。密码框的实现方式与文本框相同。本质上,区别在于输入到文本框中的任何字符都会显示为星号(*)。 |
多行(滚动)输入框
|
<textarea name="areabox" rows="3" cols="20">
Example of a multiple line input box (Text Area)
</textarea> |
<textarea> 。这个多行输入框(或文本区域)生成一个名为 areabox 的参数。初始值由开始标签和结束标签之间的文本设置,大小由 |
复选框
|
<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 个独立的参数,分别名为 check1 、check2 和 check3 。如果复选框被选中,其 请注意,第三个复选框被指定为初始 |
选项(单选按钮)
|
<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 是初始选择的选项( |
选项(列表)
|
<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。(注意:如果我们没有在
此处初始选项 |
多选(列表)
|
<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 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> 标签中定义的程序。此示例演示了如何生成一个附加参数(名为
|
隐藏 | <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 方法(默认方法)传递的数据被脚本作为命令变量读取(例如,通过 argc
和 argv[]
读取),而 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,它隐藏了两个类中的多线程工作:CHttpServer
和 CHttpServerContext
。
出于我们的目的,我们将编写官方称为 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]"
|
用于生成记录集的 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
对象的一些基本属性,您应该在研究附录示例之前熟悉它们……
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_StoredProcParam
和m_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_nFields
和 m_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 扩展代码包含十一个函数,其中四个可通过解析映射直接访问。
- 默认 - 关于框。(ISODBCExample.dll)
- 打开记录集 - 连接到 InternetPAL 数据库,从存储过程 ISAPIInterface 创建记录集并执行显示表函数。(ISODBCExample.dll?OpenRecordset)
- 关闭记录集 - 关闭记录集和数据库连接。(ISODBCExample.dll?CloseRecordset)
- 显示 - 此函数根据传递给它的参数 '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