程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> ASP.NET >> ASP.NET基礎 >> 在ASP.NET 2.0中操作數據之六十:創建一個自定義的Database-Driven Site Map Provider

在ASP.NET 2.0中操作數據之六十:創建一個自定義的Database-Driven Site Map Provider

編輯:ASP.NET基礎

導言:

  ASP.NET 2.0的網站地圖(site map)功能允許頁面開發者在一些持久介質(persistent medium),比如一個XML文件裡,自己定義一個web程序的site map.一旦定義了之後,我們可以通過System.Web命名空間的SiteMap class類或某個Web導航控件,比如SiteMapPath, Menu, 或TreeView來對其進行訪問。site map系統使用的是provider model模式,所以可以創建不同的site map,並將其應用到一個web應用程序。ASP.NET 2.0默認的site map provider,其結構為一個XML文件。在教程《Master Pages and Site Navigation》裡我們創建了一個Web.sitemap文件,它就包含了這種結構,並且在教程的每一個新部分裡我們都要更新其XML.

  當site map的結構是靜態的時候,默認的這種基於XML(XML-based)的site map provider工作正常,就像本系列教程一樣。但是在很多時候我們需要動態的site map.如圖1的site map,每個種類以及屬於該種類的產品在網站的結構裡做層次狀體系分布。在該site map裡,當訪問根目錄的web頁面時,將列出所有的種類;再訪問某個具體的種類的根目錄時,將列出屬於該種類的所有產品;再訪問某個具體的產品時將列出該產品的詳細信息。

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401584.gif
圖1:Categories 和 Products構成了Site Map的層次結構

  這種基於category 和product的結構可以通過"硬編碼"的方式添加到Web.sitemap文件.每當對category 或 product進行添加、刪除、重命名等操作時,都需要對該文件進行更新。很自然的,如果其結構是通過數據庫,或更理想地,是從業務邏輯層來獲取的,那麼對site map的維護是很簡單的。那樣的話,只要對products 和 categories進行添加、刪除、重命名時,site map會自動的更新以反應這些變化。

  由於ASP.NET 2.0的site map是建立在provider模式的基礎上的,因此我們可以創建一個自定義的site map provider,從數據庫或某個層來獲取數據.在本文,我們創建的provider將從業務邏輯層獲取數據。讓我們開始吧!

  注意:本文創建的用戶定制site map provider僅僅依賴於系統的層及其數據模式(data model)。Jeff Prosise的文章《Storing Site Maps in SQL Server》(http://msdn.microsoft.com/msdnmag/issues/05/06/WickedCode/)
和《The SQL Site Map Provider You've Been Waiting For》
(http://msdn.microsoft.com/msdnmag/issues/06/02/wickedcode/default.aspx)
考察了將site map數據存儲在SQL Server的方法。

第一步:創建用戶定制Site Map Provider頁面

在創建用戶定制Site Map Provider之前,先添加本章將用到的ASP.NET頁面。首先添加一個名為SiteMapProvider的文件夾;然後在文件夾裡添加如下所示的頁面確保采用母版Site.master:

Default.aspx
ProductsByCategory.aspx
ProductDetails.aspx

同樣,在App_Code文件夾裡添加CustomProviders

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401693.gif
圖2:添加相關的ASP.NET頁面.

由於這部分只有一篇文章,沒有必要使Default.aspx頁面列出本部分的文章;我們將在Default.aspx裡用一個GridView控件來列出categories,在第二步裡探討.

然後,更新Web.sitemap使其包含對Default.aspx頁面的引用。特別的,在“Caching” <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的更新後,花點時間在浏覽器裡登錄頁面,在左面的菜單裡包含了本教程的條目。

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401682.gif
圖3:Site Map現在包含了本章的條目

  本教程主要考察如何創建一個用戶自定義的site map provider,及對設置web應用程序進行包含該site map provider.具體來講,它返回的網站地圖(site map)不僅包含了根節點,而且包含每個種類節點和產品節點,就像圖1顯示的那樣。總的來說,網站地圖裡的每一個節點都對應一個具體的URL.就我們的網站地圖而言,根節點的URL為~/SiteMapProvider/Default.aspx,它列出了所有產品和種類;每個種類節點對應的URL 為~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID,它根據指定的categoryID列出該種類的所有產品;最後,每個產品對應的URL為~/SiteMapProvider/ProductDetails.aspx?ProductID=productID, 它根據指定的productID值,列出該產品的詳細信息。

  首先,讓我們創建Default.aspx, ProductsByCategory.aspx和ProductDetails.aspx頁面。我們將分別在第二、三、四步創建這些頁面.因為本文的重點是site map providers,並且這種主/從頁面在前面的教程裡已經討論過了,我們在第2到第4步將一筆帶過,如果你對這種主/從頁面頁面不是很了解的話,請參考前面的教程之9《Master/Detail Filtering Across Two Pages》.

第二步:將Categories顯示出來

  打開文件夾SiteMapProvider裡的Default.aspx頁面,在設計模式裡從工具箱拖一個 GridView控件到頁面,設置其ID為Categories.從其智能標簽裡,將其綁定到一個名為CategoriesDataSource的ObjectDataSource,設置其使用CategoriesBLL類的 GetCategories方法。因為該GridView控件只是顯示categories而不修改數據,因此在UPDATE, INSERT和 DELETE標簽裡選“(None)”.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401614.gif
圖4:設置ObjectDataSource使用GetCategories方法返回Categories

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401720.gif
圖5:在UPDATE, INSERT和DELETE標簽裡選“(None)”

  設置完成後,Visual Studio會自動的添加CategoryID, CategoryName, Description, NumberOfProducts 和 BrochurePath這些綁定列(BoundField),修改GridView,使其只包含CategoryName 和 Description兩列,且將CategoryName綁定列的HeaderText屬性改為“Category”.

  然後,添加一個HyperLinkField,將其放在最左邊,設其DataNavigateUrlFields屬性為 CategoryID;DataNavigateUrlFormatString 屬性為 ~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0};再將Text屬性設置為“View Products”.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401739.gif
圖6:為GridView添加一個HyperLinkField

創建完ObjectDataSource並定制GridView控件的列後,這2個控件的聲明代碼看起來應該和下面的差不多:

<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頁面,點某個類的“View Products”鏈接,將會轉到ProductsByCategory.aspx?CategoryID=categoryID頁面,該頁面我們將在第三步新建。

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401875.gif
圖7:每個類都有一個“View Products”鏈接

第三步:顯示指定類的所有產品

  打開ProductsByCategory.aspx頁面並添加一個GridView控件,設其ID為ProductsByCategory.從其智能標簽,將其綁定到一個名為ProductsByCategoryDataSource的ObjectDataSource;設置它使用ProductsBLL類的 GetProductsByCategoryID(categoryID)方法;在UPDATE, INSERT,和 DELETE標簽裡選擇“(None)”.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401834.gif
圖8:使用ProductsBLL類的GetProductsByCategoryID(categoryID)方法

  設置向導的最後一步是指定categoryID的參數來源,因為此信息是通過查詢字符串(querystring field)CategoryID來傳遞的,因此在參數來源裡選QueryString,在QueryStringField裡輸入“CategoryID”;如圖9所示,點Finish完成設置.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916401940.gif
圖9:為參數categoryID指定CategoryID Querystring Field

  完成設置後,Visual Studio將為GridView添加相應的綁定列以及CheckBo列;將除ProductName, UnitPrice, SupplierName外的列刪除掉。將這3個列的HeaderText屬性分別設置為“Product”, “Price”, and “Supplier”, 將UnitPrice列格式化為貨幣形式.

  然後,添加一個HyperLinkField列,並將其放在最左邊;設其Text屬性為“View Details”,設其DataNavigateUrlFields屬性為ProductID;其DataNavigateUrlFormatString屬性為 ~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916402072.gif
圖10:添加一個“View Details” 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頁面,點Beverages(飲料)的“View Products”鏈接,這將轉到ProductsByCategory.aspx?CategoryID=1頁面,顯示飲料類的所有產品的names, prices, 和 suppliers信息(見圖11)。盡管改進該頁面吧,添加一個鏈接以方便用戶返回上一頁(Default.aspx) .還可以添加一個DetailsView 或 FormView控件來顯示該種類的名稱和描述。

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916402030.gif
圖11:顯示Beverages類的Names, Prices, Suppliers信息

第四步:顯示產品的詳細信息

  最後要創建的頁面—ProductDetails.aspx,是用來顯示指定產品的詳細信息的。打開ProductDetails.aspx頁面,從工具箱拖一個DetailsView控件到頁面,設置其ID為ProductInfo,並清除其Height 和 Width屬性值。在其智能標簽裡,綁定到一個名為ProductDataSource的ObjectDataSource,設置該ObjectDataSource使用ProductsBLL類的GetProductByProductID(productID)方法。在UPDATE, INSERT,和DELETE標簽裡選“(None)”.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916402784.gif
圖12:設置該ObjectDataSource控件調用GetProductByProductID(productID)方法

  最後,需要設置參數productID的來源,由於數據通過查詢字符串ProductID來傳遞,在參數源下拉列表裡選QueryString,在QueryStringField對話框裡輸入“ProductID”. 最後,點Finish按鈕完成設置。

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916403123.gif
圖13:設置參數productID來源於查詢字符串

  完成設置後,Visual Studio會為DetailsView控件添加相應的綁定列和CheckBox列,移除ProductID, SupplierID, 和CategoryID列,剩下的列想怎樣設就怎樣設置吧。我對界面做了些優化,這樣的話,聲明代碼看起來像下面這樣:

<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頁面,點種類Beverages的“View Products”鏈接;再點產品Chai Tea的“View Details”鏈接。這將轉到ProductDetails.aspx?ProductID=1頁面,其顯示的是Chai Tea的詳細信息(如圖14所示).

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916403161.gif
圖14:Chai Tea的Supplier, Category, Price等信息顯示出來了

第五步:理解Site Map Provider的內部處理機制

  site map呈現的是源於某種層次結構的SiteMapNode實例集(a  collection of SiteMapNode instances)。其必須有一個根節點,所有的非根節點都有一個父節點,且每個節點都可以有任意數量的子節點.每個SiteMapNode對象對應的是website體系結構的某個部分。這些部分通常都有對應的web頁面,因此,SiteMapNode class類有像Title, Url, 和 Description這樣的屬性,它們用來描述SiteMapNode所對應部分的相關信息。
還有一個Key屬性用來專門唯一的標識這些SiteMapNode;除此以外,還有ChildNodes, ParentNode, NextSibling, PreviousSibling等等.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916403262.gif
圖15顯示的是對應於圖1的site map的總體結構,只是更細化了而已.

   可以通過命名空間System.Web的SiteMap class類來訪問site map;該類的RootNode屬性返回網站地圖的根目錄的SiteMapNode實例;CurrentNode屬性返回的是這種SiteMapNode,其Url屬性剛好與當前請求頁面的URL匹配.ASP.NET 2.0的Web導航控件的內部就會用到SiteMap class類.

  當訪問SiteMap class類的屬性時,必須將網站地圖的層次結構從某個介質傳入內存(memory).SiteMap class類並不是通過“硬編碼”的方式來處理網站地圖的邏輯關系,而是通過某種site map provider來工作.在默認情況下,使用的是XmlSiteMapProvider class類,它從一個標准的XML文件讀取網站地圖的結構.不過,我們稍微做點工作就可以創建自己的site map provider.

  所有的site map providers都繼承自SiteMapProvider class類,該類包含了site map providers要用到的最基本的方法和屬性,不過略去了很多執行細節.site map providers要用到的第二個類是StaticSiteMapProvider class類,它對SiteMapProvider class類進行了擴充,包含了更多的必要的函數.在其內部,StaticSiteMapProvider將網站地圖的SiteMapNode實例存儲在一個哈希表(Hashtable)裡,並包含了AddNode(child, parent), RemoveNode(siteMapNode), Clear()等方法,以對哈希表裡的SiteMapNodes執行添加、刪除等操作.另外,XmlSiteMapProvider也繼承自StaticSiteMapProvider.

  當創建自定義的site map provider時,要對StaticSiteMapProvider進行擴充,重寫(overrid)2個抽象方法——BuildSiteMap 和 GetRootNodeCore. 對BuildSiteMap而言,就像它的名字暗示的那樣,將網站地圖的結構從某種介質裡按層次結構裝載進內存;而GetRootNodeCore返回的是網站地圖的根目錄.

  在使用某個site map provider時,需要在應用程序的配置文件裡進行注冊(registered)
.默認情況下,XmlSiteMapProvider class類被注冊為AspNetXmlSiteMapProvider.為對額外添加的site map providers進行注冊,可以在Web.config文件裡添加如下的代碼:

<configuration>
 <system.web>
 ...

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

  name屬性可以為site map provider指派一個易讀的名稱;type屬性決定了該site map provider的類型.當創建完我們定制的site map provider後,我們將在第七步為name 和type屬性賦值.

  當第一次從SiteMap class類訪問site map provider時,site map provider class類都應該被實例化,並在web應用程序的整個生命周期裡都駐留在內存.

  基於性能等方面的考慮,我們應該將對駐留在內存裡的網站地圖結構進行數據緩存,每次調用BuildSiteMap的方法時,直接返回緩存的數據而不用重新檢索數據.在任何情況下,如果我們不對BuildSiteMap對應的網站結構進行緩存的話,每次調用時,我們都需要通過“層”來重新檢索產品和種類的信息(這將最終導致對數據庫的查詢).我們在前面的緩存章節探討過緩存數據“過時”的問題,為此,我們要麼使用基於時間,要麼使用基於SQL cache dependency的緩存技術.

  注意:一個site map provider可以任意地重寫(override)Initialize method方法.Initialize 方法是當site map provider第一次實例化的時候被調用的,並可以將我們在Web.config 文件的<add>元素裡賦值的用戶自定義屬性值傳遞給它,比如:<add name="name" type="type" customAttribute="value" />.當一個頁面開發者希望指定各種與site map provider相關的設置,而又不希望修改site map provider的代碼的時候,這樣做很有用.比如,假如我們希望不通過“層”而直接從數據庫讀取category 和 products的數據時,我們當然希望頁面開發者調用Web.config文件裡的數據庫連接字符串,而不使用site map provider代碼裡的“硬編碼”值.我們不打算在第六步創建的自定義site map provider裡重寫Initialize方法.見Jeff Prosise的文章《Storing Site Maps in SQL Server》(http://msdn.microsoft.com/msdnmag/issues/05/06/WickedCode/)

第六步:創建自定義的Site Map Provider

  要想創建一個自定義的site map provider來構建源於Northwind數據庫裡的categories 和 products信息的網站地圖(site map),我們需要創建一個類來擴展StaticSiteMapProvider.在前面我們在App_Code文件夾裡添加了一個CustomProviders文件夾,在該文件夾裡添加名為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 statement聲明。lock statement每次只允許“單線程操作”(one thread at a time to enter),以避免“多線程操作”之間的沖突.

  屬於“類級別”(class-level)的SiteMapNode變量—root,用來緩存網站地圖結構.當網站地圖第一次被“結構化”,或“源數據”發生變動後的第一次“結構化”時,root為null值,在“結構化”的過程中,root被賦值為網站地圖的根節點;所以,當第二次調用BuildSiteMap方法時,root就不為null值了.自然,只要root不為null,直接將網站地圖結構返回,而用不著重新創建.

  如果root為null,那麼將根據product 和 category信息創建網站地圖結構.為此,先要創建一個SiteMapNode實例,再調用StaticSiteMapProvider class類的AddNode method方法來構建網站地圖的層次體系,再將SiteMapNode實例存儲進一個哈希表.在我們構建層次體系之前,我們首先調用Clear method方法,將內部的哈希表清空;然後,調用ProductsBLL class類的GetProducts()方法,把返回的ProductsDataTable存儲進局部變量.

  創建網站地圖結構從創建根節點並賦值給root開始,本章要用到的SiteMapNode's constructor重載,接受如下的信息:

對一個site map provider (this)的引用.

SiteMapNode的Key值:對每個SiteMapNode而言,這個待定值必須是唯一的.

SiteMapNode的Url值:Url為可選項,但一旦指定的話,每個SiteMapNode的Url值必須是唯一的.

SiteMapNode的Title值:此為必選項.

  AddNode(root) method方法將SiteMapNode root添加給網站地圖作為根節點。然後,遍歷ProductsDataTable裡的所有ProductRow,如果當前product的category所對應的SiteMapNode已經存在的話,那麼引用該SiteMapNode;如果不存在的話,則為該category創建一個新的SiteMapNode,並且調用AddNode(categoryNode, root) method方法,將其作為SiteMapNode root的子節點進行添加.當找到或創建category對應的SiteMapNode後,創建一個當前product對應的SiteMapNode,並通過AddNode(productNode, categoryNode)方法將其作為category SiteMapNode的子節點進行添加.注意,category SiteMapNode的Url屬性為~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID;而product SiteMapNode的Url屬性為~/SiteMapNode/ProductDetails.aspx?ProductID=productID.

  注意:對那種CategoryID為NULL值的產品,統統將其歸為一個category,其對應的category SiteMapNode的Title屬性可設置為“None”;Url屬性設置為空字符串。我將其Url設置為空字符串是因為ProductBLL class類的GetProductsByCategory(categoryID)方法無法返回那些CategoryID值為NULL的產品.不過我鼓勵你對本教程進行擴展,使該category SiteMapNode的Url屬性對應一個ProductsByCategory.aspx頁面,該頁面專門用來展示那些CategoryID為NULL的產品.

  當完成site map的構建後,將一個AggregateCacheDependency object對象添加到data cache,該對象使用基於Categories 和 Products表的SQL cache dependency技術。我們在前面的教程裡探討過SQL cache dependencies,不過我們自定義的site map provider使用的是重載(overload)的data cache的Insert方法,該重載方法接受一個delegate作為輸入參數.具體而言,我們將傳入一個CacheItemRemovedCallback delegate,其指向OnSiteMapChanged method方法,該方法定義在NorthwindSiteMapProvider class類裡
注意:內存裡的site map表述是緩存在一個“類級”(class-level)變量root裡的.由於只有一個site map provider的實例(instance),並且對web應用程序的線程來說都是共享的,這個類級變量當作緩存服務。BuildSiteMap method方法也會用到data cache,但僅僅做作為一種探測Categories 或 Products表裡的數據發生改變的方法。注意添加到data cache裡的僅僅是當前的date和time,實際的site map數據並沒有添加到data cache.

BuildSiteMap method方法最後返回網站地圖的根節點.

  剩下的方法就比較簡單易懂了.GetRootNodeCore方法用來返回根節點,由於BuildSiteMap返回根節點root, GetRootNodeCore方法僅僅返回BuildSiteMap方法的返回值.當緩存條碼被清除掉時,OnSiteMapChanged方法將root設置為null;當root為null的時候,當下一次調用BuildSiteMap時,將重新創建地圖網站結構.最後,如果data cache裡存儲有date 和 time值的話,CachedDate屬性將返回這些值.頁面開發員可以用該屬性來探測site map數據最近被緩存的時間.

第七步:對NorthwindSiteMapProvider進行登記

  為了使用我們在第六步創建的NorthwindSiteMapProvider site map provider,我們需要在Web.config文件的<siteMap>部分進行注冊.具體來說,將下面的代碼添加到Web.config文件的<system.web>部分:

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

  上述代碼闡明了如下2個事實:第一,它指明了“內置”的AspNetXmlSiteMapProvider為默認的site map provider;第二,它將我們在第六步創建的用戶自定義site map provider進行了注冊,取名為“Northwind”.
注意:對那些位於在App_Code文件夾的site map providers而言,type屬性的值就是類的名稱.還一種方法,我們可以用一個單獨的類庫工程來創建自定義的site map provider,將其編譯文件放置在/Bin目錄;如果是那樣的話,type屬性就變成了“Namespace.ClassName, AssemblyName”.

  更新Web.config文件後,花點時間在浏覽器裡登錄本教程的任何一個頁面,我們注意到左邊的導航界面跟以前一樣,那是因為我們把AspNetXmlSiteMapProvider作為默認的provider,為了使導航用戶界面使用我們定制的NorthwindSiteMapProvider,我們應明確的指定使用“Northwind” site map provider,我們將在第八步完成.

第八步:使用定制的Site Map Provider來顯示網站地圖信息

  把我們定制的site map provider注冊到Web.config文件後,我們可以將導航控件添加到SiteMapProvider文件夾裡的Default.aspx, ProductsByCategory.aspx, 和ProductDetails.aspx頁面.首先,打開Default.aspx頁面進入設計模式,從工具箱拖一個SiteMapPath控件到頁面。該控件位於工具箱的導航區域.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916403807.gif
圖16:為Default.aspx頁面添加一個SiteMapPath控件

  SiteMapPath控件包含一個breadcrumb,用來顯示當前頁面在網站地圖裡的位置。我們在第三章《模板頁和站點導航》裡在模板頁的頂部添加了一個SiteMapPath控件.

  花點時間在浏覽器裡登錄頁面,我們在圖16裡添加的SiteMapPath控件使用的是默認的site map provider,它從Web.sitemap文件獲取數據,因此breadcrumb顯示為“Home > Customizing the Site Map”.如下圖:

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916403971.gif
圖17:Breadcrumb使用的是默認的Site Map Provider

  要使在圖16裡添加的SiteMapPath使用我們定制的site map provider的話,設其SiteMapProvider property屬性為“Northwind”, 這個名字是我們在Web.config文件裡分配給NorthwindSiteMapProvider的.不過,在設計器裡依然使用的是默認的site map provider,但是如你在浏覽器裡登錄該頁面的話,你將看到breadcrumb使用的是我們定制的site map provider了.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916404096.gif
圖18:Breadcrumb現在使用的是我們定制的NorthwindSiteMapProvider

  SiteMapPath控件將在ProductsByCategory.aspx 和 ProductDetails.aspx頁面展示更具功能性的用戶界面.在這2個頁面裡添加SiteMapPath控件,設置其SiteMapProvider屬性為“Northwind”. 在Default.aspx頁面裡點擊Beverages類的“View Products”鏈接,然後再點Chai Tea的“View Details”鏈接,如圖19所示,breadcrumb顯示的是當前的網站地圖節點(“Chai Tea”),及其上級節點:“Beverages” 和“All Categories”.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916404008.gif
圖19:Breadcrumb現在使用的是我們定制的NorthwindSiteMapProvider

  除了SiteMapPath外,還可以使用其它的導航控件,比如Menu 和 TreeView控件.本章的下載代碼裡,Default.aspx, ProductsByCategory.aspx,和ProductDetails.aspx頁面都包含Menu控件(見圖20).要想更深入的了解ASP.NET 2.0裡的導航控件和site map體系的話,可參閱《ASP.NET 2.0 QuickStarts》系列(http://quickstarts.asp.net/QuickStartv20/aspnet/)的《Examining ASP.NET 2.0's Site Navigation Features》和《Using Site Navigation Controls》部分.

https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010916404150.gif
圖20:Menu控件列出了所有的Categories 和 Products

就像在本教程前面提到的那樣,網站地圖結構可以通過SiteMap class類來進行訪問,下面的代碼返回默認的provider的root SiteMapNode:

SiteMapNode root = SiteMap.RootNode;

由於AspNetXmlSiteMapProvider是默認的provider,上述代碼返回的是定義在Web.sitemap文件裡的根節點,要引用其它的site map provider的話,使用SiteMap class類的Providers property屬性,如:

SiteMapNode root = SiteMap.Providers["name"].RootNode;
這裡的name是用戶定制的site map provider的名稱(就本文而言,為“Northwind”)

要訪問某個具體的site map provider,使用SiteMap.Providers["name"]來獲取該provider的實例,再將其轉換成恰當的類型。比如,要展示NorthwindSiteMapProvider的CachedDate property屬性,使用如下的代碼:

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 cache dependency屬性,訪問完Default.aspx, ProductsByCategory.aspx, 和 ProductDetails.aspx頁面後,轉到本系列教程的《編輯插入和刪除數據》部分的任一個頁面,編輯某個category 或 product的名稱;然後再轉到SiteMapProvider文件夾裡的某個頁面,假設時間足夠長,長到檢測機制(polling mechanism)發現“源數據庫”已經發生了改動,那麼site map應該被更新以顯示新的product 或 category名字.

結語:

  ASP.NET 2.0的site map屬性包含一個SiteMap class類,一系列內置的的導航Web控件,以及一個默認的site map provider.為了使用來自某些數據源的site map信息——比如數據庫、系統的“層”、或者某些Web服務,我們需要創建一個用戶定制的 site map provider.這就要創建一個類,該類直接或間接的源自SiteMapProvider class類.

  本章我們探討了如何創建一個用戶定制的site map provider,它以一個由product 和 category信息構成的site map為基礎.我們的provider對StaticSiteMapProvider class類進行了擴充,並創建了一個BuildSiteMap method方法來獲取數據、構建site map的層次體系,並且將最終的網站地圖結構緩存在一個“類級”的變量裡.我們使用一個SQL cache dependency來確保當Categories 或 Products的“源數據”發生改動時使緩存的數據失效.

  祝編程快樂!

作者簡介

  本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用 微軟Web技術。大家可以點擊查看全部教程《[翻譯]Scott Mitchell 的ASP.NET 2.0數據教程》,希望對大家的學習ASP.NET有所幫助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved