建置自訂的資料庫驅動網站導覽提供者 (C#)
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
資料夾。
圖 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.aspx
、 ProductsByCategory.aspx
和 ProductDetails.aspx
頁面。 這些頁面分別在步驟 2、3 和 4 中完成。 由於本教學課程的主要內容位於網站地圖提供者上,而且過去教學課程涵蓋如何建立這類多頁主版/詳細數據報告,因此我們會急於執行步驟 2 到 4。 如果您需要重新整理,以建立跨越多個頁面的主要/詳細數據報表,請參閱 跨兩頁的主要/詳細數據篩選 教學課程。
步驟 2:顯示類別清單
Default.aspx
開啟 資料夾中的頁面SiteMapProvider
,並將 GridView 從 [工具箱] 拖曳至 Designer,並將其ID
設定為 Categories
。 從 GridView 智慧標記,將它系結至名為 CategoriesDataSource
的新 ObjectDataSource,並加以設定,讓它使用 CategoriesBLL
類別 s GetCategories
方法來擷取其數據。 由於此 GridView 只會顯示類別,且未提供資料修改功能,因此請將 UPDATE、INSERT 和 DELETE 索引卷標的下拉式清單設定為 [無]) (。
圖 4:使用 方法設定 ObjectDataSource 以傳 GetCategories
回類別 (按兩下即可檢視完整大小的影像)
圖 5:將 UPDATE、INSERT 和 DELETE 索引標籤中的 Drop-Down 清單 設定為 ([無]) (按兩下即可檢視完整大小的映像)
完成 [設定數據源精靈] 之後,Visual Studio 會新增 、、CategoryName
Description
、 NumberOfProducts
和 BrochurePath
的 CategoryID
BoundField。 編輯 GridView,使其只包含 CategoryName
和 Description
BoundFields,並將 BoundField s HeaderText
屬性更新CategoryName
為 Category 。
接下來,新增 HyperLinkField 並將它定位為最左邊的字段。 屬性設定為 CategoryID
,DataNavigateUrlFields
並將 DataNavigateUrlFormatString
屬性設定為 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}
。 將 Text
屬性設定為 [檢視產品]。
圖 6:將 HyperLinkField 新增至 Categories
GridView
建立 ObjectDataSource 並自定義 GridView 字段之後,兩個控件宣告式標記看起來會如下所示:
<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkField DataNavigateUrlFields="CategoryID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}"
Text="View Products" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL"></asp:ObjectDataSource>
圖 7 顯示 Default.aspx
透過瀏覽器檢視時。 按兩下類別的 [檢視產品] 連結會帶您前往 ProductsByCategory.aspx?CategoryID=categoryID
,我們將在步驟 3 中建置。
圖 7:每個類別都會連同檢視產品連結一起列出, (按兩下即可檢視全大小的影像)
步驟 3:列出選取的類別產品
ProductsByCategory.aspx
開啟頁面並新增 GridView,並將其命名為 ProductsByCategory
。 從其智能標記中,將 GridView 系結至名為 ProductsByCategoryDataSource
的新 ObjectDataSource。 將 ObjectDataSource 設定為使用 ProductsBLL
類別 s GetProductsByCategoryID(categoryID)
方法,並將下拉式清單設定為 [UPDATE]、[INSERT] 和 [DELETE] 索引卷標的 ([無) ]。
圖 8:使用 ProductsBLL
類別 s GetProductsByCategoryID(categoryID)
方法 (Click 來檢視完整大小的影像)
[設定數據源精靈] 中的最後一個步驟會提示您輸入 categoryID 的參數來源。 由於這項資訊會透過 querystring 字段 CategoryID
傳遞,請從下拉式清單中選取 [QueryString],然後在 [QueryStringField] 文本框中輸入 CategoryID,如圖 9 所示。 按一下 [完成] 以完成精靈。
圖 9:使用 CategoryID
categoryID 參數的 Querystring 欄位 (按兩下即可檢視完整大小的影像)
完成精靈之後,Visual Studio 會將對應的 BoundFields 和 CheckBoxField 新增至 GridView 以取得產品數據欄位。 拿掉、 UnitPrice
和 SupplierName
BoundFields 的所有ProductName
專案。 自定義這三個 BoundFields HeaderText
屬性,分別讀取 Product、Price 和 Supplier。 UnitPrice
將 BoundField 格式化為貨幣。
接下來,新增 HyperLinkField 並將它移至最左邊的位置。 將屬性Text
設定為 [檢視詳細資料],並將其 屬性設定為 ProductID
,並將其 DataNavigateUrlFields
DataNavigateUrlFormatString
屬性設定為 ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}
。
圖 10:新增指向的檢視詳細數據 HyperLinkField ProductDetails.aspx
進行這些自定義之後,GridView 和 ObjectDataSource 的宣告式標記應該如下所示:
<asp:GridView ID="ProductsByCategory" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsByCategoryDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkField DataNavigateUrlFields="ProductID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductDetails.aspx?ProductID={0}"
Text="View Details" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False"
SortExpression="UnitPrice" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
<SelectParameters>
<asp:QueryStringParameter Name="categoryID"
QueryStringField="CategoryID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
返回透過瀏覽器檢視,然後按兩下 [檢視 Default.aspx
產品] 連結,以取得[飲料]。 這會將您帶至 ProductsByCategory.aspx?CategoryID=1
,顯示 Northwind 資料庫中屬於[飲料] 類別之產品的名稱、價格和供應商, (請參閱圖 11) 。 您可以進一步增強此頁面,以包含將用戶傳回類別清單頁面的連結, () Default.aspx
以及顯示所選類別名稱和描述的 DetailsView 或 FormView 控制件。
圖 11:[ 按兩下] 可檢視全大小影像 (顯示 [供應專案名稱]、[價格] 和 [供貨商])
步驟 4:顯示產品詳細數據
最後一個頁面 ProductDetails.aspx
會顯示選取的產品詳細數據。 開啟 ProductDetails.aspx
[詳細數據][檢視],然後將 [工具箱] 拖曳至 Designer。 將 DetailsView 的 ID
屬性設定為 ProductInfo
,並清除其 Height
和 Width
屬性值。 從智慧標記中,將 DetailsView 系結至名為 ProductDataSource
的新 ObjectDataSource,將 ObjectDataSource 設定為從 ProductsBLL
類別 s GetProductByProductID(productID)
方法提取其數據。 如同在步驟 2 和 3 中建立的先前網頁,將 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單設定為 [無] () 。
圖 12:將 ObjectDataSource 設定為使用 GetProductByProductID(productID)
方法 (按兩下即可檢視完整大小的影像)
[設定數據源精靈] 的最後一個步驟會提示 productID 參數的來源。 由於此數據會通過 querystring 欄位 ProductID
,請將下拉式清單設定為 QueryString,並將 QueryStringField 文字框設定為 ProductID。 最後,按兩下 [完成] 按鈕以完成精靈。
圖 13:將 productID 參數設定為從 ProductID
Querystring 欄位提取其值, (按兩下即可檢視完整大小的影像)
完成 [設定數據源精靈] 之後,Visual Studio 會在產品數據欄位的 DetailsView 中建立對應的 BoundFields 和 CheckBoxField。 ProductID
拿掉、 SupplierID
和 CategoryID
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) 。
圖 14:Chai Tea s 供應商、類別、價格和其他資訊會顯示 (按兩下即可檢視全大小的影像)
步驟 5:了解網站地圖提供者的內部工作
網站地圖會以構成階層的 SiteMapNode
實例集合的形式,在網頁伺服器記憶體中表示。 必須只有一個根目錄,所有非根節點都必須只有一個父節點,而且所有節點可能會有任意數目的子節點。 每個 SiteMapNode
物件都代表網站結構中的區段;這些區段通常會有對應的網頁。 因此,類別SiteMapNode
具有、 Url
和 Description
之類的Title
屬性,可提供 所表示區段SiteMapNode
的資訊。 也有一個屬性可唯一 Key
識別 SiteMapNode
階層中的每個 ,以及用來建立此階層 ChildNodes
、 ParentNode
、 NextSibling
、 PreviousSibling
等的屬性。
圖 15 顯示圖 1 中的一般網站地圖結構,但實作詳細數據會以更精細的詳細數據繪製。
圖 15:每個都有 SiteMapNode
屬性,例如 Title
、 Url
、 Key
、 等等 (按單擊即可檢視完整大小的影像)
網站地圖可透過 SiteMap
命名空間中的 System.Web
類別存取。 這個類別 s RootNode
屬性會傳回網站地圖的根 SiteMapNode
實例; CurrentNode
傳回 SiteMapNode
其 Url
屬性符合目前要求之頁面 URL 的 。 ASP.NET 2.0 s 導覽 Web 控件會在內部使用此類別。
SiteMap
存取類別的屬性時,它必須從某些持續性媒體將網站地圖結構串行化為記憶體。 不過,網站地圖串行化邏輯不會硬式編碼到 SiteMap
類別中。 相反地,在運行時間,類別 SiteMap
會決定要用於串行化的月臺地圖 提供者 。 根據預設,會 XmlSiteMapProvider
使用 類別 ,它會從格式正確的 XML 檔案讀取網站地圖結構。 不過,只要稍微工作,我們就可以建立自己的自定義網站地圖提供者。
所有網站地圖提供者都必須衍生自 SiteMapProvider
類別,其中包括網站地圖提供者所需的基本方法和屬性,但省略許多實作詳細數據。 第二個類別 StaticSiteMapProvider
會 SiteMapProvider
擴充 類別,並包含更強固的所需功能實作。 在內部,會將 StaticSiteMapProvider
網站地圖的實體儲存 SiteMapNode
在 Hashtable
中,並提供 方法,例如 AddNode(child, parent)
, RemoveNode(siteMapNode),
並將 Clear()
新增和移除 SiteMapNode
至內部 Hashtable
。 XmlSiteMapProvider
衍生自 StaticSiteMapProvider
。
建立擴充 StaticSiteMapProvider
的自訂網站地圖提供者時,必須覆寫兩個抽象方法: BuildSiteMap
和 GetRootNodeCore
。 BuildSiteMap
,如同其名稱,負責從永續性記憶體載入網站地圖結構,並在記憶體中建構它。 GetRootNodeCore
會傳回網站地圖中的根節點。
Web 應用程式必須先在應用程式的組態中註冊,才能使用網站地圖提供者。 根據預設,類別 XmlSiteMapProvider
會使用名稱 AspNetXmlSiteMapProvider
來註冊。 若要註冊其他網站地圖提供者,請將下列標記新增至 Web.config
:
<configuration>
<system.web>
...
<siteMap defaultProvider="defaultProviderName">
<providers>
<add name="name" type="type" />
</providers>
</siteMap>
</system.web>
</configuration>
名稱值會將人類可讀取的名稱指派給提供者,而類型則指定網站地圖提供者的完整類型名稱。 在建立自定義網站地圖提供者之後,我們將探索步驟 7 中名稱和類型值的具體值。
網站地圖提供者類別會在第一次從 SiteMap
類別存取時具現化,並在 Web 應用程式的存留期內保留在記憶體中。 由於網站地圖提供者只有一個實例可從多個並行網站訪客叫用,因此提供者的方法必須是 安全線程。
基於效能和延展性考慮,請務必快取記憶體內部網站地圖結構,並傳回這個快取結構,而不是每次叫用 方法時 BuildSiteMap
重新建立它。 BuildSiteMap
根據頁面上使用的導覽控件,以及網站地圖結構的深度,可能會針對每位使用者呼叫數次每個頁面要求。 在任何情況下,如果我們未快 BuildSiteMap
取網站地圖結構,則每次叫用網站地圖結構時,都必須從架構重新擷取產品與類別資訊 (,這會導致查詢資料庫) 。 如我們在先前的快取教學課程中所討論,快取的數據可能會過時。 為了對抗這種情況,我們可以使用以時間或 SQL 快取相依性為基礎的到期日。
注意
網站地圖提供者可以選擇性地覆寫 Initialize
方法。 Initialize
會在網站地圖提供者第一次具現化時叫用,並傳遞指派給 中提供者 Web.config
的任何自定義屬性, <add>
例如: <add name="name" type="type" customAttribute="value" />
。 如果您想要允許頁面開發人員指定各種網站地圖提供者相關設定,而不需要修改提供者的程序代碼,這非常有用。 例如,如果我們直接從資料庫讀取類別和產品數據,而不是透過架構,我們可能會想要讓頁面開發人員指定資料庫 連接字串Web.config
,而不是使用提供者程序代碼中的硬式編碼值。 我們將在步驟 6 中建置的自訂網站地圖提供者不會覆寫此方法 Initialize
。 如需使用 方法的Initialize
範例,請參閱 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
是用來快取網站地圖結構。 第一次建構網站地圖時,或在修改基礎數據之後第一次建構時,將會是 null
,root
並建構網站地圖結構。 網站地圖的根節點會在建構程式期間指派給 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
SiteMapNode
Key
。 每個的必要值都必須是唯一SiteMapNode
的。 - s
SiteMapNode
Url
。Url
是選擇性的,但如果提供,則每個SiteMapNode
Url
值都必須是唯一的。 Title
,SiteMapNode
這是必要專案。
方法呼叫會將 AddNode(root)
新增 SiteMapNode
root
至網站地圖作為根目錄。 接下來,會 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=productID
Url
。
注意
具有其CategoryID
資料庫NULL
值的這些產品會分組在屬性設定為 None 且屬性設定為空字串的Url
類別SiteMapNode
Title
之下。 我決定將 設定 Url
為空字串, ProductBLL
因為類別的 GetProductsByCategory(categoryID)
方法目前缺少只傳回具有 NULL
CategoryID
值的這些產品的功能。 此外,我想要示範巡覽控件如何呈現 SiteMapNode
缺少其 Url
屬性值的 。 我們鼓勵您擴充本教學課程,讓 None SiteMapNode
屬性Url
指向 ProductsByCategory.aspx
,但只會顯示具有CategoryID
NULL
值的產品。
建構網站地圖之後,會使用透過 物件對 Categories
和 Products
數據表 AggregateCacheDependency
的 SQL 快取相依性,將任意物件新增至數據快取。 我們在上一個教學課程中探索了 使用 SQL 快取相依性:使用 SQL 快取相依性。 不過,自定義網站地圖提供者會使用我們尚未探索的數據快取 Insert
方法多載。 此多載接受做為其最終輸入參數,這是從快取中移除物件時所呼叫的委派。 具體而言,我們會傳入指向 類別中NorthwindSiteMapProvider
進一步定義的 方法的新CacheItemRemovedCallback
委派OnSiteMapChanged
。
注意
網站地圖的記憶體內部表示會透過類別層級變數 root
快取。 因為自定義網站地圖提供者類別只有一個實例,而且因為該實例會在 Web 應用程式中的所有線程之間共用,所以這個類別變數會做為快取。 方法BuildSiteMap
也會使用數據快取,但只有在 或 Products
數據表中的Categories
基礎資料庫數據變更時,才會收到通知。 請注意,放入數據快取中的值只是目前的日期和時間。 實際的網站地圖數據 不會 放入數據快取中。
方法 BuildSiteMap
會傳回網站地圖的根節點來完成。
其餘的方法相當簡單。 GetRootNodeCore
負責傳回根節點。 由於 BuildSiteMap
會傳回根目錄, GetRootNodeCore
因此只會傳 BuildSiteMap
回 傳回值。 方法會在 OnSiteMapChanged
移除快取項目時設定 root
回 null
。 將根設定回 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.aspx
、 ProductsByCategory.aspx
和 ProductDetails.aspx
頁面 SiteMapProvider
。 從開啟頁面開始,Default.aspx
並將 從 [工具箱] 拖曳SiteMapPath
到 Designer。 SiteMapPath 控制件位於 [工具箱] 的 [瀏覽] 區段中。
圖 16:新增 SiteMapPath 以 Default.aspx
(按兩下即可檢視完整大小的影像)
SiteMapPath 控件會顯示階層連結,指出網站地圖中的目前頁面位置。 我們已在主 版頁面和網站導覽 教學課程中,將 SiteMapPath 新增回主版頁面頂端。
請花點時間透過瀏覽器檢視此頁面。 圖 16 中新增的 SiteMapPath 會使用預設的網站地圖提供者,從 Web.sitemap
提取其數據。 因此,階層鏈接會顯示首頁 > 自定義網站地圖,就像右上角的階層連結一樣。
圖 17:階層連結使用默認網站地圖提供者, (按兩下即可檢視完整大小的影像)
若要在圖 16 中新增 SiteMapPath,請使用我們在步驟 6 中建立的自定義網站地圖提供者,將其 SiteMapProvider
屬性設定為 Northwind,也就是我們在 中Web.config
指派給 NorthwindSiteMapProvider
的名稱。 不幸的是,Designer 會繼續使用預設的網站地圖提供者,但是如果您在進行此屬性變更之後瀏覽頁面,您會看到階層連結現在使用自定義網站地圖提供者。
圖 18:階層鏈接現在使用自訂網站地圖提供者 NorthwindSiteMapProvider
, (按兩下即可檢視完整大小的影像)
SiteMapPath 控制件會在 ProductsByCategory.aspx
和 ProductDetails.aspx
頁面中顯示功能更實用的使用者介面。 將 SiteMapPath 新增至這些頁面,將兩者中的 屬性設定 SiteMapProvider
為 Northwind。 從 Default.aspx
按兩下 [檢視產品] 連結的 [建立者],然後在Chai Tea的 [檢視詳細資料] 連結上。 如圖 19 所示,階層連結包含目前的網站地圖區段, ( Chai Tea ) 及其祖系:飲料和所有類別 。
圖 19:階層連結現在使用自定義網站地圖提供者 NorthwindSiteMapProvider
, (按兩下即可檢視完整大小的影像)
除了 SiteMapPath 之外,也可以使用其他導覽使用者介面元素,例如 Menu 和 TreeView 控制件。 Default.aspx
本教學課程下載中的、 ProductsByCategory.aspx
和 ProductDetails.aspx
頁面,例如,所有包含功能表控件 (請參閱圖 20) 。 如需 ASP.NET 2.0 中流覽控件和網站導覽系統的深入探討,請參閱 ASP.NET 2.0 快速入門中的 ASP.NET 2.0複雜網站導覽功能和網站導覽控件一節。
圖 20:功能表控件 清單 每個類別和產品 (按單擊即可檢視完整大小的影像)
如本教學課程稍早所述,網站地圖結構可以透過 SiteMap
類別以程式設計方式存取。 下列程式代碼會傳回預設提供者的根 SiteMapNode
目錄:
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.aspx
、 ProductsByCategory.aspx
和 ProductDetails.aspx
頁面之後,請移至 [編輯]、[插入] 和 [刪除] 區段中的其中一個教學課程,然後編輯類別或產品名稱。 然後返回資料夾中的其中一個頁面 SiteMapProvider
。 假設輪詢機制已經過足夠的時間,以記下基礎資料庫的變更,則網站地圖應該更新以顯示新的產品或類別名稱。
摘要
ASP.NET 2.0 s 網站地圖功能包括類別 SiteMap
、一些內建導覽 Web 控制件,以及預期網站地圖資訊保存到 XML 檔案的預設網站地圖提供者。 若要使用來自某些其他來源的網站地圖資訊,例如從資料庫、應用程式架構或遠端 Web 服務,我們需要建立自定義網站地圖提供者。 這牽涉到建立直接或間接衍生自 類別的 SiteMapProvider
類別。
在本教學課程中,我們瞭解如何建立自定義網站地圖提供者,以從應用程式架構中擷取的產品和類別資訊為基礎的網站地圖。 我們的提供者擴充 了 StaticSiteMapProvider
類別,並需要建立 BuildSiteMap
方法來擷取數據、建構網站地圖階層,並在類別層級變數中快取產生的結構。 我們使用 SQL 快取相依性搭配回呼函式,在修改基礎 Categories
或 Products
數據時使快取結構失效。
快樂的程序設計!
深入閱讀
如需本教學課程中所討論之主題的詳細資訊,請參閱下列資源:
關於作者
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。
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應