本文档是 Visual C# 教程 (切换到 Visual Basic 教程)
ASP.NET 2.0 中的默认站点地图提供程序从静态 XML文件中提取数据。虽然基于 XML 的提供程序适合于许多中小型 Web 网站,不过更大的 Web 应用需要更动态的站点地图。本教程中,我们将创建从业务逻辑层提取数据的定制的站点地图提供程序,业务逻辑层则从数据库中提取数据。
Part 2 步骤4 : 显示产品的详细信息最后一个页面 , 即ProductDetails.aspx , 显示所选产品的详细信息。打开 ProductDetails.aspx , 从Toolbox 将一个 DetailsView 拖到Designer 。将该 DetailsView 的 ID 属性赋值为 ProductInfo ,并清除其 Height 和 Width 属性值。从其智能标记将该 DetailsView 与一个名为 ProductDataSource 的新 ObjectDataSource 绑定,并配置该 ObjectDataSource 使用 ProductsBLL 类的 GetProductByProductID(productID) 方法获取数据。与步骤 2 和 3 中创建的 web 页面一样,将 UPDATE 、 INSERT 和 DELETE 选项卡的下拉列表赋值为 “(None)” 。 图12 : 配置 ObjectDataSource 使其 使用 GetProductByProductID(productID) 方法 Configure Data Source 向导的最后一个步骤提示输入 productID 参数源。由于该数据源于查询字符串 ProductID ,所以将下拉列表赋值为 QueryString ,将 QueryStringField 文本框赋值为 “ProductID” 。最后,单击 Finish 按钮完成向导。 图13 : 配置 productID 参数从ProductID Querystring 字段 提取值 完成Configure Data Source 向导后 ,Visual Studio 将在产品数据字段的 DetailsView 中创建相应的 BoundFields 和 CheckBoxField 。删除 ProductID 、 SupplierID 和 CategoryID BoundFields ,按照您的意愿配置剩余字段。经过一些美观的配置后,我的 DetailsView 和 ObjectDataSource 的声明式标记应如下所示:
为测试该页面 , 返回到Default.aspx ,并 单击 Beverages 类别的 “View Products” 。在饮料产品列表中 , 单击Chai Tea 的 “View Details” 链接。 这将转到 ProductDetails.aspx?ProductID=1 ,显示 Chai Tea 的详细信息(见图 14 )。 图14 : 显示出的 Chai Tea 的厂商、类别、价格及其它信息 步骤5 : 了解站点地图提供程序的内部运行机制站点地图在web 服务器的内存中表现为形成层次结构的SiteMapNode 实例集。必须只有一个根节点,所有的非根节点必须只有一个父节点,但所有的节点可有任意数量的子节点。每个 SiteMapNode 对象表示网站结构的一部分;这些部分通常有相应的 web 页面。因此,SiteMapNode 类 有 Title 、 Url 和 Description 等属性,用以描述 SiteMapNode 所对应部分的信息。还有一个 Key 属性,用以唯一标识层次结构中的各 SiteMapNode ,以及用于建立该层次结构的属性: ChildNodes 、 ParentNode 、 NextSibling 、 PreviousSibling 等。 图15 显示的是图 1 中的总体站点地图结构 ,只是其实现细节提供得更细化 了。 图15 : 每个 SiteMapNode 都提供 Title 、Url 、Key 等属性 可通过 System.Web 命名空间 中的 SiteMap 类 来 访问站点地图。该类的 RootNode 属性返回站点地图的根 SiteMapNode 实例 ;CurrentNode 返回其Url 属性与当前请求页面的 URL 匹配的SiteMapNode 。该类由 ASP.NET 2.0 的导航 Web 控件内部使用。 访问SiteMap 类的属性时 , 必须从某种永久介质中将站点地图结构序列化地传入内存。不过,站点地图序列化逻辑不是通过硬编码进入 SiteMap 类的。而是在运行时, SiteMap 类确定用于序列化的站点地图提供程序。默认状态下,使用的是XmlSiteMapProvider 类 ,它从格式正确的 XML 文件中读取站点地图的结构。不过,我们稍微做点工作就可创建自己的定制站点地图提供程序。 所有的站点地图提供程序必须从 SiteMapProvider 类 派生,该类 包括站点地图提供程序所需的基本方法和属性 , 但忽略了许多实现细节。另一个类,StaticSiteMapProvider ,扩展了 SiteMapProvider 类并包含所需函数的更强实现。在其内部, StaticSiteMapProvider 将站点地图的 SiteMapNode 实例存储在一个哈希表中,并提供 AddNode(child, parent) 、 RemoveNode(siteMapNode) 和 Clear() 等方法,以对内部哈希表的 SiteMapNodes 进行添加和删除。 XmlSiteMapProvider 是由 StaticSiteMapProvider 派生的。 创建扩展StaticSiteMapProvider 的定制站点地图提供程序时 , 必须覆盖两个抽象方法 :BuildSiteMap 和GetRootNodeCore 。 BuildSiteMap ,正如其名称所示,负责从持久的存储中装载站点地图结构并在内存中构造它。 GetRootNodeCore 则返回站点地图的根节点。 在web 应用程序可使用站点地图提供程序之前 , 必须在应用程序的配置中进行注册。默认状态下,使用 AspNetXmlSiteMapProvider 名称注册 XmlSiteMapProvider 类。要注册到其它站点地图提供程序,在 Web.config 中添加如下标记:
name 值为提供程序指派一个易读的名称 , 而 type 则 指定完全合格的站点地图提供程序的类型名。创建了定制站点地图提供程序后,我们将在步骤 7 中探讨 name 和 type 值的具体值。 首次从SiteMap 类中访问和例示站点地图提供程序类 , 并在整个 web 应用程序生命周期都保持在内存中。由于多个并发的网站访问者只能调用一个站点地图提供程序的实例,所以必须保证提供程序的方法是 线程安全的。 基于性能和伸缩性原因 , 重要的是缓存内存站点地图结构 ,并在 每次调用BuildSiteMap 方法时返回缓存的结构而不用重新创建它。每用户对每页面的请求可多次调用 BuildSiteMap ,这取决于页面上使用的导航控件以及站点地图结构的深度。任何情况下,如果我们没有缓存 BuildSiteMap 中的站点地图结构,则每次调用时我们都必须从架构中重新提取产品和类别信息(这将导致对数据库的查询)。就如我们在前面的缓存教程中所讨论的,缓存的数据可能过时。为避免此情况,我们可使用基于时间或基于 SQL 缓存依赖项的期限。 注意 : 站点地图提供程序可选择覆盖 Initialize 方法 。Initialize 是在首次例示站点地图提供程序 , 并将如下 <add> 元素中 Web.config 的定制属性传递给提供程序时被调用的。<add name="name" type="type" customAttribute="value" />.这在允许页面开发者在不修改提供程序代码的前提下指定各种与站点地图有关的设置时有用。例如,如果我们直接从数据库中而不是架构中读取类别和产品数据,我们可能希望页面开发者使用 Web.config 中的数据库连接字符串,而不使用提供程序代码中的硬编码值。我们将在步骤 6 中建立的定制站点地图提供程序不会覆盖该 Initialize 方法。有关使用 Initialize 方法的示例,可参考 Jeff Prosise 的 在 SQL Server 中存储站点地图 文章。 步骤6 : 创建定制站点地图提供程序要创建从Northwind 数据库的 categories 和 products 构建站点地图的定制站点地图提供程序 , 我们需要创建一个扩展 StaticSiteMapProvider 的类。在步骤 1 中,曾要求您在 App_Code 文件夹中添加 CustomProviders 文件夹:在该文件夹中添加一个名为 NorthwindSiteMapProvider 的新类。在 NorthwindSiteMapProvider 类中添加以下代码:
我们从介绍该类的 BuildSiteMap 方法开始 , 它有一个 lock 语句 。lock 语句每次只允许单线程操作 , 因此序列化访问该代码 , 以避免出现两个并发线程。 类级别的SiteMapNode 变量 root 用于缓存站点地图结构。首次或底层数据修改后首次构建站点地图时, root 将为空,并将构建站点地图结构。构建过程中, root 被赋值为站点地图的根节点;这样,下次调用该方法时, root 就不会为空。因此,只要 root 不为空,站点地图结构就会返回,而无需重建。 如果root 为空 , 将从产品和类别信息中创建站点地图结构。站点地图的创建方法:先创建 SiteMapNode 实例,然后调用 StaticSiteMapProvider 类的 AddNode 方法来形成层次结构。 AddNode 执行内部记账、在哈希表中存储各种 SiteMapNode 实例。在开始构建层次体系之前,我们要调用 Clear 方法,以清除内部哈希表的元素。接下来,将 ProductsBLL 类的 GetProducts 方法以及返回的 ProductsDataTable 存储在局部变量中。 从创建根节点并将它赋值给 root 开始来构建站点地图。这里要用到 SiteMapNode 的构造函数 过载并通过该 BuildSiteMap 传递如下信息:
AddNode(root) 方法调用将 SiteMapNode root 添加到站点地图作为根节点。接下来 , 枚举 ProductsDataTable 的所有 ProductRow 。如果当前产品类别的 SiteMapNode 已经存在,则引用它。否则,创建该类别的新 SiteMapNode ,并调用 AddNode(categoryNode, root) 方法,将它作为 SiteMapNode root 的子节点进行添加。找到或创建了相应的类别 SiteMapNode 节点后,为当前产品创建 SiteMapNode ,并通过 AddNode(productNode, categoryNode) 将它作为类别 SiteMapNode 的子节点进行添加。注意,类别 SiteMapNode 的 Url 属性值为 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID ,而产品 SiteMapNode 的 Url 属性被赋值为 ~/SiteMapNode/ProductDetails.aspx?ProductID=productID 。 注意 : 对于CategoryID 为数据库 NULL 值的那些产品 , 统统被归类在 Title 属性赋值为 “None” 且 Url 属性赋值为空字符串的类别SiteMapNode 下。由于 ProductBLL 类的 GetProductsByCategory(categoryID) 方法目前不能返回那些具有 NULL CategoryID 值的产品,所以我决定将 Url 赋值为空字符串。同时,我想说明导航控件如何呈现缺乏 Url 属性值的 SiteMapNode 。鼓励您扩展该教程,使 “None” SiteMapNode 的 Url 属性指向 ProductsByCategory.aspx ,并只显示有 NULL CategoryID 值的产品。 构建了站点地图后 , 通过AggregateCacheDependency 对象 , 使用 Categories 和Products 表上的 SQL 缓存依赖项添加任意对象到数据缓存中。在前面的 使用 SQL 缓存依赖项 教程中,我们探讨了相关内容。然而,定制站点地图提供程序使用的是我们还未探讨的数据缓存的 Insert 方法过载。该过载接受一个 delegate 作为输入参数,从缓存中删除对象时调用该 delegate 。特别地,我们传入一个新的CacheItemRemovedCallback delegate ,它指向在 NorthwindSiteMapProvider 类中详细定义的 OnSiteMapChanged 方法。 注意 : 站点地图的内存表示通过一个类级别的变量 root 缓存。由于只有一个定制站点地图提供程序类的实例,并且该实例在 web 应用程序的所有线程间共享,这个类变量充当缓存。 BuildSiteMap 方法也会用到数据缓存,但仅作为一种接收 Categories 或 Products 表的底层数据库数据变化通知的方法。注意,加入到数据缓存的值仅仅是当前的日期和时间。实际的站点地图 并不在数据缓存中。 BuildSiteMap 方法最后返回站点地图的根节点。 剩下的方法就比较简单易懂了。GetRootNodeCore 负责返回根节点。由于 BuildSiteMap 返回 root , GetRootNodeCore 仅返回 BuildSiteMap 的返回值。清除缓存条目时, OnSiteMapChanged 方法又将 root 赋值为 null 。当 root 又被赋值为空时,下一次调用 BuildSiteMap 时,将重新创建站点地图结构。最后,如果数据缓存中有日期和时间值,则 CachedDate 属性将返回这些值。页面开发者可以使用该属性来确定站点地图数据最近被缓存的时间。 步骤7 : 注册NorthwindSiteMapProvider为让 Web 应用程序使用步骤 6 中创建的NorthwindSiteMapProvider 站点地图提供程序 , 需要在Web.config 的 <siteMap> 部分中进行注册。具体来说,就是要在 Web.config 的 <system.web> 元素中添加下面的标记:
该标记说明了两件事 : 第一 , 它指明了内置AspNetXmlSiteMapProvider 是默认的站点地图提供程序 ; 第二 , 它注册了步骤 6 中创建的定制站点地图提供程序 , 并取了一个易读的名字 :“Northwind” 。 注意 : 对于那些位于 App_Code 文件夹的站点地图提供程序, type 属性的值就是类名。或者,在单独的 Class Library 项目中创建的定制站点地图提供程序,其编译文件放在 web 应用程序的 /Bin 目录下。此时, type 属性值将为 “Namespace.ClassName, AssemblyName” 。 更新完 Web.config 后,花点时间在浏览器中查看本教程的任何页面。注意,左边的导航界面仍然显示 Web.sitemap 中定义的区域和教程。这是因为我们把 AspNetXmlSiteMapProvider 作为默认的提供程序了。为创建使用该 NorthwindSiteMapProvider 的导航用户界面元素,我们需要明确地指出要使用 “Northwind” 站点地图提供程序。这一点我们将在步骤 8 中完成。 步骤8 : 使用定制站点地图提供程序显示站点地图信息创建了定制站点地图提供程序并在 Web.config 中注册后 , 我们可以向 SiteMapProvider 文件夹中的 Default.aspx 、ProductsByCategory.aspx 和 ProductDetails.aspx 页面添加导航控件了。从打开 Default.aspx 页面开始,从 Toolbox 拖一个 SiteMapPath 到设计器中。 SiteMapPath 控件位于 Toolbox 的 Navigation 区域。 图16 : 向 Default.aspx 添加一个 SiteMapPath SiteMapPath 控件显示一个 breadcrumb , 表明当前页面在站点地图中的位置。在母版页和网站导航教程中,我们在母版页的顶部添加了一个 SiteMapPath 。 花点时间在浏览器中查看本页。在图 16 中添加的 SiteMapPath 使用的是默认的站点地图提供程序,从 Web.sitemap 获取其数据。因此, breadcrumb 显示为 “Home > Customizing the Site Map” ,同右上角的 breadcrumb 。 图17 :Breadcrumb 使用默认的站点地图提供程序 为了让在图16 中添加的 SiteMapPath 使用我们在步骤 6 中创建的定制站点地图提供程序 , 需要将其 SiteMapProvider 属性 赋值为 “Northwind” , 这个名字在 Web.config 中被我们分配给了 NorthwindSiteMapProvider 。不过, Designer 依然使用默认的站点地图提供程序,但是如果您在属性更改后通过浏览器访问该页面,您将看到 breadcrumb 现在使用的是定制站点地图提供程序。 图18 :Breadcrumb 现在使用定制站点地图提供程序NorthwindSiteMapProvider SiteMapPath 控件将在 ProductsByCategory.aspx 和 ProductDetails.aspx 页面上展示更具功能性的用户界面。在这两个页面里添加一个 SiteMapPath ,并将其 SiteMapProvider 属性赋值为 “Northwind” 。在 Default.aspx 页面,单击 Beverages 的 “View Products” 链接,然后单击 Chai Tea 的 “View Details” 链接。如图 19 所示, breadcrumb 包括当前的站点地图部分 (“Chai Tea”) 及其上一级: “Beverages” 和 “All Categories” 。 图19 :Breadcrumb 现在使用定制站点地图提供程序NorthwindSiteMapProvider 除SiteMapPath 外 , 还可以使用其它导航用户界面元素 , 如Menu 和 TreeView 控件。例如,本教程下载的 Default.aspx 、 ProductsByCategory.aspx 和 ProductDetails.aspx 页面都有 Menu 控件(见图 20 )。要深入了解 ASP.NET 2.0 中的导航控件和站点地图系统,请参见ASP.NET 2.0 快速入门 中的深入了解 ASP.NET 2.0 的网站导航特性 和使用网站导航控件 部分。 图20 : 菜单控件列出了所有的类别和产品 如本教程前面提到的 , 可通过SiteMap 类通过编码访问站点地图结构。下面的代码返回默认提供程序的根 SiteMapNode :
由于AspNetXmlSiteMapProvider 是我们应用程序的默认提供程序 , 所以上述代码返回在 Web.sitemap 中定义的根节点。为了引用其它站点地图提供程序,需要使用 SiteMap 类的如下 Providers 属性 :
其中 ,name 是定制站点地图提供程序的名称 ( 对于我们的web 应用 , 为 “Northwind” ) 。 要访问站点地图提供程序的某个成员属性 , 使用SiteMap.Providers["name"] 来获取提供程序实例 , 然后将其转换成适当的类型。例如,要在 ASP.NET 页面显示 NorthwindSiteMapProvider 的 CachedDate 属性,使用下面的代码:
小结ASP.NET 2.0 的站点地图特性包括一个 SiteMap 类、许多内置的导航 Web 控件,以及期望站点地图信息保存在 XML 文件的默认站点地图提供程序。为使用来自其它数据源(数据库、应用程序的架构、或远程 Web 服务等)的站点地图信息,我们需要创建定制站点地图提供程序。这涉及到要创建一个类,该类由 SiteMapProvider 类直接或间接派生。 在本教程中,我们介绍了如何创建一个定制站点地图提供程序,其站点地图基于从应用程序架构中获取的产品和类别信息。我们的提供程序扩充了 StaticSiteMapProvider 类,创建了一个 BuildSiteMap 方法来获取数据、构建了站点地图层次结构,并将最终的结构缓存在一个类级别的变量中。我们使用一个具有回传功能的 SQL 缓存依赖项,在底层 Categories 或 Products 数据发生改动时使缓存的结构无效。 快乐编程!
|