共用方式為


建置自訂的資料庫驅動網站導覽提供者 (VB)

作者:Scott Mitchell

下載 PDF

ASP.NET 2.0 中的預設網站地圖提供者會從靜態 XML 檔案擷取其數據。 雖然 XML 型提供者適用於許多小型和中型網站,但較大的 Web 應用程式需要更動態的網站對應。 在本教學課程中,我們將建置自定義網站地圖提供者,以從商業規則層擷取其數據,進而從資料庫擷取數據。

簡介

ASP.NET 2.0 s 網站地圖功能可讓頁面開發人員定義某些持續性媒體中的 Web 應用程式網站地圖,例如在 XML 檔案中。 定義之後,即可透過SiteMap命名空間中的 System.Web 類別,或透過各種導覽 Web 控件,例如 SiteMapPath、Menu 和 TreeView 控件,以程式設計方式存取網站地圖數據。 網站地圖系統會使用提供者模型,以便建立不同的網站地圖串行化實作,並插入 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 ve waiting For articles, examine a generalized approach to storing site map data in 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:網站地圖現在包含網站地圖提供者教學課程的專案

本教學課程的主要焦點在於說明如何建立自定義網站地圖提供者,以及設定Web應用程式以使用該提供者。 特別是,我們將建置一個提供者,以傳回網站地圖,其中包含根節點以及每個類別和產品的節點,如圖 1 所述。 一般而言,網站地圖中的每個節點都可以指定URL。 針對我們的網站地圖,根節點的 URL 將會是 ~/SiteMapProvider/Default.aspx,這會列出資料庫中的所有類別。 網站地圖中的每個類別節點都會有指向 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID的 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 只會顯示類別,且不提供資料修改功能,因此請將 UPDATE、INSERT 和 DELETE 索引卷標的下拉式清單設定為 ([無) ]。

使用 GetCategories 方法設定 ObjectDataSource 以傳回類別

圖 4:使用 方法設定 ObjectDataSource 以傳 GetCategories 回類別 (按兩下即可檢視大小完整的影像)

將 UPDATE、INSERT 和 DELETE 索引標籤中的 Drop-Down 清單 設定為 ([無])

圖 5:將 UPDATE、INSERT 和 DELETE 索引標籤中的 Drop-Down 清單 設定為 ([無]) (按兩下即可檢視完整大小的映像)

完成 [設定數據源精靈] 之後,Visual Studio 會新增 、CategoryName、、 DescriptionNumberOfProductsBrochurePathCategoryIDBoundField。 編輯 GridView,使其只包含 CategoryNameDescription BoundFields,並將 BoundField s HeaderText 屬性更新CategoryName為 Category 。

接下來,新增 HyperLinkField 並將它定位為最左邊的字段。 屬性設定為 CategoryIDDataNavigateUrlFields並將 DataNavigateUrlFormatString 屬性設定為 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}。 將 Text 屬性設定為 [檢視產品]。

將 HyperLinkField 新增至類別 GridView

圖 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] 索引標籤中的 [無) ]。

使用 ProductsBLL 類別 s GetProductsByCategoryID (categoryID) 方法

圖 8:使用 ProductsBLL 類別 s GetProductsByCategoryID(categoryID) 方法 (按下即可檢視大小完整的影像)

[設定數據源精靈] 中的最後一個步驟會提示 輸入 categoryID 的參數來源。 由於這項資訊是透過 querystring 字段 CategoryID傳遞,請從下拉式清單中選取 [QueryString],然後在 [QueryStringField] 文本框中輸入 CategoryID,如圖 9 所示。 按一下 [完成] 以完成精靈。

針對 categoryID 參數使用 CategoryID 查詢字串字段

圖 9:使用 CategoryIDcategoryID 參數的 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}

新增指向 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 s ID 屬性設定為 ProductInfo ,並清除其 HeightWidth 屬性值。 從智慧標記中,將 DetailsView 系結至名為 ProductDataSource的新 ObjectDataSource,將 ObjectDataSource 設定為從 ProductsBLL 類別 s GetProductByProductID(productID) 方法提取其數據。 如同在步驟 2 和 3 中建立的先前網頁,請將 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單設定為 (None) 。

將 ObjectDataSource 設定為使用 GetProductByProductID (productID) 方法

圖 12:將 ObjectDataSource 設定為使用 GetProductByProductID(productID) 方法 (按兩下即可檢視大小完整的映像)

[設定數據源精靈] 的最後一個步驟會提示 productID 參數的來源。 由於此數據會通過 querystring 欄位 ProductID,請將下拉式清單設定為 QueryString,並將 QueryStringField 文字框設定為 ProductID。 最後,按兩下 [完成] 按鈕以完成精靈。

將 productID 參數設定為從 ProductID 查詢字串字位提取其值

圖 13:將 productID 參數設定為從 ProductID Querystring 欄位提取其值 (按兩下即可檢視大小完整的影像)

完成 [設定數據源精靈] 之後,Visual Studio 會在 [詳細數據] 欄位中建立對應的 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 ,然後按兩下 [檢視產品] 以取得 [供應專案] 類別。 從飲料產品清單中,按兩下 Chai Tea 的 [檢視詳細資料] 連結。 這會帶您前往 ProductDetails.aspx?ProductID=1,其中顯示 Chai Tea 的詳細數據 (請參閱圖 14) 。

顯示 Chai Tea s 供應商、類別、價格和其他資訊

圖 14:Chai Tea s 供應商、類別、價格和其他資訊隨即顯示 (按兩下即可檢視大小完整的影像)

步驟 5:了解網站地圖提供者的內部工作

網站地圖會以構成階層的 SiteMapNode 實例集合的形式呈現在網頁伺服器的記憶體中。 必須只有一個根目錄,所有非根節點都必須只有一個父節點,而且所有節點可能都有任意數目的子節點。 每個 SiteMapNode 物件都代表網站結構中的區段;這些區段通常有對應的網頁。 因此, SiteMapNode 類別 具有的屬性,例如 TitleUrlDescription,可提供 所表示區段 SiteMapNode 的資訊。 另外還有一個屬性可唯一 Key 識別 SiteMapNode 階層中的每個屬性,以及用來建立此階層 ChildNodesParentNodeNextSiblingPreviousSibling等等的屬性。

圖 15 顯示圖 1 中的一般網站地圖結構,但實作詳細數據會更精細地繪製出來。

每個 SiteMapNode 都有標題、URL、金鑰等屬性

圖 15:每個 SiteMapNode 屬性都有類似 TitleUrl、、 Key等等 (按鍵即可檢視完整大小的影像)

網站地圖可透過 SiteMap 命名空間中的 System.Web類別來存取。 這個類別 s RootNode 屬性會傳回網站地圖的根 SiteMapNode 實例; CurrentNode 傳回 SiteMapNodeUrl 屬性符合目前要求頁面 URL 的 。 ASP.NET 2.0 s 導覽 Web 控件會在內部使用此類別。

SiteMap存取類別的屬性時,它必須將網站地圖結構從某些持續性媒體串行化為記憶體。 不過,網站地圖串行化邏輯不會硬式編碼到 SiteMap 類別中。 相反地,類別會在 SiteMap 運行時間決定要用於串行化的月臺地圖 提供者 。 根據預設,會 XmlSiteMapProvider 使用 類別 ,它會從正確格式化的 XML 檔案讀取網站地圖結構。 不過,我們只需要一些工作,就可以建立自己的自定義網站地圖提供者。

所有網站地圖提供者都必須衍生自 SiteMapProvider 類別,其中包含網站地圖提供者所需的基本方法和屬性,但省略許多實作詳細數據。 第二個類別 StaticSiteMapProviderSiteMapProvider 擴充 類別,並包含更強固的所需功能實作。 在內部,會將 StaticSiteMapProvider 網站地圖的實例儲存 SiteMapNode 在 中 Hashtable ,並提供 之類的 AddNode(child, parent)方法, RemoveNode(siteMapNode), 並將 Clear() 新增和移除 SiteMapNode 至內部 HashtableXmlSiteMapProvider 衍生自 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>

名稱值會在類型指定網站地圖提供者的完整類型名稱時,將人類可讀取的名稱指派給提供者。 我們將在建立自定義網站地圖提供者之後,探索步驟 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 ProsiseSQL 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 可用來快取網站地圖結構。 第一次建構網站地圖時,或第一次修改基礎數據之後,將會是 Nothingroot並建構網站地圖結構。 網站地圖的根節點會在建構程式期間指派給 root ,因此下次呼叫 root 此方法時,將不會是 Nothing。 因此,只要 root 不是 Nothing 網站地圖結構,就會傳回給呼叫端,而不需要重新建立它。

如果 root 為 Nothing,則會從產品和類別資訊建立網站地圖結構。 網站地圖是藉由建立 SiteMapNode 實例,然後透過對類別 s AddNode 方法的呼叫StaticSiteMapProvider來形成階層來建置。 AddNode 會執行內部記事,將各種 SiteMapNode 實例儲存在 中 Hashtable。 開始建構階層之前,我們會先呼叫 Clear 方法,從內部 Hashtable清除元素。 接下來,類別 ProductsBLL s GetProducts 方法和結果 ProductsDataTable 會儲存在局部變數中。

網站地圖的建構從建立根節點開始,並將它指派給 root。 這裡所使用的 s 建構函式多SiteMapNode載,並在整個過程中BuildSiteMap傳遞下列資訊:

  • 網站地圖提供者的參考 (Me) 。
  • SiteMapNode s Key。 每個的必要值都必須是唯一 SiteMapNode的。
  • SiteMapNode s UrlUrl 是選擇性的,但如果提供,每個 SiteMapNode 值都必須是唯一的 Url
  • 需要SiteMapNode的 。Title

方法呼叫會將 AddNode(root)SiteMapNoderoot 新增至網站地圖作為根目錄。 接下來,會列舉 中的ProductsDataTable每個 ProductRow 。 如果目前產品類別已有, SiteMapNode 則會加以參考。 否則,會建立類別的新 SiteMapNode ,並透過 AddNode(categoryNode, root) 方法呼叫新增為 的SiteMapNode``root子系。 找到或建立適當的類別SiteMapNode節點之後,會為目前的產品建立 ,SiteMapNode並透過 AddNode(productNode, categoryNode)新增為類別SiteMapNode的子系。 請注意,類別 SiteMapNode s Url 屬性值是在~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID指派 ~/SiteMapNode/ProductDetails.aspx?ProductID=productIDproduct SiteMapNode s Url 屬性時。

注意

具有其CategoryID資料庫NULL值的這些產品會分組在屬性設定為 None 且其屬性設定為空字串的Url類別SiteMapNodeTitle之下。 我決定設定 Url 為空字串, ProductBLL 因為類別 s GetProductsByCategory(categoryID) 方法目前缺少只傳回具有 NULLCategoryID 值的這些產品的功能。 此外,我想要示範導覽控件如何呈現 SiteMapNode 缺少其 Url 屬性值的 。 我們鼓勵您擴充本教學課程,讓 None SiteMapNodeUrl 屬性指向 ProductsByCategory.aspx,但只顯示具有 NULLCategoryID 值的產品。

建構網站地圖之後,會使用透過 物件對 CategoriesProducts 數據表 AggregateCacheDependency 的 SQL 快取相依性,將任意物件新增至數據快取。 我們在上一個教學課程中探索了 使用 SQL 快取相依性:使用 SQL 快取相依性。 不過,自定義網站地圖提供者會使用我們尚未探索的數據快取 Insert 方法多載。 此多載接受做為其最終輸入參數,這是從快取中移除物件時所呼叫的委派。 具體而言,我們會傳入指向 類別中NorthwindSiteMapProvider進一步定義的 方法的新CacheItemRemovedCallback委派OnSiteMapChanged

注意

網站地圖的記憶體內部表示會透過類別層級變數 root快取。 因為自定義網站地圖提供者類別只有一個實例,而且因為該實例會在 Web 應用程式中的所有線程之間共用,所以這個類別變數會做為快取。 方法BuildSiteMap也會使用數據快取,但只有在 或 Products 數據表中的Categories基礎資料庫數據變更時,才會收到通知。 請注意,放入數據快取中的值只是目前的日期和時間。 實際的網站地圖數據 不會 放入數據快取中。

方法 BuildSiteMap 會傳回網站地圖的根節點來完成。

其餘的方法相當簡單。 GetRootNodeCore 負責傳回根節點。 由於 BuildSiteMap 會傳回根目錄, GetRootNodeCore 因此只會傳 BuildSiteMap 回 傳回值。 方法會在 OnSiteMapChanged 移除快取項目時設定 rootNothing 。 將根設定回 Nothing時,下次 BuildSiteMap 叫用時,將會重建網站地圖結構。 最後,如果這類值存在, CachedDate 屬性會傳回儲存在數據快取中的日期和時間值。 此頁面開發人員可以使用此屬性來判斷網站地圖數據上次快取的時間。

步驟 7:註冊NorthwindSiteMapProvider

為了讓 Web 應用程式使用 NorthwindSiteMapProvider 在步驟 6 中建立的網站地圖提供者,我們必須在 <siteMap>Web.config區段中註冊它。 具體而言,請在 中的 <system.web>Web.config元素內新增下列標記:

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

此標記會執行兩件事:首先,它會指出內 AspNetXmlSiteMapProvider 建是預設的網站地圖提供者;其次,它會使用人類易記的名稱 Northwind 註冊在步驟 6 中建立的自定義網站地圖提供者。

注意

對於位於應用程式 App_Code 資料夾的網站地圖提供者,屬性的值 type 只是類別名稱。 或者,自定義網站地圖提供者可能已在個別的類別庫專案中建立,並將編譯的元件放在 Web 應用程式目錄中 /Bin 。 在此情況下,type屬性值會是 Namespace。ClassName,AssemblyName

更新 Web.config之後,請花點時間從瀏覽器中的教學課程檢視任何頁面。 請注意,左側導覽介面仍會顯示 中 Web.sitemap定義的區段和教學課程。 這是因為我們保留 AspNetXmlSiteMapProvider 為預設提供者。 若要建立使用 的 NorthwindSiteMapProvider導覽使用者介面元素,我們必須明確指定應該使用 Northwind 網站地圖提供者。 我們將瞭解如何在步驟 8 中完成此作業。

步驟 8:使用自訂網站地圖提供者顯示網站地圖資訊

在 中 Web.config建立並註冊自定義網站地圖提供者之後,我們即可將導覽控件新增至資料夾中的 Default.aspxProductsByCategory.aspxProductDetails.aspx 頁面 SiteMapProvider 。 從開啟頁面開始,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 按兩下 [檢視產品] 連結的 [建立者],然後在Chai Tea的 [檢視詳細資料] 連結上。 如圖 19 所示,階層連結包含目前的網站地圖區段, ( Chai Tea ) 及其祖系:飲料和所有類別 。

顯示階層連結如何顯示目前網站地圖區段的螢幕快照, (Chai Tea) 及其上階 () 。

圖 19:階層連結現在使用自定義網站地圖提供者 NorthwindSiteMapProvider , (按兩下即可檢視完整大小的影像)

除了 SiteMapPath 之外,也可以使用其他導覽使用者介面元素,例如 Menu 和 TreeView 控制件。 Default.aspx本教學課程下載中的、 ProductsByCategory.aspxProductDetails.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

其中 name 是 Northwind ( 自定義網站地圖提供者的名稱,在我們的 Web 應用程式) 。

若要存取網站地圖提供者特定的成員,請使用 SiteMap.Providers["name"] 來擷取提供者實例,然後將它轉換成適當的類型。 例如,若要在 NorthwindSiteMapProvider ASP.NET 頁面中顯示 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.aspxProductsByCategory.aspxProductDetails.aspx 頁面之後,請移至 [編輯]、[插入] 和 [刪除] 區段中的其中一個教學課程,然後編輯類別或產品名稱。 然後返回資料夾中的其中一個頁面 SiteMapProvider 。 假設輪詢機制已經過足夠的時間,以記下基礎資料庫的變更,則網站地圖應該更新以顯示新的產品或類別名稱。

摘要

ASP.NET 2.0 s 網站地圖功能包括類別 SiteMap 、一些內建導覽 Web 控制件,以及預期網站地圖資訊保存到 XML 檔案的預設網站地圖提供者。 若要使用來自某些其他來源的網站地圖資訊,例如從資料庫、應用程式架構或遠端 Web 服務,我們需要建立自定義網站地圖提供者。 這牽涉到建立直接或間接衍生自 類別的 SiteMapProvider 類別。

在本教學課程中,我們瞭解如何建立自定義網站地圖提供者,以從應用程式架構中擷取的產品和類別資訊為基礎的網站地圖。 我們的提供者擴充 了 StaticSiteMapProvider 類別,並需要建立 BuildSiteMap 方法來擷取數據、建構網站地圖階層,並在類別層級變數中快取產生的結構。 我們使用 SQL 快取相依性搭配回呼函式,在修改基礎 CategoriesProducts 數據時使快取結構失效。

快樂的程序設計!

深入閱讀

如需本教學課程中所討論之主題的詳細資訊,請參閱下列資源:

關於作者

Scott Mitchell 是 1998 年以來,1998 年與 Microsoft Web 技術合作的 篇 ASP/ASP.NET 書籍和 4GuysFromRolla.com 作者。 Scott 是獨立的顧問、訓練者和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格連到,也可以透過其部落格來存取,網址為 http://ScottOnWriting.NET

特別感謝

本教學課程系列是由許多實用的檢閱者所檢閱。 本教學課程的首席檢閱者是 Dave Gardner、Zack Jones、Teresa Murphy 和 Bernadette 一節。 想要檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行放在 mitchell@4GuysFromRolla.com。