ATL服务器教程——每日贴士





5.00/5 (5投票s)
2001 年 4 月 19 日
19分钟阅读

177050

930
创建一个ATL服务器来生成随机提示,
背景
随着 VisualStudio.NET (VS7) 的日益普及,微软的各个团队为开发者提供了许多新功能。其中一项新功能称为“ATL Server”。ATL Server 使之前 ISAPI 扩展的开发变得更加容易,它允许您只将所需逻辑放入 ATL Server 对象中,并将结果注入到名为“Server Response File”(服务器响应文件)的调用文件中,这种文件扩展名为“.SRF”。ISAPI 扩展与网站或 Web 应用程序级别的文件扩展名相关联,并在必要时或被调用时通过扩展名运行它。
我将从中获得什么
本文档讨论并引导您完成 ATL Server 应用程序的开发,您可以将其扩展或在 VS.NET Beta1 中参考。此外,您还可以学习有关智能指针、IXMLDOM* 接口、新的 ATL CString 类和_bstr_t 的知识。
开始之前
我假设您会 C++ 编程,并且了解 ATL 的基本知识,对 HTML、CSS 和 XML 有基本了解。此外,虽然代码相当简洁;我使用了一些约定,使应用程序开发的各个阶段更容易进行跟踪,并且不一定是我会这样做的方式。代码布局便于随着本文档的进展进行轻松跟踪。例如,在变量使用前进行声明。
ATL Server 基础知识
有关 ATL Server 架构的更多信息,请在 MSDN Online 中搜索“ATL Server”。其中一篇文章提供了概述,位于 此处。
ATL Server 是 ISAPI 扩展的下一步。ATL Server 由 ISAPI 扩展调用以处理服务器响应文件 (.SRF)。当遇到 SRF 文件时,ISAPI 扩展会将其传递给应用程序 DLL(在我们的例子中是 TipOfTheDay.dll),然后该 DLL 会解析其中的令牌并处理找到的令牌(标签)。这对找到的每个标签都会进行处理,然后将其传递给默认的 ATL Server 处理程序或指定的处理程序,该处理程序会在调用时用处理程序写入 m_HttpResposne 流的响应替换标签。
SRF 文件中有几种标签可以使用。标识标签的方式是使用 {{[tag]}}
形式,其中“[tag]”被替换为某个标签名称。我将使用以下标签:
- 处理程序
处理程序告诉 ISAPI 扩展使用哪个 ATL Server DLL 和 DLL 中的处理程序,因为 ATL Server 实现可以包含多个处理程序。 - include
include 标签用于在此位置将另一个文件包含到 SRF 流中。这为您提供了与 ASP 页面中的 #include 指令相同的概念。ISAPI 扩展将解析包含的文件中的令牌,并在找到任何令牌时进行调用。与 ASP #include 指令不同,此文件独立于父文件进行处理,只有结果流会被插入到父文件中。因此,如果您使用来自 ATL Server 对象的任何处理程序,则需要定义一个处理程序令牌。我们将在下面的项目中展示 Header.SRF 部分。 - [tagname]
这可以是默认处理程序或命名处理程序中已知的标签。在我们的应用程序中,一个示例是“DisplayVersion”。发生的情况是,ATL Server 会被调用处理 tagname,然后调用该 tagname 的处理程序。实现然后可以在流中为该标签写入任何内容,如下所示。
ATL Server 示例 - 每日小贴士
创建项目
- 启动 Visual Studio.NET
- 在 VS 开始页上,单击“创建新项目”。或从 IDE 菜单中单击 **文件 | 新建 | 项目…**。您会看到一个类似于图 1 的对话框。

图 1: Visual Studio.NET 新建项目对话框
- 在“项目类型”树状视图中,选择“Visual C++ 项目”。“模板”列表视图将更新。
- 在“模板”列表视图中,选择“ATL Server Project”。
- 在“名称”编辑框中输入“TipOfTheDay”。
- 单击“确定”按钮。

图 2: ATL Server 项目向导概述
- ATL Server 项目向导将启动,并提供类似于图 2 的概述信息。
- 您会注意到向导是带有选项卡式方向的。通过使用鼠标在左侧文本上悬停,您会看到它们类似于超链接。
- 您可以检查不同页面以了解默认值的设置。
- 准备就绪后,单击“完成”按钮以生成项目及其模板代码。

图 3: 向导生成代码后 TipOfTheDay 的解决方案资源管理器视图
- 在解决方案资源管理器中,您会在解决方案节点下看到 3 个树节点。一个是 ATL Server 项目,第二个是与 ATL Server 项目关联的 ISAPI 扩展项目,第三个是用于在目标计算机上部署您的 ATL Server 及其 ISAPI 扩展的部署向导。
- 展开 TipOfTheDay 解决方案根节点下的“TipOfTheDay”项目树节点。
- 然后展开项目下的“头文件”和“源文件”树节点。您会看到 ATL Server 项目关联了 2 个源文件和 2 个头文件。这些文件将是您实现编译逻辑和标签处理程序的的地方。
- 另外,请注意在同一项目下有一个“TipOfTheDay.srf”文件。如果您双击此树项,您将在主文档区域中看到 SRF 文件的内容。这是向导在创建项目时为您生成的模板。
初始构建和部署
现在是时候进行您的初始构建和解决方案部署了。
- 在解决方案资源管理器中右键单击 TipOfTheDay 解决方案节点,然后从上下文菜单中选择“部署解决方案”。这将确保所有解决方案项目都是最新的,并构建一个 MSI,其中将部署您的组件到您的 Web 服务器。您也可以重用它来将解决方案部署到另一个站点或服务器。
- 如果您认为系统在部署过程中挂起(它停留在同一个任务上,并且过去几分钟内进度没有变化),您可以尝试取消并重新部署解决方案。
- 如果在部署解决方案时遇到错误,请重建整个解决方案并再次部署。
- 前面两条评论可能会在后续构建中稍后发生,但现在是时候提到了。
访问部署
解决方案已构建并部署后,请查看向导为您生成的内容以及它在解析和处理应用程序后生成的内容。 [https:///TipOfTheDay] 该站点应显示图 4 中的内容。

图 4: TipOfTheDay 部署的站点访问
移除向导默认设置
向导为我们生成的内容只是一个基本的示例,而且相当无用。由于我们的应用程序不会使用已实现的 Hello Handler,因此我们将删除所有对其的引用。
- 从解决方案资源管理器中打开 TipOfTheDay 项目的 TipOfTheDay.h 头文件。
- 从 TipOfTheDay.h 头文件中删除以下行:
// Here is an example of how to use a replacement tag with the stencil // processor [ tag_name(name="Hello") ] HTTP_CODE OnHello(void) { m_HttpResponse << "Hello World!"; return HTTP_SUCCESS; }
- 现在打开 TipOfTheDay.srf 文件,并删除带有 hello token handler 的那一行。
This is a test: {{Hello}}<br>
- 保存您的解决方案。这是一个好主意,因为 IDE 仍然是 Beta 版,请保存您的工作以免丢失。
- 如果此时构建并部署解决方案,应用程序仍然可以正常工作,只是不会执行任何操作。现在比以前更少。
实现标签处理程序
我们将需要实现一些 ATL Server 标签处理程序。我们需要一个 DisplayVersion,它以一个字符串的形式给出应用程序的标题和版本。DisplayVersion 标签将用于内容文件的页面标题和页眉内容。我们还需要几个标签处理程序来加载一个随机提示,RandomTip。我们需要标签处理程序来获取随机提示的详细信息;标签名称是 TipCategoryMajor、TipCategoryMinor、TipSource、TipOfTheDay。我们还需要声明一些成员变量、一个私有方法,并进行一些初始化。此外,我们还需要一些额外的头文件。
必需的头文件
- 打开 TipOfTheDay 项目的 stdafx.h。在 TODO 行下添加
comdef.h
和msxml.h
的 include 语句。然后保存。
// TODO: reference additional headers your program requires here #include <comdef.h> // need this for _bstr_t and _com_ptr_t #include <msxml.h> // need this for our tips XML file to parse it.
成员变量
- 打开 TipOfTheDay 项目的 TipOfTheDay.h。在 protected 部分注释下添加以下成员变量。然后保存。
// Put protected members here _bstr_t m_bsCategoryMajor; // category major: company, product, heading _bstr_t m_bsCategoryMinor; // category minor: division, technology, {optional} _bstr_t m_bsTipOfTheDay; // Tip Of The Day: Text but could be substituted // with quote of the day _bstr_t m_bsSource; // Source: Who added the tip or came up with the // quote bool m_bInited;
私有方法
- 我们需要一个私有方法来确保用户在访问任何 Tip* 标签处理程序之前调用了 RandomTip。将 IsInited 方法添加到 TipOfTheDay.h 文件的 protected 部分,如下所示。
inline bool IsInited(bool bShowInstructions = false) { if (!m_bInited && bShowInstructions) { m_HttpResponse << "Call RandomTip before accessing any Tip* Methods"; } return m_bInited; }
在 ValidateAndExchange 中初始化
- 在同一个头文件中,将以下代码添加到 ValidateAndExchange 方法的 TODO 注释下,如下所示。然后保存。
HTTP_CODE ValidateAndExchange() { // TODO: Put all initialization and validation code here m_bInited = false; m_bsCategoryMajor = ""; m_bsCategoryMinor = ""; m_bsTipOfTheDay = ""; m_bsSource = "Unknown"; // Set the content-type m_HttpResponse.SetContentType("text/html"); return HTTP_SUCCESS; }
DisplayVersion
DisplayVersion 标签处理程序将应用程序名称和版本写入请求响应对象的输出流。将以下代码插入 TipOfTheDay 的头文件和源文件中,如下所示。
插入到TipOfTheDay.h
[ tag_name(name="DisplayVersion") ] HTTP_CODE OnDisplayVersion(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnDisplayVersion(void) { m_HttpResponse << "Tip Of The Day ATL Server 1.0"; return HTTP_SUCCESS; }
RandomTip
RandomTip 处理程序用于加载提示 XML 文件并从响应对象中随机获取一个提示显示给请求者。目前,我们将放置存根,稍后插入并检查此方法的实现。
插入到TipOfTheDay.h
[ tag_name(name="RandomTip") ] HTTP_CODE OnRandomTip(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnRandomTip(void) { m_bInited = true; return HTTP_SUCCESS; }
TipCategoryMajor
TipCategoryMajor
处理程序将提示所属的主要类别写入请求的响应对象流。在提示 XML 文件中,此字段的内容是可选的。
TipOfTheDay.h
[ tag_name(name="TipCategoryMajor") ] HTTP_CODE OnTipCategoryMajor(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnTipCategoryMajor(void) { if (IsInited()) { m_HttpResponse << static_cast<TCHAR*>(m_bsCategoryMajor); } return HTTP_SUCCESS; }
TipCategoryMinor
TipCategoryMinor 处理程序将提示所属的次要类别写入请求的响应对象流。在提示 XML 文件中,此字段的内容是可选的。
插入到TipOfTheDay.h
[ tag_name(name="TipCategoryMinor") ] HTTP_CODE OnTipCategoryMinor(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnTipCategoryMinor(void) { if (IsInited()) { m_HttpResponse << static_cast<TCHAR*>(m_bsCategoryMinor); } return HTTP_SUCCESS; }
TipOfTheDay
TipOfTheDay 处理程序将 XML 文件中为提示指定的 TipOfTheDay 文本写入请求的响应对象流。此字段在 XML 文件中是必需的,否则您将没有提示可供显示。请注意,标签的名称不必与处理函数匹配。您也可能注意到调用 IsInited 时传递的参数是 true
,这是为了确保如果未调用 RandomTip 方法,我们可以显示一个输出字符串,让内容文件的开发人员/测试人员知道应该调用它。
TipOfTheDay.h
[ tag_name(name="TipOfTheDay") ] HTTP_CODE OnTipInfo(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnTipInfo(void) { if (IsInited(true)) { m_HttpResponse << static_cast<TCHAR*>(m_bsTipOfTheDay); } return HTTP_SUCCESS; }
TipSource
TipSource 处理程序将解析文件中的 TipSource 标签替换为提示的来源,如果 XML 文件中未指定来源,则向响应流写入空字符串,并且不会显示任何内容。
插入到TipOfTheDay.h
[ tag_name(name="TipSource") ] HTTP_CODE OnTipSource(void);
插入到 TipOfTheDay.cpp
HTTP_CODE CTipOfTheDayHandler::OnTipSource(void) { if (IsInited()) { m_HttpResponse << static_cast<TCHAR*>(m_bsSource); } return HTTP_SUCCESS; }
更新 TipOfTheDay.SRF
在继续之前,请保存所有工作。现在我们已经实现了大部分的标签处理程序,我们需要更新 TipOfTheDay.SRF 文件来调用它们并将它们的结果显示在页面上。下面是更新后的 TipOfTheDay.SRF 文件应该是什么样子,新的处理程序使用和 HTML 是粗体的。在 TipOfTheDay.SRF 文件中,我们将 DisplayVersion 标签放在 TITLE 标签之间。我们不调用 RandomTip,因为我们还没有完全实现处理程序,这样我们就可以看到如果 RandomTip 在使用任何其他 Tip* 标签之前未被调用时会生成什么。然后我们创建一个表来存放我们的每日小贴士信息,在左上角显示 CategoryMajor 值,右上角显示 CategoryMinor 值,中间显示 Tip 文本,在 Tip 文本下方显示来源。
将 粗体 代码段插入TipOfTheDay.srf
<html>
{{handler TipOfTheDay.dll/Default}}
<head>
{{// Set the title of the page with the component's title string}}
<title>{{DisplayVersion}}</title>
</head>
<body>
<table align="center" cellspacing="0" cellpadding="4" width="80%">
<tr>
{{// Display the random tip product and technology associations}}
<td class="ProductTechnology" nowrap="true">{{TipCategoryMajor}}</td>
<td> </td>
<td class="ProductTechnology" nowrap="true"
align="right">{{TipCategoryMinor}}</td>
</tr>
<tr>
{{// Display the Tip of the day text information}}
<td COLSPAN="3" class="TipInfo">{{TipOfTheDay}}</td>
</tr>
<tr>
{{// Display the Source if any}}
<td COLSPAN="3" class="Source" nowrap="true"
align="right">{{TipSource}}</td>
</tr>
</table>
</body>
</html>
构建和部署(第二次)
重复“初始构建和部署”和“访问部署”部分下的步骤和说明。您应该看到类似于图 5 的内容。它仍然很单调,但我们会很快解决这个问题。

图 5: TipOfTheDay,基本处理程序已实现
外观和感觉
我们有一个应用程序,它在一个表格中,但我们还无法分辨。从我们刚刚实现的此代码可以看出,我们正在使用 class 属性。我们应该有一个样式表来给生成的页面带来外观和感觉。我们将添加一个样式表到 TipOfTheDay.SRF 文件并实现样式表源。
- 在解决方案资源管理器中,右键单击 TipOfTheDay 项目,然后从上下文菜单中选择 **添加 | 添加新项…**。
- 在“添加新项”对话框中,在左侧选择“Visual C++”类别,然后从“模板”列表视图中选择“样式表”。
- 在名称字段中键入“TipOfTheDay”,然后单击打开。一个名为“TipOfTheDay.css”的文件将被添加到您的项目中。
- 在解决方案资源管理器中单击“TipOfTheDay.css”树节点。然后查看属性窗口,并将 Content 属性设置为 True,请参见图 6。通过将 Content 属性设置为 true,这将告知项目在构建时需要将此文件添加到部署包中。
图 6: TipOfTheDay.CSS 属性,Content=True - 双击“TipOfTheDay.css”树节点以打开文件,并将下面的样式表信息插入到该文件中并保存所有内容。
TipOfTheDay.CSS
TABLE
{
border:solid 1px black;
}
TD
{
background-color:506C86;
color:FFFFFF;
}
TD.ProductTechnology
{
font-weight:bold;
}
TD.TipInfo
{
background-color:infobackground;
text-align:center;
color:000000;
}
TD.Source
{
background-color:506C86;
font-size:xx-small;
}
TD.PageHeader
{
font-size:x-large;
text-align:center;
}
A
{
color:FFFFFF;
}
接下来,我们需要更新 TipOfTheDay.SRF 文件以使用新的样式表。将粗体文本插入到 TipOfTheDay.SRF 文件中。并保存。
已将样式表添加到TipOfTheDay.SRF
<html>
{{handler TipOfTheDay.dll/Default}}
<head>
<link rel="stylesheet" type="text/css" href="TipOfTheDay.css">
{{// Set the title of the page with the component's title string}}
<title>{{DisplayVersion}}</title>
</head>
<body>
<table align="center" cellspacing="0" cellpadding="4" width="80%">
<tr>
{{// Display the random tip product and technology associations}}
<td class="ProductTechnology" nowrap="true">{{TipCategoryMajor}}</td>
<td> </td>
<td class="ProductTechnology" nowrap="true"
align="right">{{TipCategoryMinor}}</td>
</tr>
<tr>
{{// Display the Tip of the day text information}}
<td COLSPAN="3" class="TipInfo">{{TipOfTheDay}}</td>
</tr>
<tr>
{{// Display the Source if any}}
<td COLSPAN="3" class="Source" nowrap="true"
align="right">{{TipSource}}</td>
</tr>
</table>
</body>
</html>
构建和部署(第三次)
重复“初始构建和部署”和“访问部署”部分下的步骤和说明。您应该看到类似于图 7 的内容。现在它具有更好的外观和感觉。

图 7: TipOfTheDay,外观和感觉
实现每日随机小贴士
现在我们已经实现了标签替换处理程序、具有体面 UI 的 SRF 文件以及具有外观和感觉的 UI;让我们回到您的 TipOfTheDay.cpp 文件,插入 RandomTip 的实现,以从 Tips XML 文件加载提示并缓存所选提示的值。
- 首先,让我们向项目中添加另一个文件。这次是一个 XML 文件,用于存放我们的提示。我们将重复为 CSS 文件执行的完全相同的步骤,但选择并命名一个 XML 文件。为了方便起见,这些步骤将重复。
- 在解决方案资源管理器中,右键单击 TipOfTheDay 项目,然后从上下文菜单中选择 **添加 | 添加新项…**。
- 在“添加新项”对话框中,在左侧选择“Visual C++”类别,然后从“模板”列表视图中选择“XML 文件”。
- 在名称字段中键入“TipOfTheDay”,然后单击打开。一个名为“TipOfTheDay.xml”的文件将被添加到您的项目中。
- 在解决方案资源管理器中单击“TipOfTheDay.xml”树节点。然后查看属性窗口,并将 Content 属性设置为 True,请参阅图 6 作为参考。通过将 Content 属性设置为 true,这将告知项目在构建时需要将此文件添加到部署包中。
- 现在我们有了 Tips XML 文件,请双击“TipOfTheDay.xml”树节点,并使用以下架构插入您选择的提示:
TipOfTheDay.xml
文件架构,其中 Tip 及其子节点应为每个新提示重复。<?xml version="1.0"?>
<Tips>
<Tip>
<CategoryMajor>ATL Server</CategoryMajor>
<CategoryMinor>SRF Tags</CategoryMinor>
<TipText>When commenting your files being used by your ATL Server,
don't put tags in comments unless you mean to, as they will be
processed by the associated ATL Server handler</TipText>
<Source>Erik Thompson</Source>
</Tip>
</Tips>
现在我们有了一个包含 Tip(s) 的 XML 文件,让我们来实现 RandomTip 标签处理程序的核心。
- 首先保存您的项目。然后,双击“TipOfTheDay.cpp”开始编辑。
- 将以下代码行添加到 stdafx.h include 指令下方的源文件中,如下所示。time 的新 include 允许我们看到 srand 函数,以便我们选择的提示尽可能随机。智能指针声明使我们可以轻松使用 XMLDOM 接口。
#include <stdafx.h> #include <time.h> // need this to see the random function to get a random tip // Smart pointers that we will use when building the current random tip. _COM_SMARTPTR_TYPEDEF(IXMLDOMDocument, __uuidof(IXMLDOMDocument)); _COM_SMARTPTR_TYPEDEF(IXMLDOMElement, __uuidof(IXMLDOMElement)); _COM_SMARTPTR_TYPEDEF(IXMLDOMNodeList, __uuidof(IXMLDOMNodeList)); _COM_SMARTPTR_TYPEDEF(IXMLDOMNode, __uuidof(IXMLDOMNode));
- 现在滚动到 RandomTip 方法实现,我们将插入加载提示并存储其关联值的必要代码。
- 目前,我们的 RandomTip 实现只是将初始化标志设置为 true,让我们添加代码来打开我们的 TipOfTheDay.xml 文件。
- 我们需要做的第一件事是使用 CreateInstance 创建一个 XMLDOMDocument 对象。一旦我们有了对象,就可以进行下一步,尝试加载 XML 文件。
- 将下面的粗体代码添加到 OnRandomTip 方法中。
HTTP_CODE CTipOfTheDayHandler::OnRandomTip(void) { HRESULT hr = S_OK; IXMLDOMDocumentPtr ptrTipsFile = NULL; hr = ptrTipsFile.CreateInstance(CLSID_DOMDocument); if (SUCCEEDED(hr) && ptrTipsFile != NULL) { // More implemetation to come. ptrTipsFile=NULL; } m_bInited = true; return HTTP_SUCCESS; }
- 在拥有 XML 对象后,我们使用新的 ATL CString 类和 m_HttpRequest 对象构建 TipOfTheDay.xml 文件的物理路径。
- 然后我们尝试将 XML 提示列表加载到 XML 对象中。如果成功,我们将继续并尝试获取一个随机提示及其内容。
- 将下面的粗体代码添加到 OnRandomTip 方法中。
HTTP_CODE CTipOfTheDayHandler::OnRandomTip(void) { HRESULT hr = S_OK; IXMLDOMDocumentPtr ptrTipsFile = NULL; hr = ptrTipsFile.CreateInstance(CLSID_DOMDocument); if (SUCCEEDED(hr) && ptrTipsFile != NULL) { // build path to tip of the day xml file CString strPath; m_HttpRequest.GetPhysicalPath(strPath); strPath.Append("TipOfTheDay.xml"); // load document VARIANT_BOOL vbLoaded = VARIANT_FALSE; ptrTipsFile->put_async(VARIANT_FALSE); hr = ptrTipsFile->load(_variant_t(strPath), &vbLoaded); if (SUCCEEDED(hr) && vbLoaded == VARIANT_TRUE) { IXMLDOMElementPtr ptrDocRoot = NULL; hr = ptrTipsFile->get_documentElement(&ptrDocRoot); if (SUCCEEDED(hr) && ptrDocRoot != NULL) { // More implemetation to come. ptrDocRoot = NULL; } } ptrTipsFile=NULL; } m_bInited = true; return HTTP_SUCCESS; }
- 接下来,我们需要获取 XML 文档的根节点的子节点,这将是可用的提示。
- 从可用提示列表中,我们将获取提示的数量,并使用
rand()
方法从可用提示中选择一个随机提示。 - 一旦我们有了提示,我们就可以最终获取提示的属性值,供您关联的 Tip* 处理程序使用。
- 将下面的粗体代码添加到 OnRandomTip 方法中。
HTTP_CODE CTipOfTheDayHandler::OnRandomTip(void) { HRESULT hr = S_OK; IXMLDOMDocumentPtr ptrTipsFile = NULL; hr = ptrTipsFile.CreateInstance(CLSID_DOMDocument); if (SUCCEEDED(hr) && ptrTipsFile != NULL) { // build path to tip of the day xml file CString strPath; m_HttpRequest.GetPhysicalPath(strPath); strPath.Append("TipOfTheDay.xml"); // load document VARIANT_BOOL vbLoaded = VARIANT_FALSE; ptrTipsFile->put_async(VARIANT_FALSE); hr = ptrTipsFile->load(_variant_t(strPath), &vbLoaded); if (SUCCEEDED(hr) && vbLoaded == VARIANT_TRUE) { IXMLDOMElementPtr ptrDocRoot = NULL; hr = ptrTipsFile->get_documentElement(&ptrDocRoot); if (SUCCEEDED(hr) && ptrDocRoot != NULL) { IXMLDOMNodeListPtr ptrTipsList = NULL; hr = ptrDocRoot->get_childNodes(&ptrTipsList); if (SUCCEEDED(hr) && ptrTipsList != NULL) { long lTipCount = 0; hr = ptrTipsList->get_length(&lTipCount); if (SUCCEEDED(hr) && lTipCount) { IXMLDOMNodePtr ptrTip = NULL; // randomize and load tip of the day // seed random number generator srand( (unsigned)time( NULL ) ); hr = ptrTipsList->get_item(rand() % lTipCount, &ptrTip); if (SUCCEEDED(hr) && ptrTip != NULL) { // More implemetation to come. ptrTip = NULL; } } ptrTipsList = NULL; } ptrDocRoot = NULL; } } ptrTipsFile=NULL; } m_bInited = true; return HTTP_SUCCESS; }
- 最后,我们选择我们正在处理的节点为当前属性的 Tip 节点。
- 然后我们获取属性包含的值,并将其存储到标签处理程序稍后将用于将值写入页面请求者的关联成员变量中。
- 将下面的粗体代码添加到 OnRandomTip 方法中。
HTTP_CODE CTipOfTheDayHandler::OnRandomTip(void) { HRESULT hr = S_OK; IXMLDOMDocumentPtr ptrTipsFile = NULL; hr = ptrTipsFile.CreateInstance(CLSID_DOMDocument); if (SUCCEEDED(hr) && ptrTipsFile != NULL) { // build path to tip of the day xml file CString strPath; m_HttpRequest.GetPhysicalPath(strPath); strPath.Append("TipOfTheDay.xml"); // load document VARIANT_BOOL vbLoaded = VARIANT_FALSE; ptrTipsFile->put_async(VARIANT_FALSE); hr = ptrTipsFile->load(_variant_t(strPath), &vbLoaded); if (SUCCEEDED(hr) && vbLoaded == VARIANT_TRUE) { IXMLDOMElementPtr ptrDocRoot = NULL; hr = ptrTipsFile->get_documentElement(&ptrDocRoot); if (SUCCEEDED(hr) && ptrDocRoot != NULL) { IXMLDOMNodeListPtr ptrTipsList = NULL; hr = ptrDocRoot->get_childNodes(&ptrTipsList); if (SUCCEEDED(hr) && ptrTipsList != NULL) { long lTipCount = 0; hr = ptrTipsList->get_length(&lTipCount); if (SUCCEEDED(hr) && lTipCount) { IXMLDOMNodePtr ptrTip = NULL; // randomize and load tip of the day // seed random number generator srand( (unsigned)time( NULL ) ); hr = ptrTipsList->get_item(rand() % lTipCount, &ptrTip); if (SUCCEEDED(hr) && ptrTip != NULL) { IXMLDOMNodePtr ptrField = NULL; _bstr_t bsField; BSTR bstrText = NULL; bsField = "CategoryMajor"; hr = ptrTip->selectSingleNode(bsField, &ptrField); if (SUCCEEDED(hr) && ptrField != NULL) { ptrField->get_text(&bstrText); m_bsCategoryMajor = bstrText; SysFreeString(bstrText), bstrText = NULL; ptrField = NULL; } bsField = "CategoryMinor"; hr = ptrTip->selectSingleNode(bsField, &ptrField); if (SUCCEEDED(hr) && ptrField != NULL) { ptrField->get_text(&bstrText); m_bsCategoryMinor = bstrText; SysFreeString(bstrText), bstrText = NULL; ptrField = NULL; } bsField = "TipText"; hr = ptrTip->selectSingleNode(bsField, &ptrField); if (SUCCEEDED(hr) && ptrField != NULL) { ptrField->get_text(&bstrText); m_bsTipOfTheDay = bstrText; SysFreeString(bstrText), bstrText = NULL; ptrField = NULL; } bsField = "Source"; hr = ptrTip->selectSingleNode(bsField, &ptrField); if (SUCCEEDED(hr) && ptrField != NULL) { ptrField->get_text(&bstrText); m_bsSource = bstrText; SysFreeString(bstrText), bstrText = NULL; ptrField = NULL; } ptrTip = NULL; } } ptrTipsList = NULL; } ptrDocRoot = NULL; } } ptrTipsFile=NULL; } m_bInited = true; return HTTP_SUCCESS; }
在构建和部署之前,我们需要做的最后一件事是将 {{RandomTip}} 标签添加到 TipOfTheDay.srf 文件中。
将粗体行添加到TipOfTheDay.srf
<html>
{{handler TipOfTheDay.dll/Default}}
<head>
<link rel="stylesheet" type="text/css" href="TipOfTheDay.css">
{{// Set the title of the page with the component's title string}}
<title>{{DisplayVersion}}</title>
</head>
<body>
{{// See the TipOfTheDay}}
{{RandomTip}}
<table align="center" cellspacing="0" cellpadding="4" width="80%">
<tr>
{{// Display the random tip product and technology associations}}
<td class="ProductTechnology"
nowrap="true">{{TipCategoryMajor}}</td>
<td> </td>
<td class="ProductTechnology" nowrap="true"
align="right">{{TipCategoryMinor}}</td>
</tr>
<tr>
{{// Display the Tip of the day text information}}
<td COLSPAN="3" class="TipInfo">{{TipOfTheDay}}</td>
</tr>
<tr>
{{// Display the Source if any}}
<td COLSPAN="3" class="Source" nowrap="true"
align="right">{{TipSource}}</td>
</tr>
</table>
</body>
</html>
构建和部署(第四次)
重复“初始构建和部署”和“访问部署”部分下的步骤和说明。您应该看到类似于图 8 的内容。

图 8: TipOfTheDay,RandomTip 已实现。
引入其他 SRF 标签
服务器响应文件允许您包含其他 HTML 文件、文本文件和 SRF 文件来构建页面。当调用另一个 SRF 文件时,该文件会被解析并处理找到的令牌。结果是已处理的 SRF 文件内容会被注入回包含它的 SRF 文件中,替换包含标签的位置。我们将为 SRF 文件添加页眉和页脚。页眉是另一个 SRF 文件,它会添加一个带有大字体显示版本的表格栏。页脚将显示另一个表格,显示项目正在构建的版本以及如何联系我。
添加页眉
要将页眉添加到 SRF 文件,我们首先需要创建该文件并将其添加到我们的项目中。我们将遵循与 TipOfTheDay.css 和 TipOfTheDay.xml 文件相同的步骤。为了方便起见,这些步骤将重复。
- 在解决方案资源管理器中,右键单击 TipOfTheDay 项目,然后从上下文菜单中选择 **添加 | 添加新项…**。
- 在“添加新项”对话框中,在左侧选择“Visual C++”类别,然后从“模板”列表视图中选择“SRF 文件 (.srf)”。
- 在名称字段中键入“Header”,然后单击打开。一个名为“Header.srf”的文件将被添加到您的项目中。
- 在解决方案资源管理器中单击“Header.srf”树节点。然后查看属性窗口,并将 Content 属性设置为 True,请参阅图 6 作为参考。通过将 Content 属性设置为 true,这将告知项目在构建时需要将此文件添加到部署包中。
- 现在我们有了 Header 文件,请双击“Header.srf”树节点,并将下面的代码插入其中。
Header.srf
的实现{{handler TipOfTheDay.dll/Default}}
<table width="100%" align="center">
<tr>
<td class="PageHeader">{{DisplayVersion}}</td>
</tr>
</table>
<BR>
<BR>
<BR>
添加页脚
要将页脚添加到 SRF 文件,我们首先需要创建该文件并将其添加到我们的项目中。我们将遵循与过去 3 次相同的步骤。
- 在解决方案资源管理器中,右键单击 TipOfTheDay 项目,然后从上下文菜单中选择 **添加 | 添加新项…**。
- 在“添加新项”对话框中,在左侧选择“Visual C++”类别,然后从“模板”列表视图中选择“HTML 页面”。
- 在名称字段中键入“Footer”,然后单击打开。一个名为“Footer.htm”的文件将被添加到您的项目中。
- 在解决方案资源管理器中单击“Footer.htm”树节点。然后查看属性窗口,并将 Content 属性设置为 True,请参阅图 6 作为参考。通过将 Content 属性设置为 true,这将告知项目在构建时需要将此文件添加到部署包中。
- 现在我们有了 Footer 文件,请双击“Footer.htm”树节点,并将下面的页脚代码插入其中。
Footer.htm
的实现<br>
<br>
<br>
<table width="100%" align="center">
<tr>
<td align="left" width="50%">VisualStudio.NET Beta1, ATL Server
Example</td>
<td align="right">Created By:
<a href="mailto:erikt@radbytes.com">Erik Thompson</a></td>
</tr>
</table>
更新 TipOfTheDay.SRF
现在我们有了这些新的内容文件,我们需要在 TipOfTheDay.srf 文件中添加代码,让 ATLServer 处理响应并将它们注入到已处理的结果中。
将下面的粗体代码添加到 TipOfTheDay.srf<html>
{{handler TipOfTheDay.dll/Default}}
<head>
<link rel="stylesheet" type="text/css" href="TipOfTheDay.css">
{{// Set the title of the page with the component's title string}}
<title>{{DisplayVersion}}</title>
</head>
<body>
<!-- Header section-->
{{include Header.srf}}
<!-- Tip Of The Day section-->
{{// See the TipOfTheDay}}
{{RandomTip}}
<table align="center" cellspacing="0" cellpadding="4" width="80%">
<tr>
{{// Display the random tip product and technology associations}}
<td class="ProductTechnology"
nowrap="true">{{TipCategoryMajor}}</td>
<td> </td>
<td class="ProductTechnology" nowrap="true"
align="right">{{TipCategoryMinor}}</td>
</tr>
<tr>
{{// Display the Tip of the day text information}}
<td COLSPAN="3" class="TipInfo">{{TipOfTheDay}}</td>
</tr>
<tr>
{{// Display the Source if any}}
<td COLSPAN="3" class="Source" nowrap="true"
align="right">{{TipSource}}</td>
</tr>
</table>
<!-- Footer section-->
{{include Footer.htm}}
</body>
</html>
构建和部署(第五次)
让我们最后一次构建和部署项目。重复“初始构建和部署”和“访问部署”部分下的步骤和说明。您应该看到类似于图 9 的内容。

图 9: TipOfTheDay,最终外观
结论
ATL Server 是 ISAPI 的下一步。它为 ISAPI 带来了过去所没有的东西。过去 ISAPI 扩展包含逻辑、内容和 UI。现在 ATL Server 提供了两全其美。它们只包含逻辑,并允许部分内容和 UI 在 DLL 外部且可更新。它也是一个编译解决方案,与使用 ASP 相比,它为未运行 .NET 的系统提供了 Web 应用程序性能提升的途径。通过容纳 ASP 和 ASP 组件开发人员熟悉的 Response、Request 接口,但又通过编译对象为外部服务提供丰富的功能,微软在使 ISAPI 开发更容易方面做得很好。