作者:J.D. Meier
Microsoft Corporation
2000 年 1 月 24 日
简介
组件。有人喜欢它们,有人则害怕。害怕组件的人通常都能给您讲一个骇人的经历。让我们面对它:当开始在 ASP 下使用组件时,并不知道什么能伤害您。如果您摔倒了,那么站起来,自己拍干净,然后接着来。在这篇文章中,我将提供从实践中获得的一般指南,帮助您建立更好的基于组件的 ASP 解决方案。
为什么使用组件?
在我开始讨论组件指南之前,值得考虑将组件添加到 ASP 应用程序的价值。许多对组件不熟悉的开发人员总觉得一切都那么新鲜。组件可以为 ASP 应用程序带来以下种种益处:
封装功能和隐藏实现细节
可重用性(包括被不同客户机应用程序重复使用)
知识产权保护
可伸缩性(体现在允许将应用程序分布到多台计算机上)
配置和部署灵活性
性能(尤其在早期绑定是重要因素时)
访问系统,例如 Win32 API 调用或编程语言的任何其他底层功能
键入功能较强(“Visual Basic® 脚本编辑器 [VBScript]”的键入功能较弱,而且 JScript® 也不太好)
业务逻辑与用户界面分离,或者 Web 设计人员与 Web 开发人员分离
利益与付出同在。就增加开发过程的复杂性而论,创建组件解决方案可能更加昂贵。部署和疑难解答也可能变得更困难并成为现实因素。但是,不要让眼前的困难阻碍了长期的利益。如何知道付出的成本是否值得呢?请考虑下列方面:
现有的代码库是如何?
开发队伍的水平和经验怎样?
您对主服务器的控制范围如何?
为特定任务选择了什么样的工具和语言?
存在什么协同问题?
存在性能和可伸缩性的因素吗?
项目时间框架是什么?
谁会继续维护和支持该应用程序?例如,开发小组能介入和接管吗?
审查以上考虑的问题后,请考察您的假设。原型能迅速从设想中得出实际情况。
现在,对组件可能带来的益处有了一定的理解,让我们继续讨论。下面的指南将帮助您获得最大的益处。这些指南可能成为指引您顺利建立更稳定的、可升级的、性能更优的 ASP 组件应用程序的向导。
状态管理
建议
一般来说,在可能的场合尽量使用无状态的组件和无状态的 ASP 页面。组件不应需要状态从一个方法调用到下一个状态。将复杂的状态存储在数据库中。对于简单的数据、沿用 cookies、QueryString 或在页面之间传递数据的隐藏的表单字段。
为什么
服务器资源是有限的。维护组件中的状态意味着应用程序在资源冲突和并发问题时将消耗宝贵的资源。无状态组件将帮助您避免这些问题。无状态组件还提供更多的部署选项,并增强在多个客户机上共享资源的能力。
常见的陷阱
开发人员常犯的错误是设计或使用需要维护状态的组件。请注意防止这种常用于桌面开发的思想。通常,具有桌面开发背景的开发人员会设计出依赖状态的组件。
详细信息
避免使用“ASP 会话”将提高服务器的性能,因为它简化了代码路径并减少了服务器资源的消耗。如果不使用“ASP 会话”,请通过“Internet 服务管理器” (请参阅“Internet 信息服务 [IIS]”文档)禁用“会话”状态。也可以在不需要“会话”的 ASP 页面中使用下面的标记禁用基于页的“会话”:
部署灵活性是另一个重要方面,尤其在 Web 区域中运行应用程序时。如果依赖“ASP 会话”, 则给定用户的请求绑定在指定的 Web 服务器上,因为“会话”状态是服务器专用的。在中间层和 Web 服务器中避免状态,并使用数据库,将使 ASP 请求可由区域中任何有效的 Web 服务器处理。因此, 您将减少竞争,提供更好的冗余,并允许更多的分布选项。
不使用“ASP 会话”而在页间传递数据的其他方法,请参阅下面的“知识库 (KB)”文章:
Q175167 HOWTO: Persisting Values Without Sessions(英文)
Q157906 HOWTO: Maintain State Across Pages with VBScript(英文)
Don Box 在 ActiveX® Q&A(英文) 一文中 还提出有关 MTS 状态管理的更多见解。
范围
建议
通常,请在页面范围内使用组件。页面范围的含义就是在同一页上创建对象、并使用它和释放它 — 所有这些均在同一页上操作。
标记为“双重”或“单元”的组件在页面范围内都能正常工作。仅在页面范围内使用“单元”模型组件,例如 VisualBasic 组件。如果需要在“应用程序”或“会话”中存储组件,则建议使用“双重”。可以在“会话”或“应用程序”范围内存储标记为“双重”的组件,但组件需要保证线程安全。
为什么
在页面范围内使用组件使服务器资源得以回收。释放资源将使并发问题减到最小程度,并允许可汇集的资源在客户机上共享。另外,页面范围组件避免了影响“会话” 或“应用程序”范围的对象的线程问题。在下面的“线程模型分类”中将详细讨论线程问题。
常见的陷阱
最常见的问题之一就是在“应用程序”范围内存储 Visual Basic 或其他“单元”模型对象。如果您尝试在“应用程序”范围内存储一个用 Server.CreateObject 创建的“单元”模型对象,可以看见下面的错误:
应用程序对象错误 'ASP 0197: 80004005'
不允许的对象使用
/VirDir/global.asa, line 7
不能将带有单元模型行为的对象添加到应用程序的内部对象。
但是,如果使用 <OBJECT> 标记在“应用程序”范围内存储“单元”模型对象,就不会出现运行错误。相反,对象将创建在指定的“单线程单元 (STA)”线程上,并且所有调用都汇集到那个线程 — 而且是连续地。原因是没有复选该组件的线程模型。很遗憾,在运行时出现了问题。
另一个常见问题是在“会话”范围存储“单元”模型对象,该“会话”范围将用户会话绑定到指定的线程。这个行为严重影响服务器的性能。由于所有调用将连续地汇集到创建该对象的线程,因此从根本上影响了线程缓冲池的目的。
详细信息
有关详细内容,请参阅下面的 KB 文章:
Q243543 INFO: Do Not Store STA Objects in Session or Application(英文)
Q243548 INFO: Design Guidelines for VB Components Under ASP(英文)
分割服务
建议
将表达、业务和数据服务分离。业务组件应该实施业务规则。业务组件不应包含数据访问技术。那是数据层组件的任务。业务组件不应包含对 ASP 对象的引用。
ASP 提供表达服务。引用 ASP 的对象应该呈现为 HTML。这些对象能够依次调用对 MTS/COM+ 注册的业务对象。
为什么
将应用程序分割为单独的和截然不同的服务,有以下好处:
更便于组件的重用
支持 Windows DNA model(英文)
更好地孤立疑难问题
更灵活的部署选项(去掉服务的耦合允许在多台计算机上分布应用程序)
常见的陷阱
有一种我们称为“瑞士军刀”组件的常见问题。 该“瑞士军”组件将所有服务合成一体 (就像有螺丝锥、牙签等 17 种工具的小瑞士军刀)。 把不相关的服务组合到一个组件中, 使该组件很难使用、理解和维护。
容易掉入的另一个陷阱是从业务组件中引用 ASP。使 ASP 和业务逻辑耦合(通过使用 请求或响应对象,或在其内部构建 HTML),不仅限制不同的客户机重用您的组件,而且限制了横向的可伸缩性。引用 ASP 内置对象的对象应该与 Web 服务器在同一框围中。理想情况下,由于横向可伸缩性,业务组件可以分布在不同的框围中。可以直接在 ASP 脚本中提供表达服务,也可以建立呈现引用 ASP 内置对象的组件的 HTML,并将这些组件保持在 IIS 框围中。
详细信息
成功的设计模型可用作处理公共业务问题的模型。例如,处理“创建读更新删除(CRUD)” 操作的模型,可帮助您将应用程序分为几个截然不同的逻辑服务,即表达、业务规则和数据访问。
请参阅下文以获得更多的设计模型具体示例,可以在您自己的应用程序中模仿它:
Scalable Design Patterns(英文)
Simplify MTS Apps with a Design Pattern(英文)
FMStocks Application: Start Here(英文)
线程模型
建议
选择组件的范围还是选择组件的线程模型,哪种方法优先?两种方法都要考虑线程分支,除非决定在页面范围内使用“单元”或“双重”模型组件。(如果 Visual Basic 程序员不知道组件是哪种线程模型,则总是“单元”。)
如果需要在“应用程序”或“会话”中存储对象,则需要使用标记为“双重”的组件并聚集“自由线程编组程序 (FTM)”。
不要使用“单线程”组件并避免使用来自 ASP 的“自由线程”组件。
注意: 如果不小心, Visual Basic 可产生“单线程”组件。请确保在项目属性页的常规选项卡上将线程模型设置为单元线程。还要注意在相同选项卡上选定无人值守执行和保留在内存中选项。
为什么
如果您使用的是 Visual Basic,它是一种“傻瓜”开发环境。 Visual Basic 仅限于使用“单元”模型。假如 Visual Basic“单元”模型对象执行得非常良好,我不想对页面范围上的限制考虑太多。 Fitch 和 Mathers Stock 2000 破坏了对性能的任何预先想法。另外,由 ASP、SQL 和 Visual Basic 构建的许多现有网站,无时不刻都在证明页面范围的“单元”模型组件是可伸缩和执行的。
如果在标记为“双重”的组件上聚集 FTM,则可以不用任何编组或线程切换,便能在线程之间调用。如果标记为“双重”的组件没有聚集 FTM,ASP 将其视为“单元”线程对象 — 就像 Visual Basic 组件一样。请记住,如果计划利用“COM+ 对象池”,则不要聚集 FTM。 有关“对象池”的规则,请参阅“平台 SDK”文档。
“单线程”和“自由线程”组件运行在“系统”安全环境下。更糟的是,“单线程”组件会导致死锁。
常见的陷阱
也许最常见的陷阱就是使用了没有被设计为在 ASP 下运行的组件,如“单线程”组件。大多数开发人员陷入其中,是因为将桌面应用程序移向 ASP,或者使用了第三方的控件时。如果您不能确定组件的线程模型,可以检查组件的注册表项(但不能总依赖它)。
详细信息
有关线程模型及其对 ASP 的影响,请参阅下面的文章:
Don Box's Active Server Pages and COM Apartments(英文)
Agility in Server Components
另外,下面的 KB 文章提供了有关线程问题的详细内容:
Q243543 Single-Threaded Apartment Objects in Session or Application(英文)
Q243544 INFO: Component Threading Model Summary Under Active Server Page(英文)
Q150777 INFO: Descriptions and Workings of OLE Threading Models(英文)
安全性
建议
组件不应对它运行的用户环境做任何假设。不要访问用户专用信息,如 HKEY_CURRENT_USER,或桌面计算机的专用资源,因为这些对组件来讲是不可用的。应用程序也不要使用 SendKeys 或调用依赖用户界面的组件,执行通常需要桌面交互的操作,如打开对话框。
为什么
组件将运行在不同安全性的桌面上。首先,这表示应用程序不能打开对话框,并不能与其他 GUI 实用程序交互(例如,使用 SendKeys)。默认情况下,不允许 Inetinfo.exe 与桌面交互。不同的用户环境也会限制组件访问某些资源 — 主要是注册表的 HKEY_CURRENT_USER 部分。
常见的陷阱
常见的失误是引用 HKEY_CURRENT_USER 下的表项。例如,Visual Basic 的 GetSetting 和 SaveSetting 函数不能在 ASP 下使用,因为它们引用了 HKEY_CURRENT_USER 配置单元下的表项。下面的 KB 将讨论这个问题:
Q248348 PRB: SaveSetting and GetSetting Not Available in Visual Basic 6.0 Webclass (IIS Application)(英文)
当从 ASP 而不是从桌面客户机调用组件时,打印机、MAPI 信息和网络共享通常“失效”。
有关详细内容,请参阅下面的 KB 文章:
Q184291 PRB: COM Objects Fail to Print When Called From ASP(英文)
Q217144 INFO: Difficulties Using Net APIs in ISAPI and ASP COM Objects(英文)
Q207671 HOWTO: Accessing Network Files from IIS Applications(英文)
详细信息
有关安全性的几点考虑:
启用哪种 IIS 身份验证方法?
您的 Web 应用程序是进程内的还是进程外的?
如果组件以 MTS 或 COM+ 注册,它是在“服务器”上还是在库软件包中?
您正在调用本地 DLL、远程 DLL、本地 EXE、远程 EXE 吗?
有关安全性的详细说明超出了本文的范围。但是,由于这个主题的复杂性,下面的文章对从 ASP 组件角度理解问题有很大帮助:
Securing a Web-based Microsoft Transaction Server Application(英文)
Q172925 INFO: Security Issues with Objects in ASP and ISAPI Extensions(英文)
Q217202 PRB: CGI Applications and IIS OOP Applications May Fail(英文)
下文很好地概述了 IIS 如何处理安全性:
Authentication and Security for Internet Developers(英文)
Server.CreateObject 与 CreateObject
建议
使用 Server.CreateObject。如果正在使用 MTS/COM+ 库软件包,请使用 Server.CreateObject 来避免线程阻塞。
为什么
CreateObject 相当于通过脚本引擎调用 CoCreateInstance。如果使用 CreateObject 而不是 Server.CreateObject,将发生下面情况:
ASP 不能识别该对象。
OnStartPage/OnEndPage 页面方法没有调用。
ASP 不知道对象的线程模型。
Server.CreateObject 相当于 GetObjectContext.CreateInstance。这表示 ASP 清楚该对象并知道它的线程模型。另外,如果 ASP 页面是事务性的,则通过调用 Server.CreateObject 可使组件与 ASP 页面在同一事务中。(请注意,事务性的页面可能意味着可避免的业务规则与表达层的耦合。)
常见的陷阱
如果对象处于防火墙后面,可能需要调用 CreateObject。请参阅 Q193230 PRB: Server.CreateObject Fails when Object is Behind Firewall(英文) 以获得详细信息。
详细信息
虽然在 IIS 4.0 下面 CreateObject比 Server.CreateObject 快,但在 IIS 5.0 下性能是相同的。同样,如果正在使用 MTS/COM+ 库软件包/应用程序, Server.CreateObject 可防止线程阻塞。
传递参数
建议
声明 Out 参数为 Variant。在 Visual Basic 术语中,这表示按引用 参数应该为 Variant。按值传递的参数(In 参数)不限于 Variant,但必须与 Variant 兼容。
为什么
脚本客户机使用 Variant。 COM 服务器可使用指定的数据类型。当您将指定的数据类型按值传递给 COM 服务器时, COM 服务器可以毫无问题地接收。但除 Variant 外,其他按引用参数