共用方式為


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

由斯科特· 米切爾

下載 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 been 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 資料夾。

新增網站地圖提供者相關教學課程的 ASP.NET 頁面

圖 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,這會列出資料庫中的所有類別。 網站地圖中的每個類別節點都會有指向 的 URL ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID,這會列出指定 categoryID 中的所有產品。 最後,每個產品網站地圖節點都會指向 ~/SiteMapProvider/ProductDetails.aspx?ProductID=productID,這會顯示特定產品的詳細數據。

若要開始,我們需要建立 Default.aspxProductsByCategory.aspxProductDetails.aspx 頁面。 這些頁面分別在步驟 2、3 和 4 中完成。 由於本教學課程的推力位於網站地圖提供者上,而且因為過去的教學課程涵蓋如何建立這類多頁主版/詳細數據報告,因此我們會匆忙完成步驟 2 到 4。 如果您需要建立跨越多個頁面的主要/詳細數據報表的重新整理程式,請參閱 跨兩頁 的主要/詳細數據篩選教學課程。

步驟 2:顯示類別清單

Default.aspx開啟資料夾中的頁面SiteMapProvider,並將 GridView 從 [工具箱] 拖曳至設計工具,並將其ID設定為 Categories。 從 GridView 的智慧標記,將它系結至名為 CategoriesDataSource 的新 ObjectDataSource,並加以設定,讓它使用 CategoriesBLL 類別 s GetCategories 方法來擷取其數據。 由於此 GridView 只會顯示類別,且未提供資料修改功能,因此請將 UPDATE、INSERT 和 DELETE 索引卷標的下拉式清單設定為 [無]。

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

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

將 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單設定為 [無]

圖 5:將 UPDATE、INSERT 和 DELETE 索引標籤標的下拉式清單設定為 [無] (按兩下以檢視完整大小的影像

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

接下來,新增 HyperLinkField 並將它定位為最左邊的字段。 屬性 DataNavigateUrlFields 設定為 CategoryID ,並將 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) 方法(按兩下以檢視完整大小的影像

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

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

圖 9:使用 CategoryID categoryID 參數的 Querystring 欄位 (按兩下以檢視完整大小的影像

完成精靈之後,Visual Studio 會將對應的 BoundFields 和 CheckBoxField 新增至 Product 數據欄位的 GridView。 拿掉、 UnitPriceSupplierName 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 並將DetailsView 從工具箱拖曳至設計工具。 將 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 然後按兩下 [檢視飲料] 類別的產品。 從飲料產品清單中,按下柴茶的 [檢視詳細數據] 連結。 這會帶您前往 ProductDetails.aspx?ProductID=1,其中顯示柴茶的詳細數據(請參閱圖 14)。

柴茶供應商、類別、價格及其他信息隨即顯示

圖 14:柴茶供貨商、類別、價格及其他資訊隨即顯示(按兩下以檢視全尺寸影像

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

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

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

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

圖 15:每個都有 SiteMapNode 屬性,例如 TitleUrlKey等 (按兩下以檢視完整大小的影像

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

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

所有網站地圖提供者都必須衍生自 SiteMapProvider 類別,其中包含網站地圖提供者所需的基本方法和屬性,但省略許多實作詳細數據。 第二個類別 StaticSiteMapProvider會擴充 類別, SiteMapProvider 並包含更健全的所需功能實作。 在內部,會將 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 Prosise s Storing Site Maps in SQL Server 一文。

步驟 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 可用來快取網站地圖結構。 第一次建構網站地圖時,或修改基礎數據之後的第一次, root 將會 null 是 ,並建構網站地圖結構。 網站地圖的根節點會在建構程式期間指派給 root ,因此下次呼叫此方法時, root 將不會是 null。 因此,只要 root 不是 null 網站地圖結構就會傳回給呼叫者,而不需要重新建立它。

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

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

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

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

注意

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

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

注意

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

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

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

步驟 7:註冊NorthwindSiteMapProvider

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

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

此標記會執行兩件事:第一,表示內 AspNetXmlSiteMapProvider 建為默認網站地圖提供者;第二,它會使用人類易記名稱 Northwind 註冊在步驟 6 中建立的自定義網站地圖提供者。

注意

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

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

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

在 中 Web.config建立並註冊自定義網站地圖提供者之後,我們即可將導覽控件新增至資料夾中的 Default.aspxProductsByCategory.aspxProductDetails.aspx 頁面 SiteMapProvider 。 從開啟頁面開始, Default.aspx 並將 從 [工具箱] 拖曳 SiteMapPath 至設計工具。 SiteMapPath 控制件位於 [工具箱] 的 [導覽] 區段中。

將 SiteMapPath 新增至 Default.aspx

圖 16:將 SiteMapPath 新增至 Default.aspx按兩下以檢視完整大小的影像

SiteMapPath 控件會顯示階層連結,指出網站地圖中目前頁面的位置。 我們在主版頁面和網站導覽教學課程中,將 SiteMapPath 新增至主版頁面頂端。

請花點時間透過瀏覽器檢視此頁面。 圖 16 中新增的 SiteMapPath 會使用預設的網站地圖提供者,從 Web.sitemap提取其數據。 因此,階層鏈接會顯示首頁 > 自定義網站地圖,就像右上角的階層連結一樣。

階層連結使用默認網站地圖提供者

圖 17:階層連結使用預設網站地圖提供者(按兩下以檢視完整大小的影像

若要在圖 16 中新增 SiteMapPath,請使用我們在步驟 6 中建立的自定義網站地圖提供者,將其 SiteMapProvider 屬性 設定為 Northwind,也就是我們指派給 NorthwindSiteMapProvider 中的 Web.config名稱。 不幸的是,設計師會繼續使用預設的網站地圖提供者,但是如果您在進行此屬性變更之後瀏覽頁面,您會看到階層連結現在使用自定義網站地圖提供者。

顯示階層連結如何顯示自定義網站地圖提供者的螢幕快照。

圖 18:階層連結現在使用自訂網站地圖提供者 NorthwindSiteMapProvider按兩下以檢視完整大小的影像

SiteMapPath 控制件會在 ProductsByCategory.aspxProductDetails.aspx 頁面中顯示功能更實用的使用者介面。 將 SiteMapPath 新增至這些頁面,將 兩者中的 屬性設定 SiteMapProvider 為 Northwind。 從 Default.aspx 單擊 [檢視飲料的產品] 鏈接,然後在 Chai Tea 的 [檢視詳細數據] 連結上。 如圖 19 所示,階層連結包括目前的網站地圖區段(柴茶)及其祖先:飲料和所有類別。

顯示階層連結如何顯示目前網站地圖區段 (柴茶) 及其祖先 (飲料和所有類別) 的螢幕快照。

圖 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"] 來擷取提供者實例,然後將它轉換成適當的類型。 例如,若要在 ASP.NET 頁面中顯示 NorthwindSiteMapProvider 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 數據時使快取結構失效。

快樂的程序設計!

深入閱讀

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

關於作者

斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是 山姆斯在24小時內 ASP.NET 2.0。 他可以到達 mitchell@4GuysFromRolla.com, 或通過他的博客,可以在 找到 http://ScottOnWriting.NET

特別感謝

本教學課程系列已由許多實用的檢閱者檢閱。 本教學課程的主要檢閱者是戴夫·加德納、紮克·鐘斯、特蕾莎·墨菲和伯納黛特·利。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請將一行 mitchell@4GuysFromRolla.com放在 。