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

作者 :Scott Mitchell

下載 PDF

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

簡介

ASP.NET 2.0 s 網站地圖功能可讓頁面開發人員在某些持續性媒體中定義 Web 應用程式網站地圖,例如在 XML 檔案中。 定義之後,即可透過命名空間中的 System.Web 類別,或透過SiteMap各種導覽 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 將網站地圖儲存在 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:網站地圖現在包含網站地圖提供者教學課程的專案

本教學課程主要著重於說明如何建立自定義網站地圖提供者,以及設定 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 會新增 、、CategoryNameDescriptionNumberOfProductsBrochurePathCategoryIDBoundField。 編輯 GridView,使其只包含 CategoryNameDescription BoundFields,並將 BoundField s HeaderText 屬性更新CategoryName為 Category 。

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

將 HyperLinkField 新增至 Categories 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) 方法 (Click 來檢視完整大小的影像)

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

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

圖 9:使用 CategoryIDcategoryID 參數的 Querystring 欄位 (按兩下即可檢視完整大小的影像)

完成精靈之後,Visual Studio 會將對應的 BoundFields 和 CheckBoxField 新增至 GridView 以取得產品數據欄位。 拿掉、 UnitPriceSupplierName BoundFields 的所有ProductName專案。 自定義這三個 BoundFields HeaderText 屬性,分別讀取 Product、Price 和 Supplier。 UnitPrice將 BoundField 格式化為貨幣。

接下來,新增 HyperLinkField 並將它移至最左邊的位置。 將屬性Text設定為 [檢視詳細資料],並將其 屬性設定為 ProductID,並將其 DataNavigateUrlFieldsDataNavigateUrlFormatString 屬性設定為 ~/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 中建立的先前網頁,將 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單設定為 [無] () 。

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

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

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

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

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

完成 [設定數據源精靈] 之後,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 ,然後按兩下 [檢視產品] 的 [選擇] 類別。 從食物產品清單中,按兩下 Chai Tea 的 [檢視詳細資料] 連結。 這會帶您前往 ProductDetails.aspx?ProductID=1,其中顯示 Chai Tea 的詳細數據 (請參閱圖 14) 。

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

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

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

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

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

每個 SiteMapNode 都有屬性,例如 Title、Url、Key 等等

圖 15:每個都有 SiteMapNode 屬性,例如 TitleUrlKey、 等等 (按單擊即可檢視完整大小的影像)

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

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

所有網站地圖提供者都必須衍生自 SiteMapProvider 類別,其中包括網站地圖提供者所需的基本方法和屬性,但省略許多實作詳細數據。 第二個類別 StaticSiteMapProviderSiteMapProvider 擴充 類別,並包含更強固的所需功能實作。 在內部,會將 StaticSiteMapProvider 網站地圖的實體儲存 SiteMapNodeHashtable 中,並提供 方法,例如 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範例,請參閱 SQL Server 文章中的 Jeff Prosise儲存網站地圖一文。

步驟 6:建立自訂網站地圖提供者

若要建立自定義網站地圖提供者,以從 Northwind 資料庫中的類別和產品建置網站地圖,我們必須建立擴充 的 StaticSiteMapProvider類別。 在步驟 1 中,我要求您在資料夾中新增資料夾 App_Code - 將新的類別新增CustomProviders至名為 的NorthwindSiteMapProvider這個資料夾。 將下列程式碼新增至 NorthwindSiteMapProvider 類別:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
public class NorthwindSiteMapProvider : StaticSiteMapProvider
{
    private readonly object siteMapLock = new object();
    private SiteMapNode root = null;
    public const string CacheDependencyKey = 
        "NorthwindSiteMapProviderCacheDependency";
    public override SiteMapNode BuildSiteMap()
    {
        // Use a lock to make this method thread-safe
        lock (siteMapLock)
        {
            // First, see if we already have constructed the
            // rootNode. If so, return it...
            if (root != null)
                return root;
            // We need to build the site map!
            
            // Clear out the current site map structure
            base.Clear();
            // Get the categories and products information from the database
            ProductsBLL productsAPI = new ProductsBLL();
            Northwind.ProductsDataTable products = productsAPI.GetProducts();
            // Create the root SiteMapNode
            root = new SiteMapNode(
                this, "root", "~/SiteMapProvider/Default.aspx", "All Categories");
            AddNode(root);
            // Create SiteMapNodes for the categories and products
            foreach (Northwind.ProductsRow product in products)
            {
                // Add a new category SiteMapNode, if needed
                string categoryKey, categoryName;
                bool createUrlForCategoryNode = true;
                if (product.IsCategoryIDNull())
                {
                    categoryKey = "Category:None";
                    categoryName = "None";
                    createUrlForCategoryNode = false;
                }
                else
                {
                    categoryKey = string.Concat("Category:", product.CategoryID);
                    categoryName = product.CategoryName;
                }
                SiteMapNode categoryNode = FindSiteMapNodeFromKey(categoryKey);
                // Add the category SiteMapNode if it does not exist
                if (categoryNode == null)
                {
                    string productsByCategoryUrl = string.Empty;
                    if (createUrlForCategoryNode)
                        productsByCategoryUrl = 
                            "~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=" 
                            + product.CategoryID;
                    categoryNode = new SiteMapNode(
                        this, categoryKey, productsByCategoryUrl, categoryName);
                    AddNode(categoryNode, root);
                }
                // Add the product SiteMapNode
                string productUrl = 
                    "~/SiteMapProvider/ProductDetails.aspx?ProductID=" 
                    + product.ProductID;
                SiteMapNode productNode = new SiteMapNode(
                    this, string.Concat("Product:", product.ProductID), 
                    productUrl, product.ProductName);
                AddNode(productNode, categoryNode);
            }
            
            // Add a "dummy" item to the cache using a SqlCacheDependency
            // on the Products and Categories tables
            System.Web.Caching.SqlCacheDependency productsTableDependency = 
                new System.Web.Caching.SqlCacheDependency("NorthwindDB", "Products");
            System.Web.Caching.SqlCacheDependency categoriesTableDependency = 
                new System.Web.Caching.SqlCacheDependency("NorthwindDB", "Categories");
            // Create an AggregateCacheDependency
            System.Web.Caching.AggregateCacheDependency aggregateDependencies = 
                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, 
                new CacheItemRemovedCallback(OnSiteMapChanged));
            // Finally, return the root node
            return root;
        }
    }
    protected override SiteMapNode GetRootNodeCore()
    {
        return BuildSiteMap();
    }
    protected void OnSiteMapChanged(string key, object value, CacheItemRemovedReason reason)
    {
        lock (siteMapLock)
        {
            if (string.Compare(key, CacheDependencyKey) == 0)
            {
                // Refresh the site map
                root = null;
            }
        }
    }
    public DateTime? CachedDate
    {
        get
        {
            return HttpRuntime.Cache[CacheDependencyKey] as DateTime?;
        }
    }
}

讓我們從探索這個類別的 BuildSiteMap 方法開始,其開頭為 lock 語句。 語句 lock 一次只允許一個線程輸入,藉此串行化其程式代碼的存取權,並防止兩個並行線程彼此逐步執行。

類別層級 SiteMapNode 變數 root 是用來快取網站地圖結構。 第一次建構網站地圖時,或在修改基礎數據之後第一次建構時,將會是 nullroot並建構網站地圖結構。 網站地圖的根節點會在建構程式期間指派給 root ,以便下次呼叫這個方法時, root 不會是 null。 因此,只要 root 不是 null 網站地圖結構,就會傳回給呼叫端,而不需要重新建立它。

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

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

  • 網站地圖提供者的參考 (this) 。
  • s SiteMapNodeKey。 每個的必要值都必須是唯一 SiteMapNode的。
  • s SiteMapNodeUrlUrl是選擇性的,但如果提供,則每個SiteMapNodeUrl值都必須是唯一的。
  • TitleSiteMapNode這是必要專案。

方法呼叫會將 AddNode(root) 新增 SiteMapNoderoot 至網站地圖作為根目錄。 接下來,會 ProductRow 列舉 中的每個 ProductsDataTable 。 如果目前產品類別已有, SiteMapNode 則會參考它。 否則,會建立類別的新 SiteMapNode ,並透過 AddNode(categoryNode, root) 方法呼叫新增為 的SiteMapNode``root子系。 找到或建立適當的類別SiteMapNode節點之後,會為目前產品建立 ,SiteMapNode並透過 AddNode(productNode, categoryNode)新增為類別SiteMapNode的子系。 請注意,類別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 屬性Url指向 ProductsByCategory.aspx,但只會顯示具有CategoryIDNULL值的產品。

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

注意

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

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

其餘的方法相當簡單。 GetRootNodeCore 負責傳回根節點。 由於 BuildSiteMap 會傳回根目錄, GetRootNodeCore 因此只會傳 BuildSiteMap 回 傳回值。 方法會在 OnSiteMapChanged 移除快取項目時設定 rootnull 。 將根設定回 null時,下次 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 目錄:

SiteMapNode root = SiteMap.RootNode;

AspNetXmlSiteMapProvider由於是應用程式的預設提供者,因此上述程式代碼會傳回 中Web.sitemap定義的根節點。 若要參考預設以外的網站地圖提供者,請使用 SiteMap 類別的 Providers 屬性 ,如下所示:

SiteMapNode root = SiteMap.Providers["name"].RootNode;

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

若要存取網站地圖提供者特定的成員,請使用 SiteMap.Providers["name"] 來擷取提供者實例,然後將它轉換成適當的類型。 例如,若要在 NorthwindSiteMapProvider ASP.NET 頁面中顯示 s CachedDate 屬性,請使用下列程式代碼:

NorthwindSiteMapProvider customProvider = 
    SiteMap.Providers["Northwind"] as NorthwindSiteMapProvider;
if (customProvider != null)
{
    DateTime? lastCachedDate = customProvider.CachedDate;
    if (lastCachedDate != null)
        LabelID.Text = "Site map cached on: " + lastCachedDate.Value.ToString();
    else
        LabelID.Text = "The site map is being reconstructed!";
}

注意

請務必測試 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。