理解並擴充 ASP.NET 2.0 中的網站導覽系統 http://msdn.microsoft.com/zh-cn/library/aa479338.aspx
發布日期 : 2006-3-15 | 更新日期 : 2006-3-15
David Gristwood
Developer & Platform Group, Microsoft
適用於:
Microsoft ASP.NET 2.0 (Beta 2)
摘要:ASP.NET 2.0 網站導覽系統構建於一個功能強大、靈活的體繫結構之上,設計這樣的體繫結構是為了使其具有可擴充性。本文探究網站提供者的體繫結構並提供一個樣本提供者,該提供者將檔案系統公開為網站導覽的資料來源,從而替代了標準的 Web.sitemap XML 檔案。
請從此處下載樣本。
本頁內容
簡介
理解 ASP.NET 2.0 中的導航系統
導航控制項
使用導航系統
實現您自己的網站地圖提供者
一個檔案夾網站地圖提供者
小結
簡介
大多數 web 網站採用可視化導航的某種形式來協助使用者輕鬆地瀏覽網站,以及尋找他們所需的資訊和 Web 頁。儘管不同網站之間的感觀效果千差萬別,但是通常會使用相同的基本元素 — 以導覽列或菜單列表的形式使使用者定位到 web 網站的特定位置。
ASP.NET 1.x 提供的針對網站導覽現成的支援很少,導致很多開發人員和 web 設計人員不是構建自己的導航系統,就是購買第三方控制項以滿足他們的需求。而 ASP.NET 2.0 對此作出了改進,它引入一個使用可插接式架構的導航系統,該架構能夠公開網站階層和插入這個新模型的控制項,因此易於構造一個高品質的菜單和導航系統。
本文描述 ASP.NET 2.0 導航系統的工作原理並展示如何對其進行擴充 — 不僅僅是使用簡單的 XML 檔案(Visual Studio 2005 中使用的預設機制)。
返回頁首 理解 ASP.NET 2.0 中的導航系統
ASP.NET 2.0 導航系統的一個目標是建立一個可以吸引開發人員和 web 網站設計人員的優秀的導航模型,除此之外,它還有一個目標是建立一個提供可擴充性功能的體繫結構,該功能能夠靈活地滿足廣泛的需求。該系統基於一個提供者模型,該模型的使用貫穿於整個 ASP.NET 2.0 架構,由 ASP.NET 2.0 架構提供一個標準的機制用於插入不同的資料來源。
ASP.NET 2.0 導航架構可以分解為幾個部分:
開發人員在實際 web 頁面上使用的web 導航控制項(Menu、TreeView 和 SiteMapPath)。這些控制項可以通過自訂改變感觀效果。
TreeView 和菜單導航控制項綁定的 SiteMapDataSource 控制項,在 Web 導航控制項和導航資訊的底層提供者之間提供一個抽象層。
網站地圖提供者是可插接式提供者,它用於公開描述 web 網站布局的實際資訊。ASP.NET 提供了一個提供者 XmlSiteMapProvider,它使用一個具有特定結構的 XML 檔案作為其資料存放區。
這種分層的體繫結構在底層的網站階層和 web 網站上的控制項之間製造了更為鬆散的耦合,提供了更大的靈活性,而且隨著網站的不斷髮展,更容易實現體繫結構和設計的改動。
以下表格說明提供者和控制項之間的關係。
圖 1. 導航體繫結構
對於導航系統,資料來源描述使用者能夠定位的 web 網站頁的階層,以及將這些資訊顯示給使用者的方式。它作為一個網站地圖被引用。一個簡單的 web 網站的布局可以是以下形式:
HomeProductsProduct AProduct BProduct CLatest OffersContact UsEmailVisit us
返回頁首 導航控制項
在深入研究導航系統的內部工作機制之前,瞭解開發人員如何與之互動十分重要。最常見的方法是通過 ASP.NET 2.0 中的三個新導航控制項。一個網站或頁面上可以存在多個 Web 導航控制項 — 如,主菜單控制項放置在頁面左側,另一個菜單控制項放在頁面頂部,這種情況還是時有發生的,我們能夠以編程方式使一個導航控制項控制頁面上另一個導航控制項,或使它們各自獨立操作。
導航系統通常與主版頁面功能一起使用,主版頁面功能也是 ASP.NET 2.0 中提供的新功能。通過將導航控制項放置在網站的主版頁面上,可以確保整個網站具有統一的感觀效果。但是,導航功能和主版頁面功能互不相關且各自獨立。
這三個新 web 控制項是:
菜單控制項 — 它提供一個傳統的導航介面,通常的情況是沿 web 網站的一側或橫跨頂部。它能夠顯示任意數目的嵌套子功能表,而且,當使用者的滑鼠停懸在某一項上時,可以顯示任意數目的可選彈出子功能表。
圖 2. 菜單控制項
TreeView 控制項 — 它提供一個垂直的樹狀使用者介面,通過選擇單個節點可以展開和摺疊使用者介面。它還提供用於選定某些項的複選框功能。
圖 3. TreeView 控制項
SiteMapPath 控制項 — 它通常作為“breadcrumb”控制項進行引用,因為它可以跟蹤使用者在網站階層內的位置。它將當前位置顯示為一個路徑,通常是從首頁到當前位置的路徑,因而使用者更容易瞭解自己所處的位置,並定位迴路徑上的其他頁面。
圖 4. SiteMapPath 控制項
從體繫結構的角度來看,菜單控制項和 TreeView 控制項有些相似,兩者之間的主要區別是編程以及顯示方式不同,因此,各自擁有獨特的感觀效果。要詳細瞭解這兩個控制項,請參閱 MSDN 中的文章 Introducing the ASP.NET 2.0 TreeView and Menu Controls。值得注意的是,雖然使用菜單控制項和 TreeView 控制項的最常見情況離不開網站導覽,但是它們也能夠用於非導航情況,這有待於使用者自己做選擇。
SiteMapPath (或“breadcrumb”)控制項則略為不同。它直接使用 SiteMapProvider,而不是菜單控制項和 TreeView 控制項使用的 SiteMapDataSource 控制項。這說明它是導航系統內部更有針對性的控制項,因此支援將其功能擴充到非導航情況的理論依據更少。
返回頁首 使用導航系統
理解網站導覽工作原理的一種最簡單的方法是,直接在一個應用程式內部訪問它,而不是通過一個 Web 控制項來訪問。下列程式碼範例說明如何與 SiteMap 物件模型進行互動以顯示一部分階層化的網站資訊。
<%@ Page Language="C#" %><script runat="server">private void Page_Load(object sender, System.EventArgs e){this.Label1.Text = "Current Page Title : " +SiteMap.CurrentNode.Title;if(SiteMap.CurrentNode.ChildNodes > 0) {this.HyperLink1.NavigateUrl =SiteMap.CurrentNode.ChildNodes[0].Url;this.HyperLink1.Text =SiteMap.CurrentNode.ChildNodes[0].Title;}}</script><html><head></head><body><form id="Form1" runat="server"><asp:Label ID="Label1" runat="server"Text="Label"></asp:Label><br />First Child Node: <asp:HyperLink id="HyperLink1"runat="server">HyperLink</asp:HyperLink></form></body></html>
導航系統將網站地圖模型化為一系列節點,稱為 SiteMapNodes,在一個樹狀結構內,每個節點通常表示使用者能夠定位到的 web 網站上的一個頁面(可能有一些節點只是子頁面的預留位置,或與之類似,本身沒有 Web 頁)。
SiteMapNode 類具有一些屬性和方法,其中最重要的一些是:
Title — 當此節點通過一個 web 控制項顯示時將呈現的文本
Url — SiteMapNode 在 web 網站內表示的實際頁面的 URL
Description — 當滑鼠移至上方在 HTML 頁面的節點上時顯示的工具提示
SiteMapNode 類還有一些屬性用於儲存各個 SiteMapNodes 之間的關聯,這些關聯描述出網站地圖的結構(ChildNodes、NextSibling、PreviousSibling、ParentNode 等等)。
返回頁首 實現您自己的網站地圖提供者
如前所述,ASP.NET 2.0 發行了一個名為 XmlSiteMapProvider 的導航資料存放區提供者,這是大部分開發人員熟悉的預設提供者。該提供者使用一個表示 SiteMapNodes 的 XML 檔案中的資料。在使用 Visual Studio 2005 時,如果您在 Web 項目中添加了一個新網站地圖,那麼在預設情況下,在項目內將建立一個 Web.sitemap 檔案,並按照下列模板格式進行填充。
<siteMapxmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" ><siteMapNode url="" title="" description=""><siteMapNode url="" title="" description="" /><siteMapNode url="" title="" description="" /></siteMapNode></siteMap>
您一眼就可以看出,前面提到的核心 SiteMapNodes 屬性(title、url 和 description)就是 Web.sitemap XML 結構描述中的屬性,而且樹結構(父級、子級等等)是由嵌套的 SiteMapNodes 表示的。
這是處理網站地圖資訊的一種簡單且精練的方法,對於許多 Web 設計人員而言,使用起來綽綽有餘。然而,ASP.NET 提供者模型具有擴充性並不意味著您能夠編寫自己的提供者。建立一個自訂網站地圖提供者要考慮三個可能的主要原因:
網站地圖資訊儲存在一個非 XML 檔案的資料來源中,例如資料庫或目錄服務。
網站地圖資訊以 XML 的形式使用,但是使用的架構與 Web.sitemap 使用的架構不同。
需要一個動態網站地圖結構,該結構需要在運行時構造,或者需要一個安全剪裁不能處理的自訂視圖。
為了實現您自己的網站地圖提供者,您需要從 System.Web 命名空間的 SiteMapProvider 抽象類別中派生一個自訂的提供者類。雖然 SiteMapProvider 類具有大約二十幾個抽象方法或虛方法,但是在您的自訂網站地圖提供者中,只有少數方法需要重寫或實現。
如果您的自訂網站地圖提供者使用的資料存放區與預設的 XmlSiteMapProvider 的 Web.sitemap XML 結構描述相似,則應該選擇從 StaticSiteMapProvider 類派生,它提供 SiteMapProvider 類的部分實現,並反過來作為實際的 XmlSiteMapProvider 類的基類。
您的類必須至少實現下列方法:
FindSiteMapNode — 返回一個與特定 URL 對應的 SiteMapNode。
GetChildNodes — 返回一個特定 SiteMapNode 的子節點集合。
GetParentNode — 返回一個特定 SiteMapNode 的父節點。
GetRootNodeCore — 返回當前提供者目前管理的所有節點的根節點。
您還應該重寫 SiteMapProvider 的 Initialize 方法,在調用基類 Initialize 之後在該方法中執行您自己的初始化。
如果有必要,您可以選擇重寫與節點相關的一些屬性,從而構造更為複雜的表示網站地圖的模型:
這些屬性和方法代表了 SiteMapProvider、Menu / TreeView 控制項(通過 SiteMapDataSource)和 SiteMapPath 控制項之間的協定。在以下兩種情況下會發生這種互動:控制項請求填充顯示所需的資訊,以及使用者通過網站進行導航以跟蹤網站導覽內的當前位置。它是一個事件驅動模型,在互動過程中,導航系統調入提供者。
如果編寫自己的提供者,您需要決定 SiteMapNodes 是唯讀還是可寫 — 如果是可寫的,您需要考慮安全執行緒,在適當的情況下鎖定更新代碼,從而保證實現是安全執行緒的,因為 ASP.NET 通過一個線程池調度請求,因此一個線程池會被多個線程訪問。
一個更細節的問題是有關效能和延展性的。一個 web 網站使用幾個導航控制項,或者底層導航資料存放區容量很大的情況並不少見,因此,您需要保證提供者可以響應,因為這將影響有關設計方面的事宜,包括儲存和檢索資料的最佳演算法以及緩衝的潛在作用。
安全問題也需要考慮。Web 網站的普遍要求是只允許會員或其他經過身分識別驗證的使用者查看特定頁面,而 ASP.NET 2.0 的角色管理提供了定義明確的方法,以便根據資訊安全角色限制對 Web 檔案的訪問。這一功能通過一個稱為安全剪裁的機制擴充到網站導覽系統中。安全剪裁強制在嘗試訪問頁面時也應用 Url 和檔案授權。在節點上定義一個 roles 屬性用於擴充對該節點的訪問 — 如果您是在 roles 屬性中定義的角色之一,那麼將返回該節點;如果您不是這些角色中的一個,那麼將執行 Url 和 File Authoriation 檢查。對於 XmlSiteMapProvider 而言,在一個 SiteMapNode 項中添加一個 roles="managers" 類型的屬性將決定使用者在網站地圖中是否能夠看到該節點。在編寫您自己的網站地圖提供者時,這通過 IsAccessableToUser 方法進行處理,它根據使用者的角色返回一個布爾值,指示該節點對使用者是否可用。派生的提供者能夠使用基類 SiteMapProvider 中存在的預設實現。如果要支援安全性,那麼網站地圖必須提供某種方法來儲存該資訊。
返回頁首 一個檔案夾網站地圖提供者
本文中的樣本將一個 web 網站內的子目錄公開為實際的網站地圖,這使 web 網站導覽能夠直接映射到檔案夾結構,這樣的 web 網站結構模型不少見。該樣本的重點是說明如何設計和構造一個網站地圖提供者,在這種情況下,它的目的不是覆蓋行業優點方面所有可能的“邊緣條件”。這些權衡在本文內的適當位置都已強調。
樣本提供者通過 web 網站的子目錄進行遞迴枚舉,檢查每個檔案夾(除了 App_* 和 bin 目錄,它們是 ASP.NET 2.0 使用的特殊目錄)是否存在一個 default.aspx 檔案。如果存在這個檔案,樣本提供者則將其添加到網站地圖中,並使用包含檔案夾的名稱作為菜單描述。擴充該樣本,使每個目錄不僅支援 default.aspx 項,或者必要時在目錄內的一個文字檔中儲存一個日期工具提示,這實現起來相對比較簡單。
提供者的核心代碼如下所示。
[AspNetHostingPermission(SecurityAction.Demand, Level =AspNetHostingPermissionLevel.Minimal)]public class FolderSiteMapProvider : SiteMapProvider{private SiteMapNode rootNode = null;// root of treeprivate Hashtable urlHash = null;// hash table based on URLprivate Hashtable keyHash = null;// hash table based on keystring defaultPageName = "Default.aspx";// only look for this file in each subdirectorystring defaultTitle = "Home";// description of root node// override SiteMapProvider Initializepublic override void Initialize(string name,NameValueCollection attributes){// do base class initialization firstbase.Initialize(name, attributes);// our custom initializationurlHash = new Hashtable();keyHash = new Hashtable();// get web site infostring startFolder = HttpRuntime.AppDomainAppPath;string startUri =Uri.EscapeUriString(HttpRuntime.AppDomainAppVirtualPath);// want canonical format of URI// Create root nodestring key = startFolder;string url = startUri + @"/" + defaultPageName;rootNode = new SiteMapNode(this, key, url, defaultTitle);RootNode.ParentNode = null;urlHash.Add(url, rootNode);keyHash.Add(key, rootNode);// populate entire siteEnumerateFolders(rootNode);}// Retrieves a SiteMapNode that represents a pagepublic override SiteMapNode FindSiteMapNode(string rawUrl){if (urlHash.ContainsKey(rawUrl)){SiteMapNode n = (SiteMapNode)urlHash[rawUrl];return n;}else{return null;}}// Retrieves the root node of all the nodes//currently managed by the current provider.// This method must return a non-null nodeprotected override SiteMapNode GetRootNodeCore(){return rootNode;}// Retrieves the child nodes of a specific SiteMapNodepublic override SiteMapNodeCollectionGetChildNodes(SiteMapNode node){SiteMapNode n = (SiteMapNode)keyHash[node.Key];// look up our entry, based on keyreturn n.ChildNodes;}// Retrieves the parent node of a specific SiteMapNode.public override SiteMapNode GetParentNode(SiteMapNode node){SiteMapNode n = (SiteMapNode)keyHash[node.Key];// look up our entry, based on keyreturn n.ParentNode;}// helper functions . . .// . . .}
當提供者通過其 Initialize 方法進行調用時,樣本提供者產生一個資料夾清單,但是不重新整理該列表,因此它不檢查隨時添加的新檔案夾,這對一個產品的 web 網站是不合理的。
它不實現安全剪裁,因此所有子目錄都可見,但是,要實現安全剪裁是很簡單的,只要在 FindSiteMapNode、GetChildNodes 和 GetParentNode 方法內添加對基類 IsNodeAccessible 方法的調用就可以了,IsNodeAccessible 方法將自動獲得為 web 網站配置的任何檔案授權規則的優點。
提供者在內部能夠以任意種方法表示底層儲存,但是,因為它通過 SiteMapNode 類與導航系統進行互動,特別是因為目錄階層與節點階層如此匹配,所以在這種情況下,在內部使用這些方法才有意義。它還維護兩個雜湊表,其中一個以節點 URL 為關鍵字,另一個以節點關鍵字(檔案夾名)為關鍵字,從而支援快速查詢節點。對於本樣本而言,在記憶體中儲存所有這些資訊是有意義的,因為它能夠保持簡單的編程模型和非常快的查詢速度;但是,對於一個擁有成千上萬個檔案夾的大型網站而言,研究一個不在記憶體中儲存全部資訊的機制可能是值得的。
為了保持簡單的代碼,節點沒有以唯讀方式處理。在一個實際的具有行業優點的提供者中,當應用程式不允許添加或修改網站地圖節點的內部列表時,就將各個節點以及整個集合標識為唯讀。
私人的 helper 函數用於構造資料夾階層,如以下程式碼範例所示。
private void EnumerateFolders(SiteMapNode parentNode){// create a node collection for this directorySiteMapNodeCollection nodes = new SiteMapNodeCollection();parentNode.ChildNodes = nodes;// get list of subdirectories within this directorystring targetDirectory = parentNode.Key;// we use the key to hold the file directorystring[] subdirectoryEntries =Directory.GetDirectories(targetDirectory);foreach (string subdirectory in subdirectoryEntries){// search for any sub folders in this directorystring[] s = subdirectory.Split('\\');string folder = s[s.Length-1];string tmp = String.Copy(folder);tmp = tmp.ToLower();// avoid any case sensitive matching issues// check for App_ and bin directories, and don't add themif (tmp.StartsWith("app_"))continue;if (tmp == "bin")continue;string testFileName = subdirectory + @"\" + defaultPageName;if (File.Exists(testFileName)){// create new nodestring key = subdirectory;string url = CreateChildUrl(parentNode.Url, folder);string title = folder;SiteMapNode n = new SiteMapNode(this, key, url, title);n.ParentNode = parentNode;// add it into node collection and tablenodes.Add(n);urlHash.Add(url, n);keyHash.Add(key, n);// and enummerate through this folder nowEnumerateFolders(n);}}}
當提供者在子目錄中枚舉時,它為每一個合法子目錄建立一個 SiteMapNode 類,並設定它的 key、url 和 title 屬性。它還為每一個子節點填充 ChildNodes 屬性,這個屬性是一個 SiteMapNodeCollection,而 ParentNode 屬性則向上指回父節點。同時,它也將每個節點添加到雜湊表中,以便進行快速查詢。
一旦完成這些操作,提供者需要使用 web.config 檔案進行配置。以下項需要添加到 <system.web> 下。
<system.web>. . .<siteMap defaultProvider="SimpleProvider"><providers><add name="SimpleProvider"type="Test.SimpleProvider"/></providers></siteMap>. . .</system.web>
返回頁首 小結
本文的目標是使您瞭解並正確評價 ASP.NET 2.0 網站導覽系統,理解不同元素之間協作的原理。程式碼範例說明了如何通過構造您自己的網站地圖提供者來擴充體繫結構,該提供者使用任意資料來源來定義網站階層。
關於作者
David Gristwood 在 Microsoft 位於 UK 的開發人員和平台組工作,他大部分時間用來與客戶和夥伴一起工作,協助他們設計和構建充分利用 Microsoft .NET 平台的解決方案。他也經常在會議和研討會上發言。