生成自定义数据库驱动站点地图提供程序 (VB)

作者 :Scott Mitchell

下载 PDF

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 在 SQL Server 中存储站点地图SQL 站点地图提供程序您一直在等待的文章介绍了在 SQL Server 中存储站点地图数据的通用方法。

步骤 1:创建自定义站点地图提供程序网页

在开始创建自定义站点地图提供程序之前,让我们先添加本教程所需的 ASP.NET 页面。 首先添加名为 SiteMapProvider的新文件夹。 接下来,将以下 ASP.NET 页添加到该文件夹,确保将每个页面与 Site.master 母版页相关联:

  • Default.aspx
  • ProductsByCategory.aspx
  • ProductDetails.aspx

此外,将 CustomProviders 子文件夹添加到 App_Code 文件夹。

为站点地图 Provider-Related 教程添加 ASP.NET 页面

图 2:为站点地图 Provider-Related 教程添加 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:站点地图现在包含站点地图提供程序教程的条目

本教程main重点介绍如何创建自定义站点地图提供程序以及配置 Web 应用程序以使用该提供程序。 具体而言,我们将生成一个提供程序,该提供程序返回一个站点地图,其中包含一个根节点以及每个类别和产品的节点,如图 1 所示。 通常,站点地图中的每个节点都可以指定 URL。 对于站点地图,根节点的 URL 将为 ~/SiteMapProvider/Default.aspx,它将列出数据库中的所有类别。 站点地图中的每个类别节点都有一个指向 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID的 URL,该 URL 将列出指定 categoryID 中的所有产品。 最后,每个产品站点地图节点将指向 ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID,该节点将显示特定产品的详细信息。

首先,需要创建 Default.aspxProductsByCategory.aspxProductDetails.aspx 页。 这些页面分别在步骤 2、3 和 4 中完成。 由于本教程的主旨是站点地图提供程序,并且由于过去的教程已经介绍了如何创建此类多页面母版/详细信息报表,因此我们将尽快完成步骤 2 到步骤 4。 如果需要在创建跨多个页面的主报表/详细信息报表时进行复习,请参阅 跨两页筛选母版/详细信息筛选 教程。

步骤 2:显示类别列表

Default.aspx打开 文件夹中的页面SiteMapProvider,将 GridView 从“工具箱”拖到Designer上,将其ID设置为 Categories。 在 GridView 的智能标记中,将其绑定到名为 CategoriesDataSource 的新 ObjectDataSource,并对其进行配置,以便它使用 CategoriesBLL 类 s GetCategories 方法检索其数据。 由于此 GridView 仅显示类别,不提供数据修改功能,因此请将“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 。

使用 GetCategories 方法将 ObjectDataSource 配置为返回类别

图 4:使用 GetCategories 方法配置 ObjectDataSource 以返回类别 (单击以查看全尺寸图像)

将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“ (None”)

图 5:将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“ (无) (单击以查看全尺寸图像)

完成“配置数据源”向导后,Visual Studio 将为 、CategoryName、、 DescriptionNumberOfProductsBrochurePath添加 BoundFieldCategoryID。 编辑 GridView,使其仅包含 CategoryNameDescription BoundFields,并将 BoundField 属性HeaderText更新CategoryName为 Category 。

接下来,添加 HyperLinkField 并将其定位为最左侧的字段。 将 DataNavigateUrlFields 属性设置为 CategoryID,将 DataNavigateUrlFormatString 属性设置为 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}。 将 Text 属性设置为“查看产品”。

将 HyperLinkField 添加到类别网格视图

图 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) 方法,并将下拉列表设置为“更新”、“插入”和“删除”选项卡中 (“无”) 。

使用 ProductsBLL 类 getProductsByCategoryID (categoryID) 方法

图 8:使用 ProductsBLL 类方法 GetProductsByCategoryID(categoryID) (单击以查看全尺寸图像)

配置数据源向导中的最后一步提示输入 categoryID 的参数源。 由于此信息是通过查询字符串字段 CategoryID传递的,因此从下拉列表中选择“QueryString”,并在“QueryStringField”文本框中输入 CategoryID,如图 9 所示。 单击“完成”以完成向导。

将 CategoryID 查询字符串字段用于 categoryID 参数

图 9:使用 CategoryIDcategoryID 参数的查询字符串字段 (单击以查看全尺寸图像)

完成向导后,Visual Studio 会将相应的 BoundFields 和 CheckBoxField 添加到产品数据字段的 GridView。 删除除 、 UnitPriceSupplierName BoundField 之外ProductName的所有字段。 自定义这三个 BoundFields HeaderText 属性,分别读取 Product、Price 和 Supplier。 将 UnitPrice BoundField 格式设置为货币。

接下来,添加 HyperLinkField 并将其移动到最左侧的位置。 将其 Text 属性设置为“查看详细信息”,将其 DataNavigateUrlFields 属性设置为 ProductID,将其 DataNavigateUrlFormatString 属性设置为 ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}

添加指向ProductDetails.aspx的查看详细信息 HyperLinkField

图 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“详细信息视图”并将其从“工具箱”拖到Designer。 将 DetailsView 的 ID 属性设置为 ProductInfo 并清除其 HeightWidth 属性值。 在其智能标记中,将 DetailsView 绑定到名为 ProductDataSource的新 ObjectDataSource,将 ObjectDataSource 配置为从 ProductsBLL 类 s GetProductByProductID(productID) 方法拉取其数据。 与步骤 2 和 3 中创建的先前网页一样,将“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 。

配置 ObjectDataSource 以使用 GetProductByProductID (productID) 方法

图 12:将 ObjectDataSource 配置为使用 GetProductByProductID(productID) 方法 (单击以查看全尺寸图像)

配置数据源向导的最后一步会提示输入 productID 参数的源。 由于此数据来自查询字符串字段 ProductID,因此将下拉列表设置为 QueryString,并将 QueryStringField 文本框设置为 ProductID。 最后,单击“完成”按钮以完成向导。

配置 productID 参数以从 ProductID 查询字符串字段拉取其值

图 13:配置 productID 参数以从 ProductID 查询字符串字段拉取其值 (单击以查看全尺寸图像)

完成“配置数据源”向导后,Visual Studio 将在产品数据字段的 DetailsView 中创建相应的 BoundFields 和 CheckBoxField。 ProductID删除 、 SupplierIDCategoryID BoundFields,并根据需要配置剩余字段。 经过一些美观配置后,我的 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具有 、 和 DescriptionTitleUrl属性,这些属性为 表示的 节SiteMapNode提供信息。 还有一个唯一Key标识SiteMapNode层次结构中的每个属性,以及用于建立此层次结构 、ParentNodeNextSiblingPreviousSibling等的属性ChildNodes

图 15 显示了图 1 中的常规站点地图结构,但更详细地绘制了实现细节。

每个 SiteMapNode 都有标题、URL、密钥等属性

图 15:每个属性 SiteMapNode (如 TitleUrlKey等) (单击以查看全尺寸图像)

可通过 命名空间中的 System.Web类访问SiteMap站点地图。 此类的 RootNode 属性返回站点映射的根 SiteMapNode 实例; CurrentNode 返回 SiteMapNodeUrl 属性与当前请求页面的 URL 匹配的 。 此类由 ASP.NET 2.0 秒导航 Web 控件在内部使用。

SiteMap访问类属性时,它必须将站点地图结构从某些永久性介质序列化到内存中。 但是,站点地图序列化逻辑不是硬编码到 类中 SiteMap 。 相反,在运行时, SiteMap 类确定用于序列化的站点地图 提供程序 。 默认情况下, XmlSiteMapProvider 使用 类 ,它从格式正确的 XML 文件中读取站点地图结构。 但是,通过一些工作,我们可以创建自己的自定义站点地图提供程序。

所有站点地图提供程序都必须派生自 SiteMapProvider,该类包括站点地图提供程序所需的基本方法和属性,但省略了许多实现详细信息。 第二个类 StaticSiteMapProvider扩展类 SiteMapProvider ,并包含所需功能的更可靠的实现。 在内部, StaticSiteMapProvider 将站点地图的实例存储在 SiteMapNode 中,Hashtable并提供方法(如 AddNode(child, parent)),RemoveNode(siteMapNode),以及Clear()向内部 Hashtable添加和删除 SiteMapNode 的方法。 XmlSiteMapProvider 派生自 StaticSiteMapProvider

创建扩展 的 StaticSiteMapProvider自定义站点地图提供程序时,必须重写两个抽象方法: BuildSiteMapGetRootNodeCoreBuildSiteMap顾名思义,负责从永久性存储加载站点地图结构并在内存中构造它。 GetRootNodeCore 返回站点映射中的根节点。

Web 应用程序必须先在应用程序的配置中注册站点地图提供程序,然后才能使用站点地图提供程序。 默认情况下,类 XmlSiteMapProvider 是使用名称 AspNetXmlSiteMapProvider注册的。 若要注册其他站点地图提供程序,请将以下标记添加到 Web.config

<configuration>
    <system.web>
        ...
        <siteMap defaultProvider="defaultProviderName">
          <providers>
            <add name="name" type="type" />
          </providers>
        </siteMap>
    </system.web>
</configuration>

名称值将人类可读的名称分配给提供程序,而 type 指定站点地图提供程序的完全限定类型名称。 在创建自定义站点地图提供程序后,我们将在步骤 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 文章中的存储站点地图

步骤 6:创建自定义站点地图提供程序

若要创建从 Northwind 数据库中的类别和产品生成站点地图的自定义站点地图提供程序,我们需要创建一个扩展 的 StaticSiteMapProvider类。 在步骤 1 中,我要求你在 文件夹中App_Code添加一个CustomProviders文件夹 - 将一个新类添加到名为 的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 ,站点地图结构将返回到调用方,而无需重新创建它。

如果 root 为 Nothing,则从产品和类别信息创建站点地图结构。 站点地图是通过创建 SiteMapNode 实例,然后通过调用 StaticSiteMapProvider 类 s 方法形成层次结构来构建的 AddNodeAddNode 执行内部记账,将各种 SiteMapNode 实例存储在 中 Hashtable。 在开始构造层次结构之前,首先调用 Clear 方法,该方法从内部 Hashtable中清除元素。 接下来,类 ProductsBLLGetProducts 方法和生成的 ProductsDataTable 存储在局部变量中。

站点地图的构造首先创建根节点并将其分配给 root。 此处使用的 构造函数的SiteMapNode重载以及在整个过程中BuildSiteMap传递以下信息:

  • 对站点地图提供程序的引用 (Me) 。
  • s SiteMapNodeKey。 对于每个 SiteMapNode,此必需值必须是唯一的。
  • s SiteMapNodeUrlUrl 是可选的,但如果提供,则每个 SiteMapNode 值必须是唯一的 Url
  • 需要SiteMapNode的 。Title

方法 AddNode(root) 调用将 作为根添加到 SiteMapNoderoot 站点地图。 接下来, ProductRow 枚举 中的每个 ProductsDataTable 。 如果已存在 SiteMapNode 当前产品类别的 ,则会引用它。 否则,将创建类别的新SiteMapNode,并通过 AddNode(categoryNode, root) 方法调用将其添加为 的SiteMapNode``root子级。 找到或创建相应的类别SiteMapNode节点后,会为当前产品创建 ,SiteMapNode并通过 添加为类别的子级SiteMapNodeAddNode(productNode, categoryNode)。 请注意,类别SiteMapNode的 属性值为 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID ,而产品 SiteMapNode 属性Url被分配。~/SiteMapNode/ProductDetails.aspx?ProductID=productIDUrl

注意

具有其CategoryID数据库NULL值的产品将分组到属性设置为 None 且属性设置为空字符串的Url类别SiteMapNodeTitle下。 我决定将 设置为 Url 空字符串, ProductBLL 因为 类方法 GetProductsByCategory(categoryID) 当前缺少仅返回具有 NULLCategoryID 值的产品的功能。 此外,我想演示导航控件如何呈现 SiteMapNode 缺少其 Url 属性值的 。 建议扩展本教程,以便 None SiteMapNode 的 属性指向 ProductsByCategory.aspx,但仅显示具有CategoryIDNULLUrl的产品。

构造站点地图后,通过 AggregateCacheDependency 对象使用对 和 Products 表的 Categories SQL 缓存依赖项将任意对象添加到数据缓存中。 在前面的教程“使用 SQL 缓存依赖项”中,我们探讨了 如何使用 SQL 缓存依赖项。 但是,自定义站点地图提供程序使用我们尚未探索的数据缓存 方法的 Insert 重载。 此重载接受委托作为其最终输入参数,该委托在从缓存中删除对象时调用。 具体而言,我们传入一个新的 CacheItemRemovedCallback 委托 ,该委托指向 OnSiteMapChanged 在 类中 NorthwindSiteMapProvider 进一步定义的 方法。

注意

通过类级变量 root缓存站点地图的内存中表示形式。 由于自定义站点地图提供程序类只有一个实例,并且该实例在 Web 应用程序中的所有线程之间共享,因此此类变量用作缓存。 方法BuildSiteMap还使用数据缓存,但仅在 或 Products 表中的基础数据库数据Categories发生更改时接收通知。 请注意,放入数据缓存中的值只是当前日期和时间。 实际站点地图数据 不会 放入数据缓存中。

方法 BuildSiteMap 通过返回站点地图的根节点来完成。

其余方法相当简单。 GetRootNodeCore 负责返回根节点。 由于 BuildSiteMap 返回根, GetRootNodeCore 只需返回 BuildSiteMap 返回值。 方法 OnSiteMapChanged 在删除缓存项时将 设置为 rootNothing 。 将根设置回 时 Nothing,下次 BuildSiteMap 调用 时,将重新生成站点地图结构。 最后,如果存在此类值,属性 CachedDate 将返回存储在数据缓存中的日期和时间值。 页面开发人员可以使用此属性来确定上次缓存站点地图数据时间。

步骤 7:注册NorthwindSiteMapProvider

为了使 Web 应用程序能够使用步骤 6 中创建的NorthwindSiteMapProvider站点地图提供程序,我们需要在 的 节Web.config<siteMap>注册它。 具体而言,在 中的 Web.config元素中添加以下标记<system.web>

<siteMap defaultProvider="AspNetXmlSiteMapProvider">
  <providers>
    <add name="Northwind" type="NorthwindSiteMapProvider" />
  </providers>
</siteMap>

此标记执行两项操作:第一,它指示内置 AspNetXmlSiteMapProvider 是默认的站点地图提供程序;第二,它使用人类友好名称 Northwind 注册在步骤 6 中创建的自定义站点地图提供程序。

注意

对于位于应用程序 文件夹的 App_Code 站点地图提供程序,特性的值 type 只是类名。 或者,可以在单独的类库项目中创建自定义站点地图提供程序,并将已编译的程序集放置在 Web 应用程序的 /Bin 目录中。 在这种情况下, type 属性值为 NamespaceClassNameAssemblyName

更新 Web.config后,请花点时间在浏览器中查看教程中的任何页面。 请注意,左侧的导航界面仍显示 中 Web.sitemap定义的部分和教程。 这是因为我们保留 AspNetXmlSiteMapProvider 为默认提供程序。 若要创建使用 NorthwindSiteMapProvider的导航用户界面元素,需要显式指定应使用 Northwind 站点地图提供程序。 我们将在步骤 8 中了解如何完成此操作。

步骤 8:使用自定义站点地图提供程序显示站点地图信息

在 中创建Web.config并注册自定义站点地图提供程序后,我们已准备好将导航控件添加到 文件夹中的 Default.aspxSiteMapProviderProductsByCategory.aspxProductDetails.aspx 页面。 首先打开页面,Default.aspx将 工具箱中的 拖SiteMapPath到Designer。 SiteMapPath 控件位于工具箱的导航部分中。

将 SiteMapPath 添加到 Default.aspx

图 16:将 SiteMapPath Default.aspx 添加到 (单击以查看全尺寸图像)

SiteMapPath 控件显示痕迹导航,指示当前页在站点地图中的位置。 我们在母版页和网站导航教程中将 SiteMapPath 添加到 母版页 顶部。

花点时间通过浏览器查看此页面。 图 16 中添加的 SiteMapPath 使用默认站点地图提供程序,从 Web.sitemap拉取其数据。 因此,痕迹导航显示主页 > 自定义网站地图,就像右上角的痕迹导航一样。

痕迹导航使用默认站点地图提供程序

图 17:痕迹导航使用默认站点地图提供程序 (单击以查看全尺寸图像)

若要使图 16 中添加的 SiteMapPath 使用我们在步骤 6 中创建的自定义站点地图提供程序,请将其SiteMapProvider属性设置为 Northwind,即我们在 中Web.config分配给 NorthwindSiteMapProvider 的名称。 遗憾的是,Designer继续使用默认站点地图提供程序,但如果在更改此属性后通过浏览器访问页面,你将看到痕迹导航现在使用自定义站点地图提供程序。

显示痕迹导航如何显示自定义站点地图提供程序的屏幕截图。

图 18:痕迹导航现在使用自定义站点地图提供程序 NorthwindSiteMapProvider (单击以查看全尺寸图像)

SiteMapPath 控件在 ProductsByCategory.aspxProductDetails.aspx 页面中显示功能更强大的用户界面。 向这些页面添加 SiteMapPath,将两者中的 属性设置为 SiteMapProvider Northwind。 单击 Default.aspx 饮料的“查看产品”链接,然后单击柴茶的“查看详细信息”链接。 如图 19 所示,痕迹导航包括当前站点地图部分, ( 柴茶 ) 及其祖先:饮料和所有类别。

显示痕迹导航如何显示当前站点地图部分的屏幕截图, (柴茶) 及其祖先 (饮料和所有类别) 。

图 19:痕迹导航现在使用自定义站点地图提供程序 NorthwindSiteMapProvider (单击以查看全尺寸图像)

除了 SiteMapPath 之外,还可以使用其他导航用户界面元素,例如 Menu 和 TreeView 控件。 Default.aspx例如,本教程下载中的 、 ProductsByCategory.aspxProductDetails.aspx 页面都包括菜单控件, (请参阅图 20) 。 有关 ASP.NET 2.0 中导航控件和站点地图系统的更深入介绍,请参阅 ASP.NET 2.0 快速入门 ASP.NET 2.0 中的复杂网站导航功能和使用网站导航控件部分。

菜单控件Lists每个类别和产品

图 20:菜单控件Lists每个类别和产品 (单击以查看全尺寸图像)

如本教程前面所述,可以通过 类以编程方式 SiteMap 访问站点地图结构。 以下代码返回默认提供程序的根 SiteMapNode

Dim root As SiteMapNode = SiteMap.RootNode

AspNetXmlSiteMapProvider由于 是应用程序的默认提供程序,因此上述代码将返回 在 中Web.sitemap定义的根节点。 若要引用除默认值以外的站点地图提供程序,请使用 SiteMap属性Providers,如下所示:

Dim root As SiteMapNode = SiteMap.Providers("name").RootNode

其中 ,name 是 Web 应用程序) 的自定义站点地图提供程序 ( Northwind 的名称。

若要访问特定于站点地图提供程序的成员,请使用 SiteMap.Providers["name"] 检索提供程序实例,然后将其强制转换为适当的类型。 例如,若要在 ASP.NET 页中显示 NorthwindSiteMapProvider 属性 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.aspxProductsByCategory.aspxProductDetails.aspx 页面后,请转到编辑、插入和删除部分中的教程之一,并编辑类别或产品的名称。 然后返回到 文件夹中的一个页面 SiteMapProvider 。 假设轮询机制已过足够的时间来记录对基础数据库的更改,则应更新站点地图以显示新的产品或类别名称。

总结

ASP.NET 2.0 s 站点地图功能包括类 SiteMap 、许多内置导航 Web 控件和默认站点地图提供程序,该提供程序需要站点地图信息保存到 XML 文件中。 若要使用来自其他源(例如数据库、应用程序的体系结构或远程 Web 服务)的站点地图信息,我们需要创建自定义站点地图提供程序。 这涉及到创建直接或间接从 类派生的 SiteMapProvider 类。

在本教程中,我们了解了如何创建基于从应用程序体系结构中剔除的产品和类别信息的站点地图的自定义站点地图提供程序。 我们的提供程序扩展了 StaticSiteMapProvider 类,并需要创建一个 BuildSiteMap 方法来检索数据、构造站点地图层次结构,并将生成的结构缓存在类级变量中。 我们在修改基础 CategoriesProducts 数据时,将 SQL 缓存依赖项与回调函数结合使用,使缓存结构失效。

编程快乐!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是戴夫·加德纳、扎克·琼斯、特蕾莎·墨菲和伯纳黛特·利。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。