本文基于 ASP.NET 2.0 的预发行版本,文中提供的所有信息将来都可能发生变化。
本文将讨论以下内容:
- 使用Web部件创建模块化的Web门户应用;
- 个人化特性和自定义特性;
- 将自定义用户控件作为Web部件使用;
- 创建一个个人化特性的提供程序;
现今门户应用非常流行,好的门户都有共同而显著的特点。那就是都会给访问者提供雅观的信息,并且这些信息都是通过模块化的、一致的、易于浏览的用户界面提供的。一些综合性的门户网站走得更远,它们甚至允许网站成员提供内容、上传文档以及个人化门户页面。
微软为 Windows Server 2003 平台增加了一个可扩展的门户应用框架,随之发布了一个 Windows SharePoint 服务 ,这个框架提供了门户应用框架必须的一些基本元素,其中包括站点成员的支持、内容和文档管理、使用 Web 部件以模块化的形式展示数据等等。
Web 部件提供了支持自定义特性和个人化特性的基础功能。在Windows SharePoint服务网站里面,通过配置站点,门户应用的用户能够添加、配置、删除Web部件,这样他们就能轻松地个人化或者定制页面了。基于Windows SharePoint服务的站点还提供了一种简便而且强大的方法扩展站点的功能,那就是:开发自定义Web部件。创建支持定 制特性和个人化特性的Web部件时,你只需要简单地在你的Web部件类里面增加一些属性以及设置几个特殊的标签就可以了。那些繁琐复杂的工作都由 Windows Sharepoint 服务的Web部件基础结构来完成,比如:序列化、存储和读取与站点自定义特性和成员个人化特性相关的数据。
ASP.NET 2.0 引入了一套Web部件控件集,这套控件集与 Windows SharePoint 服务提供的功能很相似,它们 被设计用来完成序列化、存储和读取站点自定义特性和成员个人化特性相关数据等功能。但是它们更独特和更灵活,它们与 SQL Server 或 Active Directory 不是紧耦合的。对于那些希望使用基于表单验证技术建立门户,或者不想受限于某一特定数据库解决方案的公司来讲,这无疑是一个好消息。
图1:使用模块化Web部件设计的一个示例门户应用
本文将向你展示一个用 ASP.NET 2.0 Web 部件开发的示例门户应用,其主要目的是让你了解为门户应用开发Web部件时 ,你将面临的一些重要的设计问题。首先,我们将着重介绍新的 ASP.NET 2.0 Web部件控件集涉及的一些基本概念和控件类型。例子参见图1。
Web部件基础
用来放置Web部件的页面,我们可以称之为Web部件页面。如图2所示,一个Web部件页面需要一个WebPartManager 控件(只能有一个)和一个或多个的 WebPartZone 控件。还可以包括一个 EditorZone 控件或者 CatalogZone 控件(不是必需的)。需要注意的是,在.aspx文件中,WebPartManager 控件的标签必须出现在与Web部件 基础结构相关的任何其它控件标签之前,比如:WebPartZone 控件、EditorZone 控件、CatalogZone 控件。为了更好地控制Web部件页面的布局和表现形式,你还可以在aspx文件中使用HTML表格,将不同的 zone 控件布局到不同的地方。
图2:一个Web部件页面的典型布局
我们先来看一个简单的Web部件页面例子,该页面包含 WebPartManager 控件和 WebPartZone 控件:
<asp:WebPartManager ID=" WebPartManager1" runat="server" /> <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1"> <ZoneTemplate> <!-- time to add a Web Part --> </ZoneTemplate> </asp:WebPartZone>
在页面里放置了一个 WebPartZone 控件之后,就可以使用Web部件定义来创建Web部件实例了。有二种不同的方法可以创建Web部件定义。第一种方法是创建一个从 WebPart 类继承的类,第二种方法是创建一个用户控件。 本文稍后的部分,我们将详细探讨这两种方法之间的不同。现在,我们先创建一个从WebPart类继承的简单的类(参见图3)。
每个Web部件实例都存在于一个页面特定的 WebPartZone 控件的某个索引位置上。一个 WebPartZone 控件可以包含多个Web部件。如图4所示,在 WebPartZone1 控件里面有二个Web部件实例。
图4:在一个特定zone控件中的Web部件
你可以通过编程方式和声明方式将Web部件添加到某个区(zone)中。稍后你还可以看到如何将Web部件添加到部件目录中。当你创建了一个 CatalogPart 控件以后,用户就可以在运行时将一个新的Web部件添加到WebPartZone控件中。
以编程方式将Web部件添加到 WebPartZone 控件里面的方法取决于需要添加的Web部件的类型。如果是一个从 WebPart 继承的类,需要以编程方式创建一个 该类的实例,然后调用 WebPartManager 类的 AddWebPart 方法。调用 AddWebPart 方法时,你需要传入的参数包括:Web部件的实例、 目标 WebPartZone 控件、以及Web部件在 WebPartZone 控件中的索引位置。代码如下:
// create Web Part instance from WebPart-derived class WebPart wp1 = new WingtipWebParts.HelloWorld(); WebPartManager1.AddWebPart(wp1, WebPartZone1, 0);
此即通过编程方式将Web部件添加到 WebPartZone。声明方式是在Web部件页面的.aspx文件中定义控件标签。当用户访问页面时,如果你希望某个Web部件出现在特定的 WebPartZone 控件中,你可以在 WebPartZone控件中添加一个 ZoneTemplate。代码如下:
<%@ Register Assembly="WingtipWebParts" Namespace="WingtipWebParts" TagPrefix="Wingtip" %> <asp:WebPartManager ID=" WebPartManager1" runat="server" /> <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1"> <ZoneTemplate> <Wingtip:HelloWorld runat="server" id="HelloWorld" /> </ZoneTemplate> </asp:WebPartZone>
别忘了给Web部件“打扮”一下,因为如果不这样的话,你的Web部件看起来会很乏味的。内联门户网应用需要的一个通用特性就是能够给站点“换肤”,根据访问者的喜好改变Web部件的 外观。过去,这意味着你要自己创建一套架构去支持改变控件呈现方式的功能,而这需要做大量的工作。ASP.NET 2.0 引入了“主题”的概念,其中包括一系列的风格属性和控件属性,这些属性可以应用到单个控件、整个页面甚至整个应用的全局范围。
为了让你的门户应用看起来显得优雅和专业,你需要定制所有 WebPartZone 控件、EditorZone 控件和 CatalogZone 控件的 外观。当你刚开始做这件事的时候,你会觉得这种工作非常乏味。因为你需要给Web部件、Editor部件以及Catelog部件的内容区、标题栏和动态菜单等等区域换肤和修改显示属性。幸运的是,新的ASP.NET 2.0 的主题特性可以将这些.aspx文件中有关界面工作的成果提取到可以重用的.skin文件和.css文件中。这篇文章的示例门户应用程序就是使用换肤和主题功能来定制Web部件的 外观的,你可以到MSDN杂志的网站上下载。
显示模式和页面范围
WebPartManager 控件的一个单一实例运行在每个Web部件页面中,它负责管理Web部件实例以及Web部件如何与WebPartZone控件交互。WebPartManager控件还提供了一个可编程接口,可以用来切换Web部件页面的显示模式,例如:你可以在以下 三种模式之间切换:浏览模式、设计模式和编辑模式。例如:要通过程序将当前页面切换到设计模式,你只需要添加一个链接控件,在这个控件的事件处理程序中将DisplayMode属性设置为DesignDisplayMode即可。代码如下:
WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode;
理解Web部件页面每种显示模式之间的区别是很重要的。默认情况下,Web部件页面处于浏览模式,此模式不允许用户修改任何的Web部件。当切换到设计模式的时候,用户就能够在WebPartZone内部或者不同的 区之间移动Web部件了。图5列出了所有的显示模式。
ASP.NET 2.0 Web部件控件会在后台生成所有必需的DHTML和Javascript代码,让用户在浏览器里可以进行拖放操作。在不支持必 需的DHTML和Javascript特性的浏览器里,除了拖放操作之外的所有其它编辑功能都可以使用。这意味着你不必担心要强制所有的客户使用Microsoft IE5.0或以上的浏览器。ASP.NET 2.0 Web部件控件还支持管理个人化数据的存储和获取,能够记住用户上一次将Web部件放在了什么地方。
除了支持使用代码切换显示模式之外,WebPartManager控件还提供了允许Web部件页面在用户范围和共享范围之间切换的方法。页面范围是指对Web部件的修改是自定义操作还是个人化操作。所有的用户都能看到自定义操作的结果,而只有用户自己才能看到个人化操作的结果。改变Web部件页面的范围可以通过调用WebPartManager 控件的 ToggleScope 方法来实现,代码如下:
WebPartManager1.Personalization.ToggleScope();
默认范围即用户范围,也就是说对Web部件的修改被记录为个人化操作,只能被当前用户看到。成功地调用ToggleScope方法会把Web部件页面改变到共享范围,这样的话,对Web部件的修改就会被记录为自定义操作。共享范围的作用是为了允许管理员或者站点设计人员统一修改Web部件页面中的Web控件,让所有用户都能看到这种改变。如果有冲突,个人化修改总是优先于全局自定义操作。
当前用户并不总是能够成功地进入共享范围。默认情况下,任何用户都没有这样的权限。只有在Web.config文件中用户被赋予了这样的权限才行。下面是一个例子,它允许所有拥有admin或者site_designer角色的用户可以进入共享范围和修改全局自定义数据:
<webParts> <personalization> <authorization> <allow roles="admin, site_designer" verbs="enterSharedScope" /> </authorization> </personalization> </webParts>属性和个人化
每一个Web部件对象都有一套标准的属性能够被自定义或者个人化。例如,每个Web部件都有一个Title属性,在被加入WebPartZone之后,还能够被自定义。EditorZone控件和一些Editor部件允许用户对Web部件属性做修改。
当用户将Web部件页面切换为编辑模式时,Web部件的菜单提供了一个编辑命令。在一个Web部件上调用编辑命令会显示出一个EditorZone控件,此控件是放在相关的 区控件中的。ASP.NET 2.0提供了一些内置的Editor部件,用来修改标准的Web部件外观、行为和布局。
通过添加一些能够被个人化的自定义属性,Web部件个人化配置数据能够被轻松地扩展。你只需要在Web部件类的定义里面添加这些属性,并且打上诸如:Personalizable、WebBrowsable、WebDisplayName之类的特性标签就可以了。当你完成这些之后,Web部件控件会帮助你存储和获取这些被个人化或者自定义的属性值。
当你创建某个个人化属性时,通常会同时定义一个私有字段,代码如下:
private bool _HR = true;
[Personalizable(PersonalizationScope.User), WebBrowsable, WebDisplayName(" Show HR News"), WebDescription(" Use this property to show/hide HR news")]
public bool HR { get { return _HR; } set { _HR = value; } }
如果你想允许你的用户像这样个人化某个属性,你只需要在当前页面的EditorZone控件中添加一个PropertyGridEditorPart部件就可以了。图6显示了当你这样做的时候,用户会看到什么:
图6 Editor 部件允许用户个人化 Web 部件
如果你定义的某个Web部件属性是字符串或者数字类型的,PropertyGridEditorPart 部件会提供一个文本框让用户修改属性值。如果这个属性是布尔类型的,PropertyGridEditorPart部件会提供一个 复选框,如图6所示。
图7给出了从 Windows SharePoint 服务Web组件开发中学来的一个极好的编程技巧:你可以定义一个基于枚举类型的个人化属性。
创建 WebBrowsable 以及基于枚举类型的个人化的属性,其真正价值在于 PropertyGridEditorPart 部件会生成一个包括所有可选属性值的下拉列表,正如图7中显示的 Timeframe 属性那样。对用户来说,这很方便,而且有助于保证用户选择一个有效的属性值。
关于 Timeframe 属性,还有一件事值得留意。它被加上了一个 Personalizable 特性,特性的值为PersonalizationScople.Shared。当属性像这样被定义为共享属性的时候,它只能被自定义,而不可以个人化。既然共享属性不能被个人化,当当前页面在用户范围的时候,PropertyGridEditorPart 部件不会显示这个属性,而只会在共享范围显示这个属性。
Web部件目录
我们已经见过了如何在 WebPartZones 控件中事先放入Web部件。你还可以用另外一种方法完成这个功能,那就是允许用户在运行时添加新的Web部件。通过使用 CatalogZone 控件和 CatalogParts 类型的部件,比如 :PageCatalogPart 和 DeclarativeCatalogPart 来达到这个目的。(参见图8)
当你用这个方法添加某个 CatalogZone 和 CatalogPart 之后,用户就可以在运行时动态添加Web部件了,界面就像如图9中所示的那样。
图9:CatalogZones允许用户动态添加Web部件
首先,你需要理解 PageCatalogPart 的用途。在设计显示模式或者编辑显示模式下,用户能调用Web部件的“关闭”命令。当用户关闭 某个Web部件时,Web部件和相对应的个人化或者自定义配置被保留下来,以便用户可以在以后再次添加该Web部件。因此,PageCatalogPart 控件显示所有已经被关闭的Web部件的列表,用户可以再次加到页面上去。
“关闭”命令与“删除”命令是不同的。“删除”命令在出现在编辑显示模式。当用户删除一个Web部件时,有关这个部件的所有相关信息,包括自定义和个人化数据,全部会被删除掉。
DeclarativeCatalogPart 部件能够以声明的方式添加Web部件。图8中的代码说明了怎样定义这个目录,其中使用了一个自定义名字的部件 WeatherWebPart。采用这种方法,你就能够给用户提供各种各样的Web部件了。
作为本文的补充,同时为了给你提供更多的有关建立Web部件页面的详细信息,我们建议大家去阅读 Stephen Walther 所写的“Introducing the ASP.NET 2.0 Web Parts Framework”一文 。文中提供了更详细的信息,以及使用 EditorZones 和 CatalogZones 建立Web控件页面的完整例子。Stephen 还讲了一些更高级的话题,包括 Verbs、Connections 和 Web 部件的导入导出等内容。
ASP.NET 2.0 门户应用开发
除了Web部件自身体系结构之外,在ASP.NET 2.0里面还有一些新的特性使得内部门户网站的开发更加有吸引力。正如文章前面所说,主题和换肤的引入使得风格属性可以方便直接地独立于门户页面,不用修改每个页面,就可以做 统一的风格改变。当然,更令人兴奋的是母版页(Master Pages)的引入。使用母版页,就可以将总体的 WebPartManager 控件和所有 WebPartZones 控件放在一个单独的模版页面,其它页面都可以继承这些基础外观和功能。在示例门户应用中,我们用到的一个有趣的技术就是在 母版页每个 WebPartZone 控件的ZoneTemplate 模版中都添加一个 ContentPlaceholder 控件。这样的话,使用这个 母版页的内容页面可以使用这个内容控件来添加其自己的Web部件,并且映射到相应的 ContentPlaceholder 控件中。
为一个门户网站设计母版页时,你必须考虑的一件事是如果给用户提供自定义特性。正如你已经看到的,根据页面中包含的不同类型的区控件,有几种不同的修改页面的自定义模式 供让用户选择。
其中一种给用户显示自定义选项的方法是将 WebPartManager 控件和一系列按钮(典型的是LinkButton)封装为一个用户控件,然后将这个控件放在 母版页中,就可以为网站中所有页面提供自定义选项。如果你的网站有多于一个的主控页面,用来给不同的页面提供不同的布局,封装为一个用户控件(我们可以叫它WebPartManagerPanel)也是很有用的。之前图1中给出的那个门户应用的菜单条就给出了一个示例的用户控件,可以显示出当前 WebPartManager 的显示模式和范围,还提供了一些 LinkButton 将页面改变 为 WebPartManager 控件支持的其中一种编辑模式。(这就是我们示例门户应用使用的用户控件)。
在你的 WebPartManagerPanel 控件中可以提供的另外一个有用的特性是可以根据当前用户和当前页面显示或者隐藏相应的显示模式菜单项。通过查看 WebPartManager 的 SupportedDisplayModes 这个collection属性中包含哪些支持的显示模式,就可以显示或隐藏相应的显示模式菜单项。例如,要找出当前页面是否支持CatalogDisplayMode,你应该写如下的代码:
if (WebPartManager1.SupportedDisplayModes.Contains( WebPartManager.CatalogDisplayMode)) { //enable catalog display mode LinkButton here... }
还应该注意,如果当前用户没有相应的权限,调用 ToggleScope 将会失败。所以通过代码判断一下是否显示或隐藏让用户进入共享范围的界面元素是个好主意,查询 WebPartManager 控件的 Personalization 属性的 CanEnterSharedScope 属性可以做到这件事。代码如下:
if (WebPartManager1.Personalization.CanEnterSharedScope) { // display UI element that allows user to enter shared scope }
本文附带的示例应用中的 WebPartManagerPanel 用户控件包含了一个完整的实现,它可以根据当前用户和当前页面的能力动态地调整 窗格的显示。
将用户控件作为Web部件
在使用 Windows SharePoint 服务创建Web部件的时候,最令人沮丧的一件事情就是你必须用代码去创建控件的整个界面,设计器一点帮不了忙。因为 许多Web部件都是一系列互相交互的服务器端控件组成的,在创建Web部件时,不能使用 Visual Studio 的设计器是一件很不幸的事情。一个显而易见的解决方法就是允许开发人员创建用户控件,并且可以作为Web部件使用。(一个叫 SmartPart 的第三方工具提供了在 Windows SharePoint服务中可以将用户控件作为Web部件使用)。
ASP.NET 2.0 Web部件解决了这个问题,它可以允许任何控件直接作为Web部件使用,不用修改或者包装这些控件。这不仅可以将用户控件 结合到Web部件集合中,而且还可以轻易地将现有 asp.net 页面中使用的那些自定义控件集成起来。
这种方法内部的工作原理是,如果一个标准控件(不是Web部件)被加入到 WebPartZone 控件中,系统会隐含地调用 WebPartManager.CreateWebPart 方法,这个方法会创建一个 GenericWebPart 类的实例,并且用 添加的那个控件去初始化这个实例。GenericWebPart 从基类 WebPart 中继承,提供了核心Web部件属性实现。当构建 GenericWebPart 控件的时候,它会将初始化的那个控件作为子控件加入。在页面呈现过程中,就像大多数复合控件那样,GenericWebPart自身不会在 响应缓存中输出任何内容,只是作为输出子控件内容的一个代理。最终结果是你可以在页面中的 WebPartZone 控件里面加入任何控件,不用担心它不会运行。例如,下面的页面定义了一个 WebPartZone 控件,里面包括一个用户控件和一个标准日历控件,在创建的时候,这两个控件都会被隐含地包装成为一个 GenericWebPart 类的控件。代码如下:
<%@ Register Src="webparts/CustomerList.ascx" TagName="CustomerList" TagPrefix="Wingtip" %> <asp:WebPartManager ID=" WebPartManager1" runat="server" /> <asp:WebPartZone ID="WebPartZone1" runat="server" HeaderText="Zone 1"> <ZoneTemplate> <Wingtip:CustomerList runat="server" id="CustomerList" /> <asp:Calendar runat="server" id="CustomerCalendar" /> </ZoneTemplate> </asp:WebPartZone>
和标准的Web部件一样,动态创建被 GenericWebPart 包装的控件也是可以的。如果是用户控件,首先,你必须调用 Page.LoadControl 来动态地载入和创建用户控件实例。其次,还必须显式地给这个控件设置一个唯一的ID。再者,你还必须调用 WebPartManager 对象的 CreateWebPart 方法去创建一个 GenericWebPart 类的实例来作为用户控件实例的包装。最后,将获得的 GenericWebPart 实例的引用作为参数传给 AddWebPart 方法,并且指定要加入的WebPartZone。代码如下:
// create Web Part instance from User Control file Control uc = this.LoadControl(@"webparts\CompanyNews.ascx"); uc.ID = "wp2"; GenericWebPart wp2 = WebPartManager1.CreateWebPart(uc); WebPartManager1.AddWebPart(wp2, WebPartZone1, 1);
这种技术的唯一缺点就是你无法控制Web部件的一些专用特性,因为你的控件不是从 WebPart 类继承的,而只有 GenericWebPart 是从 WebPart 类继承的。一旦你运行拥有由 GenericWebPart 包装的控件的页面,你马上就会很明显地发现一个现象,不想大多数Web部件,这些Web部件默认是无标题的,而且也没有相关的图标和描述信息。图10给出了一个 由 GenericeWebPart 控件包装的带有默认标题(无标题)和图标的示例用户控件。
图10 GenericWebPart
其中一种解决方法是,在你的用户控件中,增加一个Init事件处理例程。如果你的控件由 GenericWebPart包装(通过查询 Parent 属性的类型可以判断),你就应该在程序中设置 GenericWebPart 类的一些属性,代码如下:
void Page_Init(object src, EventArgs e) { GenericWebPart gwp = Parent as GenericWebPart; if (gwp != null) { gwp.Title = "My custom user control"; gwp.TitleIconImageUrl = @"~\img\ALLUSR.GIF"; gwp.CatalogIconImageUrl = @"~\img\ALLUSR.GIF"; } }
当你再次运行此页面时,一旦用户控件被 GenericWebPart 包装,对 GenericeWebPart 父控件的属性的修改会反映在包含你的控件的Web部件上。图11给出了新的设置过属性的用户控件,请注意标题和图标。
图11 标题和图标
另外一个更有吸引力的解决方案是直接在你的用户控件类里实现 IWebPart 接口。既然用户控件从来不直接查询Web部件的属性,因为那些信息是由 GenericWebPart 类处理的,这样做初看起来好像没什么帮助。幸运的是,GenericWebPart 类的设计者意识到这种需求,如果控件实现了 IWebPart 接口, 那么在 GenericeWebPart 类中实现属性就会自动委托所包装的控件。
所以定制某个用户控件的Web部件特性仅仅是实现 IWebPart 接口,并填充接口中定义的七个属性就可以了。图12中的代码给出了用户控件的 后台代码类的一个例子,实现了和之前我们动态修改 GenericWebPart 属性一样的结果。
你可能还会考虑给你的用户控件建立一个另外的基类,这个基类从 UserControl 继承,并且实现了IWebPart接口,然后就可以被你的门户应用中所有的用户控件所继承。我们在这篇文章的示例应用中就是这么做的。采用这种方法,你的用户控件就能在 它们的构造函数中初始化其所需的属性,其它的就由基类去控制了。图13给出了一个实现 IWebPart 接口的用户控件基类以及一个与之相对应的后台类的代码, 该用户控件使用这个基类设置标题和图标属性。
现在你拥有了创建用户控件的这么多的灵活性,你可能会问:当你拥有设计器支持的用户控件,同时还可以定制Web部件特性,那为什么还要创建自己自定义 的控件呢?实际上,有几个原因需要你这样做。其中一个原因是你不能给用户控件添加定义的动作(verbs)。如果需要那样做,你必须直接从 WebPart 继承,然后重写Verbs属性。当然,你也可以考虑在你的控件中实现 IWebEditable 接口。
另外一个原因是用户控件局限于应用程序的目录,除非你将.ascx文件从一个项目复制到另外一个项目的目录下,否则你不可能在多个Web应用程序中共享用户控件。另一方面,自定义Web部件类继承自 WebPart 类,能够被编译到一个可重用的dll里面, 并且部署到全局程序集缓存(GAC)。还有一点,通过自定义Web部件类,你还可以给你的控件写一个自定义的设计器,以改变在 Visual Studio 中默认的外观,而且你还可以在这个Web部件类被放在工具箱的时候,创建一个图标。图14提供了一个特性对比表,让你决定是选择自定义Web部件还是用户控件。
Web部件和个人化特性提供者程序
提供者程序是ASP.NET 2.0的一个新特性,这也是你能在这个版本中看到如此之多内置的功能完整的控件只需要很少的甚至不需要任何代码就能运行 的一个主要原因。提供者程序背后的基本思路是为某个特定的特性定义一套公共的与数据相关的任务,将那些任务聚集到一个抽象类声明中,该抽象类从公共的 ProviderBase 类继承。在本文探讨的个人化特性中,必须明确提供的数据相关任务包括:
- 为某个特定页面和用户保存Web部件的属性和布局;
- 为某个特定页面和用户装载Web部件的属性和布局;
- 保存常规Web部件属性和特定的页面布局( 用于常规定制);
- 加载常规Web部件属性和特定的页面布局(用于常规定制);
- 将某个特定页面和用户的Web部件属性和布局重置为其默认值 ;
- 将某个特定页面的Web部件属性和布局重置为其默认值(用于常规定制);
还有其它的一些属于个人化体系结构的附属特性也需要持久化存储的能力,但是基本上可以归结为以上六种需求。如果我们假设有一个类可以完成这六个动作,而且能够成功 地保存和恢复数据,那么当站点运行的时候,每个页面上的 WebPartManager 控件就能够使用那个类保存和恢复所有的个人化和自定义数据。定义这些方法的抽象类的名字叫 PersonalizationProvider 类, 默认情况下使用的一个具体的派生类是 SqlPersonalizationProvider 类。图15显示了代表我们定义的 六个功能的那三个方法。请注意,不论输入的 userName 参数是否为空,每个方法都能够完成用户个人化或者共享自定义数据的功能。
所有的个人化数据都保存为普通的二进制数据(byte[]),默认的 SqlPersonalizationProvider 类会将这些数据写入数据库中的一个image类型的字段。既然 ASP.NET 2.0 知道有一个类可以提供这些方法,它就能够在基础的控件集里面建立比以前更加多的逻辑。在我们的 案例中,每个使用Web部件的页面上的 WebPartManager类负责正确地调用当前的 PersonalizationProvider 类来序列化和恢复每个页面的个人化设置。图16展示了 EditorZone 控件与默认的 SqlPersonalizationProvider 类是如何交互的。
图16 交互
你使用 ASP.NET 2.0 越多,对该提供者架构的例子了解就会越多。比如其中有成员提供者、角色管理提供者、站点地图提供者、站点监控提供者等等很多的提供者,所有的提供者程序都定义了一个相似的核心方法集与控件交互。
修改个人化数据存储
和大多数ASP.NET 2.0中的提供者程序一样,默认的个人化提供者程序是面向 SQL Server 后台存储而实现的。如果不修改配置文件,默认的 SqlPersonalizationProvider 采用 SQL Server 2005 Express Edition 连接字符串,支持基于本地文件的数据库。这个连接字符串就像下面这样:
data source=.\SQLEXPRESS; Integrated Security=SSPI; AttachDBFilename=|DataDirectory|aspnetdb.mdf; User Instance=true
使用 SQL Server 2005 Express Edition 基于文件的数据库的一个优势就是它可以被动态创建,不需要用户任何附加的设置。这意味着你可以建立一个全新的站点,不用设置数据库就能启用个人化特性,也能够运行!当你最初与网站交互时,系统会在站点的 App_Data 目录中生成一个新的 aspnetdb.mdf 文件,并且用支持所有默认提供者程序所需的表和存储过程来初始化该数据库。
对于不需要扩展规模或者支持很多并发用户的小站点来说,这简直太好了。但是对于企业系统来说,需要将数据存储到某个被全面管理的、专用的数据库服务器上。幸运的是,修改 SqlPersonalizationProvider 使用的数据库是非常简单直接的。SqlPersonalizationProvider 的配置将连接字符串初始化为 LocalSqlServer,这意味着它会在配置文件的<connectionStrings>节中寻找名字为 LocalSqlServer 的配置项,使用相关的连接字符串去打开到数据库的连接。默认情况下,这个字符串就是你在前文所看到的,意味着它会写入一个本地的 SQL Server 2005 Express Edition .mdf 文件。要修改它,你必须首先清除掉 LocalSqlServer 连接字符串集合,在你的 Web.config 文件中重新设置一个新的连接字符串值。(或者你也可以修改机器范围的 Machine.config文件,去影响这台机器上的所有站点)以下是一个 Web.config 文件的例子,它将提供者数据库的值修改为指向一个本地的 SQL Server 2000 的实例:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/ V.2.0"> <connectionStrings> <clear /> <add name="LocalSqlServer" connectionString= "server=.;integrated security=sspi;database=aspnetdb"/> </connectionStrings> ... </configuration>
在修改生效之前,本地 SQL 服务器上必须有一个名为 aspnetdb 的数据库,里面有SqlPersonalizationProvider 所需的表和存储过程。随 ASP.NET 2.0 发行了一个名为 aspnet_regsql.exe 的工具,用它可以建立这个数据库。当以默认设置运行该程序时,它将创建一个名为 aspnetdb 的本地数据库,其中有所有提供者程序所必需的表和存储过程,或者你可以选择将这些表和存储过程安装到一个已有的数据库中。所有的表和存储过程的名字都会以“aspnet”开头,所以不太可能会与任何现有的表重复。
和所有 ASP.NET 2.0 的提供者程序一样,这种间接的模式提供了一种非常灵活的架构,使得不用修改任何页面或者Web部件,就可以将后端的数据存储完全替换掉。
创建自己的个人化提供者程序
为个人化提供者程序修改连接字符串的能力赋予了你一定程度的灵活性,但是在 SqlPersonalizationProvider 内部还是使用命名空间 System.Data.Sql.Client 的功能来存取数据。这意味着你必须使用 SqlServer 数据库。如果你需要将个人化数据保存到另外一种数据库中,或者可能是另外一种完全不同的数据存储中,你将不得不更进一步,创建你自己定制的个人化数据提供者。幸运的是,大多数困难的工作已经为你做好了,而且也易于使用。作为个人化数据存储到另外一种数据存储的例子,本文的示例门户网站有一个自定义提供者程序的完整实现,名为 FileBasedPersonalizationProvider 类,它将所有的个人化和自定义数据保存到应用程序 App_Data 目录下的一个本地二进制文件中。这个二进制文件的名称为每个用户和路径唯一生成,每个路径下还有一个唯一的通用的用户配置文件。
创建一个自定义的个人化数据提供者,你必须首先建立一个从 PersonalizationProvider 基类继承的新的类,然后重写所有从基类继承的抽象方法。图17中给出的类定义演示了如何做到这一点。
为了使你的提供者程序能够运作,其实只有两个重要的方法必须要实现:LoadPersonalizationBlobs 和 SavePersonalizationBlob。这两个方法完成了个人化数据的二进制序列化功能,当加载页面时,个人化架构会调用它们。当Web部件页面处于编辑、目录或者设计模式时,如果数据被修改了,个人化架构还会调用它们将数据写回。(典型情况下是基于一个特定的用户)。
在下载的示例代码中,SavePersonalizationBlob 的实现代码将 dataBlob 参数写入一个基于传入的用户名称和路径唯一命名的文件。相似地,LoadPersonalizationBlobs 的实现代码会查找这个文件(使用相同的命名方法),并返回一个用户个人化的或者共享的blob数据。如果传入的userName参数为空,这两个方法默认都会保存或者装载共享数据,如果不为空,就会保存或者装载用户个人化数据。图18给出了示例 FileBasedPersonalizationProvider 中这两个方法的实现代码,以及一对用来根据用户名和路径信息生成唯一文件名的 helper 方法。
一旦提供者程序完全实现了,通过个人化配置节中的提供者节,你就可以将这个程序注册为一个提供者程序。要想实际使用它,你必须在 Web.config 文件中定义它为默认的个人化提供者。下面是一个将我们自定义的基于文件的提供者程序作为默认提供者的例子:
<webParts> <personalization defaultProvider="FileBasedPersonalizationProvider"> <providers> <add name="FileBasedPersonalizationProvider" type="Wingtip.Providers.FileBasedPersonalizationProvider" /> </providers> </personalization> </webParts>
如果我们重新运行我们的网站,所有的个人化数据现在都会被存储到一个本地的二进制文件。显然这不是最好的解决方法,但是示例代码提供了一些思路,让你了解如何实现你自己的个人化提供者,不论你想基于什么样的后端存储都可以。图19给出了我们新的提供者是如何插入到整个的Web部件体系结构中去的。
图19 使用 FileBasedPersonalizationProvider
更进一步
到现在为止,你已经看到了如果使用 ASP.NET 2.0 和其新的Web部件控件来创建具有丰富特性的支持自定义和个人化的门户应用程序,而 ASP.NET 2.0 使得这一工作变得相当简单。也许这个架构的最重要的特性是可插件的能力。提供者架构使得将符合你的站点特性的个人化数据写入后端数据存储变得相对简单,而且也不会被绑定到一个特定的序列化实现和数据存储上。所以,现在就前进,大胆地使用ASP.NET 2.0 Web部件去创建可以自定义的站点吧!
如果你喜欢这篇文章,在ASP.NET 2.0门户应用中创建Web部件还有大量的东西需要学习。记得从MSDN杂志网站下载本文的示例代码。在 ASP.NET 2.0 QuickStart tutorials 中也有一些例子可以拿来研究。我们还推荐你去看看 Fredrik Normén的网志,里面有一些有关 ASP.NET 2.0 Web 部件的有趣的例子。
源代码下载:
WingtipPortal