生成自定义数据库驱动站点地图提供程序 (VB)
作者 :斯科特·米切尔
ASP.NET 2.0 中的默认站点地图提供程序从静态 XML 文件检索其数据。 虽然基于 XML 的提供程序适用于许多中小型网站,但较大的 Web 应用程序需要更动态的网站映射。 在本教程中,我们将生成一个自定义站点地图提供程序,该提供程序从业务逻辑层检索其数据,后者又从数据库检索数据。
简介
ASP.NET 2.0 s 网站地图功能使页面开发人员能够在一些永久性媒体(如 XML 文件中)中定义 Web 应用程序的网站地图。 定义后,可以通过命名空间中的System.Web
类或各种导航 Web 控件(如 SiteMapPath、Menu 和 TreeView 控件)以编程方式SiteMap
访问网站地图数据。 站点地图系统使用提供程序模型,以便可以创建不同的站点地图序列化实现并将其插入 Web 应用程序。 ASP.NET 2.0 附带的默认站点地图提供程序将站点地图结构保存在 XML 文件中。 回到母版页和网站导航教程中,我们创建了一个名为Web.sitemap
包含此结构的文件,并已使用每个新的教程部分更新其 XML。
如果站点地图结构相当静态,则基于 XML 的默认站点地图提供程序可正常工作,例如对于这些教程。 但是,在许多情况下,需要更动态的站点地图。 请考虑图 1 中显示的网站地图,其中每个类别和产品都显示为网站结构中的部分。 使用此网站地图,访问与根节点对应的网页可能会列出所有类别,而访问特定类别的网页将列出该类别的产品,并查看特定产品网页将显示该产品的详细信息。
图 1:网站地图结构构成的类别和产品(单击以查看全尺寸图像)
虽然此类别和基于产品的结构可以硬编码到 Web.sitemap
文件中,但每次添加、删除或重命名类别或产品时,都需要更新该文件。 因此,如果从数据库或理想情况下从应用程序体系结构的业务逻辑层检索结构,则站点地图维护将大大简化。 这样,随着添加、重命名或删除产品和类别,站点地图会自动更新以反映这些更改。
由于 ASP.NET 2.0 s 站点地图序列化是在提供程序模型中构建的,因此我们可以创建自己的自定义站点地图提供程序,从备用数据存储(如数据库或体系结构)中获取其数据。 在本教程中,我们将生成一个自定义提供程序,用于从 BLL 检索其数据。 让我们开始吧!
注意
本教程中创建的自定义网站地图提供程序与应用程序的体系结构和数据模型紧密耦合。 Jeff Prosise s Storing Site Maps in SQL Server and The SQL Site Map Provider you一直在等待 的文章中检查一种通用方法,用于在 SQL Server 中存储站点地图数据。
步骤 1:创建自定义网站地图提供程序网页
在开始创建自定义网站地图提供程序之前,让我们先添加本教程所需的 ASP.NET 页面。 首先添加名为 <Site.master
母版页相关联:
Default.aspx
ProductsByCategory.aspx
ProductDetails.aspx
此外,将 CustomProviders
子文件夹添加到 App_Code
文件夹。
图 2:为网站地图提供程序相关的教程添加 ASP.NET 页
由于本部分只有一个教程,因此无需 Default.aspx
列出该部分的教程。 相反, Default.aspx
将在 GridView 控件中显示类别。 我们将在步骤 2 中解决此问题。
接下来,更新 Web.sitemap
以包含对页面的 Default.aspx
引用。 具体而言,在缓存 <siteMapNode>
后添加以下标记:
<siteMapNode
title="Customizing the Site Map" url="~/SiteMapProvider/Default.aspx"
description="Learn how to create a custom provider that retrieves the site map
from the Northwind database." />
更新 Web.sitemap
后,请花点时间通过浏览器查看教程网站。 左侧的菜单现在包含唯一站点地图提供程序教程的项。
图 3:网站地图现在包括网站地图提供程序教程的条目
本教程的主要重点是说明创建自定义网站地图提供程序,以及配置 Web 应用程序以使用该提供程序。 具体而言,我们将生成一个提供程序,该提供程序返回一个站点地图,其中包含根节点以及每个类别和产品的节点,如图 1 所示。 通常,站点映射中的每个节点都可以指定 URL。 对于站点映射,根节点的 URL 将为 ~/SiteMapProvider/Default.aspx
,这将列出数据库中的所有类别。 站点地图中的每个类别节点都有一个指向 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID
的 URL,该 URL 将列出指定 categoryID 中的所有产品。 最后,每个产品站点地图节点将指向 ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID
,这将显示特定产品的详细信息。
首先,我们需要创建Default.aspx
和ProductsByCategory.aspx
ProductDetails.aspx
页面。 这些页面分别在步骤 2、3 和 4 中完成。 由于本教程的主要内容位于网站地图提供程序上,并且由于过去的教程介绍了如何创建这些多页母版/详细信息报告,因此我们将匆忙完成步骤 2 到 4。 如果需要在创建跨多个页面的主报表/详细信息报表时进行刷新,请参阅“ 跨两页 的母版/详细信息筛选”教程。
步骤 2:显示类别列表
Default.aspx
打开文件夹中的页面SiteMapProvider
,并将 GridView 从工具箱拖到设计器上,将其ID
设置为 Categories
。 从 GridView 智能标记中,将其绑定到名为 CategoriesDataSource
并配置的新 ObjectDataSource,以便它使用 CategoriesBLL
类的方法 GetCategories
检索其数据。 由于此 GridView 只显示类别,并且不提供数据修改功能,因此请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 “无”。
图 4:使用 GetCategories
方法配置 ObjectDataSource 以返回类别(单击以查看全尺寸图像)
图 5:将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为(无)(单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 将添加 BoundField forCategoryID
、CategoryName
、 Description
NumberOfProducts
和 BrochurePath
。 编辑 GridView,使其仅包含CategoryName
Description
和 BoundFields,并将 BoundField s HeaderText
属性更新CategoryName
为 Category。
接下来,添加 HyperLinkField 并将其定位为最左侧的字段。 将 DataNavigateUrlFields
属性设置为 CategoryID
,将 DataNavigateUrlFormatString
属性设置为 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}
。 将 Text
属性设置为“查看产品”。
图 6:将 HyperLinkField 添加到 Categories
GridView
创建 ObjectDataSource 并自定义 GridView 字段后,两个控件声明性标记将如下所示:
<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkField DataNavigateUrlFields="CategoryID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}"
Text="View Products" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL"></asp:ObjectDataSource>
图 7 显示 Default.aspx
通过浏览器查看时。 单击类别的“查看产品”链接会将你转到 ProductsByCategory.aspx?CategoryID=categoryID
,我们将在步骤 3 中生成该链接。
图 7:每个类别与视图产品链接一起列出(单击以查看全尺寸图像)
步骤 3:列出所选类别的产品
ProductsByCategory.aspx
打开页面并添加 GridView,将其命名ProductsByCategory
。 从智能标记中,将 GridView 绑定到名为 ProductsByCategoryDataSource
的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsBLL
类 s GetProductsByCategoryID(categoryID)
方法,并将下拉列表设置为 UPDATE、INSERT 和 DELETE 选项卡中的“无”。
图 8:使用 ProductsBLL
类方法 GetProductsByCategoryID(categoryID)
(单击以查看全尺寸图像)
“配置数据源”向导中的最后一步会提示输入 categoryID 的参数源。 由于此信息通过查询字符串字段 CategoryID
传递,因此从下拉列表中选择 QueryString,并在 QueryStringField 文本框中输入 CategoryID,如图 9 所示。 单击“完成”,完成向导。
图 9:对 categoryID 参数使用 CategoryID
Querystring 字段(单击以查看全尺寸图像)
完成向导后,Visual Studio 会将相应的 BoundFields 和 CheckBoxField 添加到 GridView 中,以获取产品数据字段。 删除除 UnitPrice
和 SupplierName
BoundFields 外ProductName
的所有内容。 自定义这三个 BoundFields HeaderText
属性,分别读取 Product、Price 和 Supplier。 将 UnitPrice
BoundField 格式化为货币。
接下来,添加 HyperLinkField 并将其移动到最左侧的位置。 将其 Text
属性设置为“查看详细信息”,将其 DataNavigateUrlFields
属性设置为“查看详细信息” ProductID
,并将属性 DataNavigateUrlFormatString
设置为 ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}
。
图 10:添加指向的视图详细信息 HyperLinkField ProductDetails.aspx
进行这些自定义后,GridView 和 ObjectDataSource 的声明性标记应如下所示:
<asp:GridView ID="ProductsByCategory" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsByCategoryDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkField DataNavigateUrlFields="ProductID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductDetails.aspx?ProductID={0}"
Text="View Details" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False"
SortExpression="UnitPrice" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
<SelectParameters>
<asp:QueryStringParameter Name="categoryID"
QueryStringField="CategoryID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
返回通过浏览器查看 Default.aspx
,然后单击饮料的“查看产品”链接。 这将带你到 ProductsByCategory.aspx?CategoryID=1
,显示属于饮料类别的 Northwind 数据库中产品的名称、价格和供应商(请参阅图 11)。 可以进一步增强此页面,以包含一个链接,用于将用户返回到类别列表页(Default.aspx
)和一个显示所选类别名称和说明的 DetailsView 或 FormView 控件。
图 11:显示饮料名称、价格和供应商(单击以查看全尺寸图像)
步骤 4:显示产品详细信息
最后一页 ProductDetails.aspx
显示所选产品详细信息。 打开 ProductDetails.aspx
并将 DetailsView 从工具箱拖到设计器上。 将 DetailsView 属性 ID
设置为 ProductInfo
并清除其 Height
和 Width
属性值。 从智能标记中,将 DetailsView 绑定到名为 ProductDataSource
的新 ObjectDataSource,将 ObjectDataSource 配置为从 ProductsBLL
类方法 GetProductByProductID(productID)
拉取其数据。 与在步骤 2 和 3 中创建的上一个网页一样,请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 (None) 。
图 12:将 ObjectDataSource 配置为使用 GetProductByProductID(productID)
方法(单击以查看全尺寸图像)
配置数据源向导的最后一步会提示输入 productID 参数的源。 由于此数据通过查询字符串字段 ProductID
,因此请将下拉列表设置为 QueryString,并将 QueryStringField 文本框设置为 ProductID。 最后,单击“完成”按钮以完成向导。
图 13:将 productID 参数配置为从 ProductID
Querystring 字段拉取其值(单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 将在 DetailsView 中为产品数据字段创建相应的 BoundFields 和 CheckBoxField。 ProductID
删除和 SupplierID
BoundFields,CategoryID
并在看到合适的情况下配置剩余字段。 经过一些审美配置,我的 DetailsView 和 ObjectDataSource 声明性标记如下所示:
<asp:DetailsView ID="ProductInfo" runat="server" AutoGenerateRows="False"
DataKeyNames="ProductID" DataSourceID="ProductDataSource"
EnableViewState="False">
<Fields>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
<asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False"
SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="Units In Stock"
SortExpression="UnitsInStock" />
<asp:BoundField DataField="UnitsOnOrder" HeaderText="Units On Order"
SortExpression="UnitsOnOrder" />
<asp:BoundField DataField="ReorderLevel" HeaderText="Reorder Level"
SortExpression="ReorderLevel" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Fields>
</asp:DetailsView>
<asp:ObjectDataSource ID="ProductDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProductByProductID" TypeName="ProductsBLL">
<SelectParameters>
<asp:QueryStringParameter Name="productID"
QueryStringField="ProductID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
若要测试此页面,请返回并单击 Default.aspx
“查看饮料”类别的产品。 从饮料产品列表中,单击柴茶的“查看详细信息”链接。 这会将你带到 ProductDetails.aspx?ProductID=1
,其中显示了柴茶的详细信息(请参阅图 14)。
图 14:显示柴茶供应商、类别、价格和其他信息(单击查看全尺寸图像)
步骤 5:了解站点地图提供程序的内部工作
站点地图在 Web 服务器内存中表示为构成层次结构的实例的 SiteMapNode
集合。 必须只有一个根,所有非根节点必须只有一个父节点,并且所有节点可能具有任意数量的子节点。 每个 SiteMapNode
对象表示网站结构中的一个部分;这些部分通常具有相应的网页。 因此,该SiteMapNode
类具有属性,Title
Url
并且Description
提供表示部分SiteMapNode
的信息。 还有一个唯一 Key
标识 SiteMapNode
层次结构中的每个属性,以及用于建立此层次结构 ChildNodes
、 ParentNode
、 NextSibling
等等 PreviousSibling
的属性。
图 15 显示了图 1 中的常规站点地图结构,但实现细节以更精细的细节绘制出来。
图 15:每个SiteMapNode
属性都具有“类似Title
”、“Url
Key
等”属性(单击可查看全尺寸图像)
可通过命名空间中的System.Web
类访问SiteMap
站点地图。 此类 s RootNode
属性返回站点映射的根 SiteMapNode
实例; CurrentNode
返回 SiteMapNode
其 Url
属性与当前请求页面的 URL 匹配。 此类由 ASP.NET 2.0 s 导航 Web 控件在内部使用。
SiteMap
访问类的属性时,它必须将站点地图结构从一些永久性介质序列化为内存。 但是,站点地图序列化逻辑不会硬编码到类中 SiteMap
。 相反,在运行时, SiteMap
类确定要用于序列化的站点地图 提供程序 。 默认情况下, XmlSiteMapProvider
将使用类,该类 从格式正确的 XML 文件读取站点地图结构。 但是,通过一些工作,我们可以创建自己的自定义网站地图提供程序。
所有网站地图提供程序都必须派生自 SiteMapProvider
该类,其中包括站点地图提供程序所需的基本方法和属性,但省略许多实现详细信息。 第二类 StaticSiteMapProvider
扩展了 SiteMapProvider
该类,并包含所需功能的更可靠的实现。 在内部,站点StaticSiteMapProvider
地图的实例存储在SiteMapNode
一个Hashtable
站点地图中,并提供如下AddNode(child, parent)
方法:RemoveNode(siteMapNode),
向Clear()
内部Hashtable
添加和移除SiteMapNode
。 XmlSiteMapProvider
派生自 StaticSiteMapProvider
。
创建自定义网站地图提供程序时 StaticSiteMapProvider
,必须重写两个抽象方法: BuildSiteMap
和 GetRootNodeCore
。 BuildSiteMap
,顾名思义,负责从永久性存储加载站点地图结构,并在内存中构造它。 GetRootNodeCore
返回站点映射中的根节点。
在 Web 应用程序可以使用站点地图提供程序之前,必须在应用程序配置中注册它。 默认情况下,该 XmlSiteMapProvider
类是使用名称 AspNetXmlSiteMapProvider
注册的。 若要注册其他站点地图提供程序,请将以下标记添加到 Web.config
:
<configuration>
<system.web>
...
<siteMap defaultProvider="defaultProviderName">
<providers>
<add name="name" type="type" />
</providers>
</siteMap>
</system.web>
</configuration>
名称值为提供程序分配可读名称,而类型指定站点地图提供程序的完全限定类型名称。 创建自定义网站地图提供程序后,我们将探索步骤 7 中名称和类型值的具体值。
站点地图提供程序类在首次从 SiteMap
类访问时实例化,并在 Web 应用程序的生存期内保留在内存中。 由于只能从多个并发网站访问者调用站点地图提供程序的一个实例,因此提供程序的方法 必须安全线程。
出于性能和可伸缩性的原因,请务必缓存内存中站点地图结构并返回此缓存结构,而不是每次调用该方法时 BuildSiteMap
重新创建它。 BuildSiteMap
根据页面上使用的导航控件和网站地图结构的深度,每个用户可以多次调用每个页面请求。 在任何情况下,如果我们不缓存站点地图结构,则每次调用站点地图结构 BuildSiteMap
时,都需要从体系结构重新检索产品和类别信息(这将导致对数据库的查询)。 正如我们在前面的缓存教程中讨论的那样,缓存的数据可能会过时。 若要解决此问题,可以使用基于时间或 SQL 缓存依赖项的过期。
注意
站点地图提供程序可以选择重写 Initialize
该方法。 Initialize
首次实例化站点地图提供程序并将分配给提供程序的任何自定义属性传递给元素中的Web.config
<add>
提供程序时调用,例如: <add name="name" type="type" customAttribute="value" />
如果希望允许页面开发人员指定与网站地图提供程序相关的各种设置,而无需修改提供程序代码,则非常有用。 例如,如果我们直接从数据库中读取类别和产品数据,而不是通过体系结构读取数据,则可能希望让页面开发人员指定数据库连接字符串,Web.config
而不是在提供程序的代码中使用硬编码值。 我们将在步骤 6 中生成的自定义网站地图提供程序不会重写此方法 Initialize
。 有关使用 Initialize
该方法的示例,请参阅 Jeff Prosise 的 SQL Server 文章中的 Storing Site Maps。
步骤 6:创建自定义网站地图提供程序
若要创建自定义网站地图提供程序,该提供程序从 Northwind 数据库中的类别和产品生成站点地图,我们需要创建一个扩展 StaticSiteMapProvider
的类。 在步骤 1 中,我要求你在文件夹中添加一个 CustomProviders
文件夹 App_Code
- 向名为 NorthwindSiteMapProvider
此文件夹的新类添加一个新类。 将以下代码添加到 NorthwindSiteMapProvider
类:
Imports System.Web
Imports System.Web.Caching
Public Class NorthwindSiteMapProvider
Inherits StaticSiteMapProvider
Private ReadOnly siteMapLock As New Object()
Private root As SiteMapNode = Nothing
Public Const CacheDependencyKey As String = "NorthwindSiteMapProviderCacheDependency"
Public Overrides Function BuildSiteMap() As System.Web.SiteMapNode
' Use a lock to make this method thread-safe
SyncLock siteMapLock
' First, see if we already have constructed the
' rootNode. If so, return it...
If root IsNot Nothing Then
Return root
End If
' We need to build the site map!
' Clear out the current site map structure
MyBase.Clear()
' Get the categories and products information from the database
Dim productsAPI As New ProductsBLL()
Dim products As Northwind.ProductsDataTable = productsAPI.GetProducts()
' Create the root SiteMapNode
root = New SiteMapNode( _
Me, "root", "~/SiteMapProvider/Default.aspx", "All Categories")
AddNode(root)
' Create SiteMapNodes for the categories and products
For Each product As Northwind.ProductsRow In products
' Add a new category SiteMapNode, if needed
Dim categoryKey, categoryName As String
Dim createUrlForCategoryNode As Boolean = True
If product.IsCategoryIDNull() Then
categoryKey = "Category:None"
categoryName = "None"
createUrlForCategoryNode = False
Else
categoryKey = String.Concat("Category:", product.CategoryID)
categoryName = product.CategoryName
End If
Dim categoryNode As SiteMapNode = FindSiteMapNodeFromKey(categoryKey)
' Add the category SiteMapNode if it does not exist
If categoryNode Is Nothing Then
Dim productsByCategoryUrl As String = String.Empty
If createUrlForCategoryNode Then
productsByCategoryUrl = _
"~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=" & _
product.CategoryID
End If
categoryNode = New SiteMapNode _
(Me, categoryKey, productsByCategoryUrl, categoryName)
AddNode(categoryNode, root)
End If
' Add the product SiteMapNode
Dim productUrl As String = _
"~/SiteMapProvider/ProductDetails.aspx?ProductID=" & _
product.ProductID
Dim productNode As New SiteMapNode _
(Me, String.Concat("Product:", product.ProductID), _
productUrl, product.ProductName)
AddNode(productNode, categoryNode)
Next
' Add a "dummy" item to the cache using a SqlCacheDependency
' on the Products and Categories tables
Dim productsTableDependency As New _
System.Web.Caching.SqlCacheDependency("NorthwindDB", "Products")
Dim categoriesTableDependency As New _
System.Web.Caching.SqlCacheDependency("NorthwindDB", "Categories")
' Create an AggregateCacheDependency
Dim aggregateDependencies As New System.Web.Caching.AggregateCacheDependency()
aggregateDependencies.Add(productsTableDependency, categoriesTableDependency)
' Add the item to the cache specifying a callback function
HttpRuntime.Cache.Insert( _
CacheDependencyKey, DateTime.Now, aggregateDependencies, _
Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, _
CacheItemPriority.Normal, AddressOf OnSiteMapChanged)
' Finally, return the root node
Return root
End SyncLock
End Function
Protected Overrides Function GetRootNodeCore() As System.Web.SiteMapNode
Return BuildSiteMap()
End Function
Protected Sub OnSiteMapChanged _
(key As String, value As Object, reason As CacheItemRemovedReason)
SyncLock siteMapLock
If String.Compare(key, CacheDependencyKey) = 0 Then
' Refresh the site map
root = Nothing
End If
End SyncLock
End Sub
Public ReadOnly Property CachedDate() As Nullable(Of DateTime)
Get
Dim value As Object = HttpRuntime.Cache(CacheDependencyKey)
If value Is Nothing OrElse Not TypeOf value Is Nullable(Of DateTime) Then
Return Nothing
Else
Return CType(value, Nullable(Of DateTime))
End If
End Get
End Property
End Class
让我们从探索此类 BuildSiteMap
方法开始,该方法以 lock
语句开头。 该 lock
语句一次只允许一个线程输入,从而序列化对其代码的访问,并阻止两个并发线程相互单步执行。
类级 SiteMapNode
变量 root
用于缓存站点地图结构。 首次构造站点地图时,或在修改基础数据后首次构造站点地图时, root
将 Nothing
构造站点地图结构。 站点映射的根节点在 root
施工过程中被分配到,以便下次调用 root
此方法时,不会 Nothing
。 因此,只要 root
不是 Nothing
站点地图结构,就不会返回给调用方,而无需重新创建它。
如果根是 Nothing
,则从产品和类别信息创建站点地图结构。 通过创建SiteMapNode
实例,然后通过对类方法的AddNode
调用StaticSiteMapProvider
形成层次结构来生成站点地图。 AddNode
执行内部记帐,将分类 SiteMapNode
实例存储在一个 Hashtable
. 在开始构造层次结构之前,首先调用 Clear
该方法,该方法从内部 Hashtable
清除元素。 接下来, ProductsBLL
类 s GetProducts
方法和结果 ProductsDataTable
存储在局部变量中。
站点地图的构造首先创建根节点并将其分配给它 root
。 此处和整个过程中BuildSiteMap
使用的构造函数的SiteMapNode
重载传递以下信息:
- 对站点地图提供程序(
Me
)的引用。 - s
SiteMapNode
Key
. 此必需值对于每个值必须是唯一SiteMapNode
的。 - s
SiteMapNode
Url
.Url
是可选的,但如果提供,则每个SiteMapNode
值必须是唯一的Url
。 - 所需的
SiteMapNode
sTitle
。
该方法AddNode(root)
调用将SiteMapNode
root
站点地图添加为根目录。 接下来, ProductRow
枚举每个 ProductsDataTable
项。 如果当前产品类别已存在, SiteMapNode
则引用它。 否则,SiteMapNode
将创建类别的新项,并将其添加为通过AddNode(categoryNode, root)
方法调用的SiteMapNode``root
子级。 找到或创建相应的类别 SiteMapNode
节点后,将为当前产品创建一个 SiteMapNode
,并通过该类别 SiteMapNode
的子级添加一 AddNode(productNode, categoryNode)
个。 请注意,类别SiteMapNode
的Url
属性值是在~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID
分配 product SiteMapNode
s Url
属性时。~/SiteMapNode/ProductDetails.aspx?ProductID=productID
注意
具有数据库NULL
CategoryID
值的这些产品将分组到属性设置为 None 且Url
其属性设置为空字符串的类别SiteMapNode
Title
下。 我决定设置为Url
空字符串,因为ProductBLL
类GetProductsByCategory(categoryID)
方法当前缺少仅返回具有NULL
CategoryID
值的这些产品的功能。 此外,我想演示导航控件如何呈现 SiteMapNode
其属性缺少值的 Url
控件。 建议你扩展本教程,以便 None SiteMapNode
Url
属性指向ProductsByCategory.aspx
,但只显示具有NULL
CategoryID
值的产品。
构造站点地图后,使用 SQL 缓存依赖项Categories
通过对象向Products
AggregateCacheDependency
数据缓存添加任意对象。 我们在前面的教程中介绍了如何使用 SQL 缓存依赖项, 即使用 SQL 缓存依赖项。 但是,自定义站点地图提供程序使用尚未探索的数据缓存方法 Insert
的重载。 此重载接受其最终输入参数,这是从缓存中删除对象时调用的委托。 具体而言,我们传入一个新的 CacheItemRemovedCallback
委托,该委托 指向 OnSiteMapChanged
在类中 NorthwindSiteMapProvider
进一步定义的方法。
注意
站点映射的内存中表示形式通过类级变量 root
进行缓存。 由于自定义网站地图提供程序类只有一个实例,并且该实例在 Web 应用程序中的所有线程之间共享,因此此类变量充当缓存。 该方法 BuildSiteMap
还使用数据缓存,但仅作为在基础数据库数据 Categories
更改或 Products
表更改时接收通知的方法。 请注意,放入数据缓存中的值只是当前日期和时间。 实际站点地图数据 未 放入数据缓存中。
该方法 BuildSiteMap
通过返回站点地图的根节点来完成。
其余方法非常简单。 GetRootNodeCore
负责返回根节点。 由于 BuildSiteMap
返回根, GetRootNodeCore
只需返回 BuildSiteMap
返回值。 此方法 OnSiteMapChanged
在删除缓存项时设置 root
回 Nothing
去。 将根设置回去 Nothing
后 BuildSiteMap
,下次调用时,将重新生成站点地图结构。 最后,如果该属性存在,则 CachedDate
返回存储在数据缓存中的日期和时间值。 页面开发人员可以使用此属性来确定网站地图数据上次缓存时间。
步骤 7:注册NorthwindSiteMapProvider
为了使 Web 应用程序使用NorthwindSiteMapProvider
在步骤 6 中创建的网站地图提供程序,我们需要在 <<siteMap>
注册它。 具体而言,在<system.web>
元素中添加以下标记:Web.config
<siteMap defaultProvider="AspNetXmlSiteMapProvider">
<providers>
<add name="Northwind" type="NorthwindSiteMapProvider" />
</providers>
</siteMap>
此标记执行两项操作:首先,它指示内置 AspNetXmlSiteMapProvider
是默认站点地图提供程序;其次,它注册在步骤 6 中创建的自定义网站地图提供程序,其名称为 Northwind。
注意
对于位于应用程序 App_Code
文件夹中的网站 type
地图提供程序,该属性的值只是类名。 或者,可以在单独的类库项目中创建自定义网站地图提供程序,并将编译的程序集放置在 Web 应用程序目录中 /Bin
。 在这种情况下,type
属性值将是命名空间。ClassName,AssemblyName 。
更新 Web.config
后,花点时间查看浏览器中教程中的任何页面。 请注意,左侧的导航接口仍显示其中 Web.sitemap
定义的部分和教程。 这是因为我们保留 AspNetXmlSiteMapProvider
为默认提供程序。 为了创建使用该 NorthwindSiteMapProvider
元素的导航用户界面元素,我们需要显式指定应使用 Northwind 站点地图提供程序。 我们将了解如何在步骤 8 中完成此操作。
步骤 8:使用自定义网站地图提供程序显示网站地图信息
创建并注册 Web.config
自定义网站地图提供程序后,我们便可以将导航控件添加到文件夹中的 Default.aspx
导航 ProductsByCategory.aspx
控件和 ProductDetails.aspx
页面 SiteMapProvider
。 首先打开页面, Default.aspx
然后将一个 SiteMapPath
从工具箱拖动到设计器上。 SiteMapPath 控件位于工具箱的导航部分中。
图 16:向 添加 SiteMapPath(Default.aspx
单击以查看全尺寸图像)
SiteMapPath 控件显示痕迹导航,指示站点地图中的当前页位置。 我们在母版页和网站导航教程中将 SiteMapPath 添加到母版页顶部。
花点时间通过浏览器查看此页面。 图 16 中添加的 SiteMapPath 使用默认站点地图提供程序,从中提取 Web.sitemap
其数据。 因此,痕迹导航显示“开始 > 自定义网站地图”,就像右上角的痕迹导航一样。
图 17:痕迹导航使用默认站点地图提供程序(单击以查看全尺寸图像)
若要在图 16 中添加 SiteMapPath,请使用我们在步骤 6 中创建的自定义网站地图提供程序,将其 SiteMapProvider
属性 设置为 Northwind,即分配给 NorthwindSiteMapProvider
in Web.config
的名称。 遗憾的是,设计器继续使用默认网站地图提供程序,但如果在进行此属性更改后通过浏览器访问页面,则会看到痕迹导航现在使用自定义网站地图提供程序。
图 18:痕迹导航现在使用自定义网站地图提供程序 NorthwindSiteMapProvider
(单击以查看全尺寸图像)
SiteMapPath 控件在页面中ProductsByCategory.aspx
ProductDetails.aspx
显示功能更强大的用户界面。 将 SiteMapPath 添加到这些页面,并将 SiteMapProvider
这两个页面中的属性都设置为 Northwind。 单击 Default.aspx
“饮料的查看产品”链接,然后在柴茶的“查看详细信息”链接上单击。 如图 19 所示,痕迹导航包括当前网站地图部分(柴茶)及其祖先:饮料和所有类别。
图 19:痕迹导航现在使用自定义网站地图提供程序 NorthwindSiteMapProvider
(单击以查看全尺寸图像)
除了 SiteMapPath 之外,还可以使用其他导航用户界面元素,例如 Menu 和 TreeView 控件。 Default.aspx
本教程下载中的页面ProductsByCategory.aspx
,ProductDetails.aspx
例如,所有包含菜单控件(请参阅图 20)。 请参阅 ASP.NET 2.0 的复杂网站导航功能和 ASP.NET 2.0 快速入门的“使用网站导航控件”部分,深入了解 ASP.NET 2.0 中的导航控件和网站地图系统。
图 20:菜单控件列出了每个类别和产品(单击以查看全尺寸图像)
如本教程前面所述,可以通过类以编程方式 SiteMap
访问站点地图结构。 以下代码返回默认提供程序的根 SiteMapNode
目录:
Dim root As SiteMapNode = SiteMap.RootNode
AspNetXmlSiteMapProvider
由于它是应用程序的默认提供程序,因此上述代码将返回在其中Web.sitemap
定义的根节点。 若要引用默认站点地图提供程序以外的站点地图提供程序,请使用SiteMap
类的属性Providers
,如下所示:
Dim root As SiteMapNode = SiteMap.Providers("name").RootNode
其中,名称是自定义网站地图提供程序的名称(Northwind,用于 Web 应用程序)。
若要访问特定于站点地图提供程序的成员,请使用 SiteMap.Providers["name"]
检索提供程序实例,然后将其强制转换为适当的类型。 例如,若要在 ASP.NET 页中显示 NorthwindSiteMapProvider
s CachedDate
属性,请使用以下代码:
Dim customProvider As NorthwindSiteMapProvider = _
TryCast(SiteMap.Providers("Northwind"), NorthwindSiteMapProvider)
If customProvider IsNot Nothing Then
Dim lastCachedDate As Nullable(Of DateTime) = customProvider.CachedDate
If lastCachedDate.HasValue Then
SiteMapLastCachedDate.Text = _
"Site map cached on: " & lastCachedDate.Value.ToString()
Else
SiteMapLastCachedDate.Text = "The site map is being reconstructed!"
End If
End If
注意
请务必测试 SQL 缓存依赖项功能。 访问Default.aspx
ProductsByCategory.aspx
ProductDetails.aspx
“编辑”、“插入”和“删除”部分中的其中一个教程,然后编辑类别或产品的名称。 然后返回到文件夹中的一个页面 SiteMapProvider
。 假设轮询机制有足够的时间来记下对基础数据库的更改,则应更新站点地图以显示新产品或类别名称。
总结
ASP.NET 2.0 s 网站地图功能包括一个 SiteMap
类、一些内置导航 Web 控件,以及一个默认网站地图提供程序,该提供程序需要将网站地图信息保存到 XML 文件。 为了使用来自其他源(例如数据库、应用程序体系结构或远程 Web 服务)的网站地图信息,我们需要创建自定义网站地图提供程序。 这涉及到创建一个直接或间接派生自类的 SiteMapProvider
类。
本教程介绍了如何创建自定义网站地图提供程序,该提供程序基于从应用程序体系结构中剔除的产品和类别信息上的站点地图。 我们的提供程序扩展了 StaticSiteMapProvider
该类,并需要创建一个 BuildSiteMap
检索数据的方法,构造了站点地图层次结构,并在类级变量中缓存了生成的结构。 在修改基础 Categories
或 Products
数据时,我们使用回调函数的 SQL 缓存依赖项使缓存结构失效。
快乐编程!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是戴夫·加德纳、扎克·琼斯、特蕾莎·墨菲和伯纳黛特·利。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请把我扔一条线。mitchell@4GuysFromRolla.com