UML软件工程组织

引入 ASP.NET Web 部件连接
作者:Sabine

本页内容
 创建用于 ASP.NET 2.0 应用程序的 Web 部件
 设计可连接的 Web 部件
 为一个 Web 部件连接计时
 定义静态 Web 部件连接
 命名连接点
 动态建立连接

当您开始使用 Microsoft® .NET Framework 2.0 和 ASP.NET 时,您会发现新的 Web 部件基础结构将一些非常强大的功能添加到了基础平台中。在 MSDN®Magazine 的 2005 年 9 月号中,我和 Fritz Onion 撰写了一篇关于对 Web 部件进行编程的文章,题为“ASP.NET 2.0: Personalize Your Portal with User Controls and Custom Web Parts”。在本月的专栏中,我要通过讨论 Web 部件连接的工作方式,扩充那篇文章中的信息。

对于本专栏,我假设您已经对 Web 部件的基本知识有所了解,例如,如何使用 WebPartManager 控件、Web 部件区域、编辑器、目录和持久性属性。如果您还不了解,建议您在继续阅读本文之前先阅读刚才提到的那篇文章。

创建用于 ASP.NET 2.0 应用程序的 Web 部件

您可以用两种方法创建 Web 部件。第一种方法涉及创建一个自定义的 Web 部件类,该类从 System.Web.UI.WebControls.WebParts 命名空间中定义的 WebPart 类继承。使用该方法时,将自定义的 Web 部件类打包到一个程序集 DLL 中通常是有意义的,因为这样可以提供对重用、版本控制和 Visual Studio® 2005 集成的更多控制。如果您对使用以前的 ASP.NET 版本生成自定义控件很熟悉,则许多相同的技术适用于将自定义的 Web 部件生成到 DLL 程序集中。

用于创建 ASP.NET 2.0 Web 部件的第二种方法涉及使用用户控件。虽然该方法不产生相同的重用和版本控制级别,但是它的确允许您使用 Visual Studio 窗体设计器来创建 Web 部件的用户界面部分。如果您想通过将用于用户输入、验证和数据绑定的控件拖放到设计界面上来创建应用程序,则该方法适合于您。当然,如果您已经花时间创建了一个您想用作 Web 部件的用户控件,它也是个可以采用的好方法。

当创建一个专门设计为 Web 部件的用户控件时,建议您实现 IWebPart 接口。这样,Web 部件后台的代码就可通过编程方式分配自己的几个内部 Web 部件属性,如它的 Title 和 TitleIconUrl。

本月专栏附带的代码示例使用一个名为 WebPartBase 的自定义基类,该基类从 UserControl 继承并实现 IwebPart。该基类的定义部署在 App_Code 目录中名为 WebPartBase.vb 的源文件中。每当您使用用户控件创建一个新 Web 部件时,只需在该代码隐藏文件中更改该基类以利用该技术:

Partial Class WebParts_Customers
 Inherits WebPartBase

Sub New()
Title = "Northwind Customer List"
TitleIconImageUrl = "~\img\Customers.gif"
End Sub

End Class

设计可连接的 Web 部件

使用 Web 部件连接,您可以使用户更轻松地形象化数据各项之间存在的关系。例如,Web 部件连接可以建模一个主-从方案,其中显示客户列表的 Web 部件连接到另一个显示当前所选客户详细信息的 Web 部件。图 1 的示例说明这种设计可能生成的用户界面外观。

Web 部件连接还可用于建模一对多关系。例如,显示客户列表的 Web 部件可以连接到另一个显示针对当前所选客户的所有定单的 Web 部件。

通常使用 Web 部件连接建模的另一个方案是表单查询。在这种方案中,一个 Web 部件提供一个用户界面,该用户界面允许用户选择查询数据(如数据库表)时所用的搜索或筛选条件。然后,该 Web 部件连接到另一个显示查询结果的 Web 部件。Web 部件连接用于在运行查询前,将筛选条件从一个 Web 部件传递到另一个 Web 部件。

Web 部件连接基于提供者和使用者的概念。提供者 Web 部件通过一个编程接口为一个或多个使用者 Web 部件提供信息。提供者和使用者之间交换的信息可以是简单的数据项(如数字或字符串),也可以是较特殊的内容(如对一个复杂数组或自定义对象集合的引用)。

如果针对 Windows® SharePoint® Services 2.0 (WSS) 编写了 Web 部件,您可能已经熟悉它用于连接 Web 部件的模型。在 WSS 中,Web 部件只能使用一组预定义的接口对连接。这些接口对的示例包括 ICellProvider 和 ICellConsumer,以及 IRowProvider 和 IRowConsumer。

ASP.NET 2.0 中的 Web 部件连接模型比 WSS 中的旧式模型更容易、更灵活,因为您可以使用自己的自定义接口。这意味着您无需使用由 Microsoft 人员创建的接口定义。而且,您无需对接口对进行任何操作,它们必须由提供者和使用者实现。使用 ASP.NET 2.0,只有提供者需要实现一个接口。

要了解其工作方式,我们先在两个 Web 部件之间创建一个连接。对于我要在本月专栏中提供的示例,我决定使用 Northwind 数据库,因为它有一个 Customers 表和一个 Orders 表。这使我能为您展示如何针对主-从和一对多关系设计 Web 部件。有一点需要注意,如果使用 SQL Server® 2005,则在产品安装过程中并不安装该示例 Northwind 数据库。要安装它,您必须下载并运行 Microsoft Web 站点上可用的脚本(请参阅 Microsoft SQL Server 主页)。

现在,假设您要在一个显示客户列表的 Web 部件和一个显示当前所选客户的详细信息的客户 Web 部件之间建立一个 Web 部件连接,如图 1所示。显示客户列表的 Web 部件将扮演提供者的角色,而显示当前所选客户的详细信息的 Web 部件则作为使用者。在这种情况下,您希望提供者为使用者提供当前所选客户的 CustomerID 字段。

首先,创建一个名为 ICustomerIDProvider 的简单接口:

Public Interface ICustomerIDProvider
ReadOnly Property CustomerID() As String
End Interface

在本月专栏附带的代码示例中,我使用带有 SqlDataSource 和 GridView 控件的用户控件创建了提供者 Web 部件,以显示来自 Northwind 的客户。Web 部件源文件是 Customers.ascx 和 Customers.ascx.vb,如图 2 所示。

您可以看到,WebParts_Customers 作为提供者并实现用于该 Web 部件连接的接口。在本例中,WebParts_Customers 实现 ICustomerIDProvider 接口。虽然最常见的模式是提供者本身实现该连接接口,但是它不需要这么做。唯一的实际要求是,ConnectionProvider 方法返回指定接口的一个实例。因此,作为替代方案,该提供者 Web 部件可以返回一个实现该连接接口的 helper 对象。如果一个提供者 Web 部件具有多个接口类型相同的连接点,这通常是有必要的。

通过返回 GridView 控件的 SelectedDataKey 属性的值,WebParts_Customers 类实现 CustomerID 属性。GridView 控件已经进行了设置以便显示来自 Northwind Customers 表的记录,而且它还将 CustomerID 字段识别为 SelectedDataKey 值。

您应该注意到 WebParts_Customers 类有一个名为 GetCustomerProvider 的方法,该方法具有一个根据 ICustomerIDProvider 接口定义的返回类型。在这种情况下,由于 Web 部件本身实现所需的接口,因此 GetCustomerProvider 可以只返回对该类的当前实例的 Me 引用。您还要注意该方法已经使用 ConnectionProvider 属性进行了定义:

<ConnectionProvider("Customer ID Provider")>

WebPartManager 负责在运行时连接 Web 部件。当 WebPartManager 看到一个 Web 部件包含一个使用 ConnectionProvider 属性定义的方法时,它知道该 Web 部件公开一个连接点,因此可以作为提供者并且连接到使用者。当需要将两个 Web 部件连接在一起时,WebPartManager 将调用 GetCustomerProvider 方法获取对提供者 Web 部件的强类型引用。

定义一个提供者 Web 部件是否接受到使用者的多个连接是可能的。在某种情况下,一个提供者同时具有到多个使用者的连接是很有用的。在其他情况下,您可能想限制提供者,使它最多可以有一个到使用者 Web 部件的连接。默认情况下,提供者允许多个连接,而使用者则不然。要改变这一点,当您应用 ConnectionProvider 属性时,可以使用命名参数 AllowsMultipleConnections,如下所示:

<ConnectionProvider("Customer ID Provider", _
AllowsMultipleConnections:=False)>

既然您已经看到如何创建一个在提供者 Web 部件中公开连接点的方法,我们来看看这在使用者 Web 部件中是如何实现的。使用者 WebPart 通过提供一个使用 ConnectionConsumer 属性定义的方法来公开连接点。使用者的连接点方法与提供者的连接方法不同,因为它不定义返回值。相反,它获取一个用该连接的接口类型定义的参数:

<ConnectionConsumer("Customer ID Consumer")> _
Sub RegisterCustomerProvider(ByVal provider As ICustomerIDProvider)
... ' implementation
End Sub

请记住,提供者的连接点方法名称与使用者的连接点方法名称并不重要。唯一需要注意的是,每个方法分别使用 ConnectionProvider 属性和 ConnectionConsumer 属性进行定义。

现在,我们看看 WebPartManager 在运行时是如何建立该连接的。WebPartManager 调用提供者的连接点方法以获取对提供者对象的引用。接下来,WebPartManager 调用使用者的连接点方法,以便为它传递一个对提供者的强类型引用。

一旦 WebPartManager 完成它的工作之后,使用者 Web 部件就有一个返回到提供者 Web 部件的活动连接。此时,使用者可以使用该引用访问接口中定义的方法和属性,从而与提供者直接交互。但是,ASP.NET 小组建议使用者 Web 部件在 PreRender 阶段之前不应使用提供者接口上的方法和属性。具体说来,它们不应该在 方法本身中使用提供者接口上的方法和属性。原因是这些连接可能彼此依赖。您可能有一个连接到 ProviderConsumerWebPart 进而连接到 ConsumerWebPart 的 ProviderWebPart。在这两个连接建立之前,ConsumerWebPart 无法查询提供者接口,而且连接建立的顺序取决于框架。

图 3 显示 CustomerDetails.ascx.vb 中使用者 Web 部件的完整代码列表。您可以看到,使用者 Web 部件负责持续引用,以便可以跟踪到提供者的连接。该使用者 Web 部件包含一个名为 provider 的私有字段,该字段是根据 ICustomerIDProvider 接口定义的。

当 WebPartManager 调用 RegisterCustomerProvider 时,使用者获取传入的引用参数并将它分配给提供者字段。当提供者字段分得该引用后,使用者 Web 部件就可以与提供者 Web 部件直接交互。为连接设计该接口时,您应该添加将提供您所需交互的任何方法和属性。

在某些情况下,使用者 Web 部件可能设计为无论是否有到提供者的活动连接都正常工作。在这种设计中,当提供者字段有一个 Nothing 值时,您可能希望将临时代码添加到正常工作的使用者 Web 部件中:

If provider IsNot Nothing Then
... ' interact with provider
Else
... ' contingency code goes here if required
End If

为一个 Web 部件连接计时

当您开始设计支持连接的 Web 部件时,了解所涉及的计时是非常重要的。图 4 显示在 HTTP GET 期间运行提供者 Web 部件和使用者 Web 部件的页面的一小部分。虽然有更多在 HTTP POST 期间激发的页面级事件,但是连接建立时的计时则保持相同。

图 4 中显示的跟踪信息阐释了对于标准的 ASP.NET 2.0 页面级事件而言,每个连接方法何时激发。您应该能够从该跟踪信息中看到,WebPartManager 在页面级 LoadComplete 命令中将 Web 部件连接在一起。

需要牢记的是,当页面级事件 PreInit、Init、PreLoad 和 Load 执行时,Web 部件连接尚未建立。这意味着您绝不应该尝试访问使用者 Web 部件的处理程序方法(它绑定到这些事件之一)中的提供者。在尝试访问提供者 Web 部件之前,使用者 Web 部件中的代码必须等待 LoadComplete 事件执行完毕。

在本示例中,使用者 Web 部件处理 SqlDataSource 控件的 Selecting 事件,该事件在页面级 PreRender 事件中激发。此时,访问提供者并检索客户 ID 是安全的。

定义静态 Web 部件连接

既然您已经看到如何创建支持连接的 Web 部件,并且了解了所涉及的计时,现在该探究如何将它们实际连接在一起了。您将看到,可以将标记直接添加到一个 Web 部件页面定义中,以便建立一个静态 Web 部件连接。也可以在运行时通过代码或用户交互动态建立 Web 部件连接。首先,我要展示如何在两个 Web 部件之间创建一个静态连接,因为这是最简单的方法。

要在一个页面上的两个 Web 部件之间创建静态 Web 部件连接,需要将 StaticConnections 元素添加到 WebPartManager 标记中:

<asp:WebPartManager ID="WebPartManager1" runat="server">
<StaticConnections>
<asp:WebPartConnection ID="c1"
ProviderID="Customers1"
ConsumerID="CustomerDetails1" />
</StaticConnections>
</asp:WebPartManager>

要使该代码正常运行,名为 Customers1 的提供者 Web 部件和名为 CustomerDetails1 的使用者 Web 部件也必须在同一页面上的 Web 部件区域中静态定义和正确命名。

需要牢记的是,每个页面只有一个 WebPartManager 控件。然而,在许多涉及 Web 部件的应用程序设计中,您会发现将 WebPartManager 添加到用户控件或母版页很方便,原因是可以在许多页面上重用它。

当 Web 部件页面基于母版页,或者使用的是包含 WebPartManager 控件的用户控件时,您不能添加 WebPartManager 控件的第二个示例来定义 StaticConnections 标记。针对这些情况,ASP.NET 2.0 Web 部件控件集提供了 ProxyWebPartManager 控件。以下是关于如何使用它的示例:

<asp:ProxyWebPartManager ID="ProxyWebPartManager1" runat="server">
<StaticConnections>
<asp:WebPartConnection ID="c1"
ProviderID="Customers1"
ConsumerID="CustomerDetails1" />
</StaticConnections>
</asp:ProxyWebPartManager>

ProxyWebPartManager 控件的价值在于,它允许您在页面无法包含 WebPartManager 标记时,在页面级添加静态连接。在本专栏附带的示例页面 default.aspx 中,必须使用 ProxyWebPartManager 建立一个静态 Web 部件连接,原因是 WebPartManager 已经封装在名为 WebPartManagerPanel.ascx 的用户控件中。

命名连接点

在我目前生成的示例中,提供者 Web 部件和使用者 Web 部件之间的连接已经基于默认的连接点。但是,Web 部件连接方法提供一个命名连接点是可能的。要将一个命名连接点添加到提供者,您只需将第二个字符串参数添加到 ConnectionProvider 属性:

<ConnectionProvider("Customer ID Provider", "CustomerIDProvider")> _
Public Function GetCustomerProvider() As ICustomerIDProvider
Return Me
End Function

之所以使用命名连接点的一个原因是提供者或使用者可能有多个连接点,因此必须能区分它们。要将一个命名连接点添加到使用者 Web 部件,您可以将第二个字符串参数添加到 ConnectionConsumer 属性:

<ConnectionConsumer("Customer ID" & "Consumer", "CustomerIDConsumer")> _
Sub RegisterCustomerProvider(ByVal provider As ICustomerIDProvider)
Me.provider = provider
End Sub

当您开始使用命名连接点时,在页面级定义 StaticConnections 标记时,必须为 ProviderConnectionPointID 和 ConsumerConnectionPointID 提供两个额外的属性值。

动态建立连接

当您想将 Web 部件连接在一起时,有时却无法依赖静态 Web 部件连接。例如,如果您想连接通过自定义代码动态创建的 Web 部件,或者由使用 Web 部件目录将 Web 部件添加到页面的用户动态创建的 Web 部件,就会出现这种情况。

在无法使用静态 Web 部件连接时,您必须使用动态技术连接 Web 部件。为此,可以使用自定义代码,或者使用 ASP.NET 2.0 Web 部件控件集附带的 ConnectionsZone 控件。

让我们首先看一下创建两个 Web 部件并将它们动态连接在一起的自定义代码,如图 5 所示。该代码通过用户控件为提供者和使用者创建了 Web 部件实例,并将它们添加到宿主 Web 部件页面的现有 Web 部件区域。然后,该代码在它们之间建立一个连接。请注意,Web 部件和连接将保存为用户个性化信息的一部分,因此它们应该只添加到 WebPartManager 一次。如果您不想以这种方式保存 Web 部件和连接,也可以使用其他 API。


 图 6 连接显示模式

 您可以看到,图 5 中显示的技术需要使用 ProviderConnectionPoint 对象和 ConsumerConnectionPoint。通过调用 WebPartManager 提供的方法并传递那些命名连接点的字符串标识符,可以检索这些对象。

您和您的用户可以用来建立动态 Web 部件连接的另一种技术涉及到 ConnectionsZone 控件。要有效地使用该技术,您应该创建一个 Web 部件页面,其右侧有一个包含 ConnectionsZone 控件的任务窗格。当用户使该页面处于连接视图显示模式中时,Connect 命令将添加到公开连接点的每个 Web 部件的 Web 部件菜单中,如图 6 所示。

当用户选择 Connect 命令时,Web 部件页面会显示 ConnectionsZone 控件,并允许用户查看该页面上所有可连接的 Web 部件的所有兼容连接点(请参见图 7)。使用该技术,您可以将使用者连接到提供者。同样,您也可以将提供者连接到使用者。

请将有关 Ted 的问题和意见发送至 instinct@Microsoft.com。

Ted Pattison 是一位作家兼讲师,他通过自己的公司提供动手培训,他的公司名为 Ted Pattison Group。Ted 目前正在研究和撰写一本新书,着重于介绍 Windows SharePoint Services "V3" 和 Office 12 服务器技术。

 

版权所有:UML软件工程组织