一個網站,需要借助某個導航系統來幫助用戶從一個頁面跳轉到另一個頁面。我們知道,可以利用母版頁幫助網站定義一個包含導航欄的模板,不過,仍然要由你來為導航欄填入內容。
當然可以利用 ASP.NET 的控件集實現幾乎所有的導航系統,但這需要大量的工作!幸好 ASP.NET 包括一組導航功能,它們可以大大簡化你的工作。
和 ASP.NET 所有的最佳功能一樣,ASP.NET 導航靈活、可配置、可插入。它包含以下 3 部分:
- 定義站點地圖導航結構。這部分是 XML 站點地圖,它(默認)保存在文件里。
- 解析站點地圖文件並轉換為適當對象模型。這部分由 SiteMapDataSoruce 和 XmlSiteMapProvider 實現。
- 利用站點地圖信息顯示用戶當前位置並讓用戶能夠方便的從一個頁面跳轉到另一個頁面的簡單方法。這部分由綁定到 SiteMapDataSoruce 控件提供,可以是瀏覽器路徑鏈接、列表、菜單或樹。
定義站點地圖
基於站點地圖導航的起始點是站點地圖提供程序。ASP.NET 只提供了一個站點地圖提供程序 XmlSiteMapProvider ,它可以從一個 XML 文件里讀取站點地圖信息。XmlSiteMapProvider 在虛擬目錄的根目錄查找一個叫做 Web.sitemap 的文件。和所有站點地圖提供程序一樣,它的任務時抓取站點地圖數據並創建相應的 SiteMap 對象。此后,這個 SiteMap 對象通過 SiteMapDataSource 對其他控件可用。
下面是站點地圖文件的基本結構:
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode title="Home" description="Root" url="~/Default.aspx">
<siteMapNode title="Products" description="Our products" url="~/Products.aspx" >
<siteMapNode title="Hardware" description="Hardware choices" url="~/Hardware.aspx" />
<siteMapNode title="Software" description="Software choices" url="~/Software.aspx" />
</siteMapNode>
<siteMapNode title="Services" description="Services we offer" url="~/Services.aspx" >
<siteMapNode title="Training" description="Training classes" url="~/Training.aspx" />
<siteMapNode title="Consulting" description="Consulting services" url="~/Consulting.aspx" />
<siteMapNode title="Support" description="Support plans" url="~/Support.aspx" />
</siteMapNode>
</siteMapNode>
</siteMap>
為了保證有效性,站點地圖必須遵守以下這些規則:
- 以 <siteMap> 節點開始,后面跟一個 <siteMapNode> 元素,它代表默認主頁。
- 可用在根 <siteMapNode> 內嵌入無限多層 <siteMapNode> 元素。
這個例子中,URL 使用了 ./ 語法,表示 Web 應用程序的根。這種風格並不是必須的,但強烈推薦,因為這樣可以保證站點地圖鏈接可以不管當前文件夾而被正確的解釋。
另一個限制是你不能為同一個 URL 創建兩個或以上的站點地圖節點。這個限制並不是導航系統固有的。它只是 XmlSiteMapProvider 的要求,因為 XmlSiteMapProvider 把 URL 作為唯一鍵。如果你創建自己的站點地圖提供程序或者使用第三方的提供程序,就可以允許重復的 URL,不過仍然需要唯一的鍵值。
綁定站點地圖
定義了 Web.sitemap 文件之后,就隨時可以在頁面中使用它了。這正是使用母版頁的地方,這樣可以把導航控件定義為母版頁的一部分並在所有頁面里重用它。
<form id="form1" runat="server">
<table>
<tr>
<td style="width: 226px; vertical-align: top">
</td>
<td style="vertical-align: top">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server" />
</td>
</tr>
</table>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
</form>
可以使用 TreeView 控件來做導航:
<asp:TreeView ID="treeNav" runat="server" DataSourceID="SiteMapDataSource1"></asp:TreeView>
也可以同樣方便的使用 Menu 控件:
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1"></asp:Menu>
導航路徑
ASP.NET 其實定義了 3 個導航控件:TreeView、Menu、SiteMapPath 。
SiteMapPath 提供導航路徑,它顯示用戶當前位置並允許使用鏈接回到更高的層級。它和其他導航控件(如 TreeView、Menu)有細微但卻重要的區別。SiteMapPath 直接作用於 ASP.NET 導航模型,它並不從 SiteMapDataSource 獲取自己的數據。因此,可以在沒有 SiteMapDataSource 的頁面上使用 SiteMapPath ,而且對 SiteMapDataSource 的屬性的修改也不會影響 SiteMapPath 。
SiteMapPath 典型的應用是在母版頁上,這樣它就可以顯示在所有的內容頁上。SiteMapPath 控件在告知用戶當前位置以及提供用戶在層次結構間向上跳轉方面都很有用(只能向上跳轉)。不過,你也必須將其結合其他導航控件一起使用,那樣才能讓用戶在站點地圖層次中向下跳轉。
下例在母版頁的 2 處都放置了 SiteMapPath 控件:
<form id="form1" runat="server">
<div>
<table>
<tr>
<td style="width: 226px; vertical-align: top;">
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
<br />
<br />
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:TreeView>
<!--<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:Menu>-->
</td>
<td style="vertical-align: top;">
<asp:SiteMapPath ID="SiteMapPath2" runat="server">
</asp:SiteMapPath>
<br />
<br />
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
</table>
</div>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
</form>
SiteMapPath 控件是完全可以自定義的。與外觀相關的屬性:
ShowToolTips | 是否顯示鼠標停留時顯示的描述性文字 |
ParentLevelsDisplayed | 設置同時顯示父節點層數的最大值。默認 -1,顯示所有層級。 |
RenderCurrentNodeAsLink | 如果為 true,當前頁面的路徑會顯式為一個可單擊的鏈接。默認為 false |
PathDirection | 兩個選擇:RootToCurrent 和 CurrentToRoot 。正序或反序的顯示路徑的層級 |
PathSeparator | 層級之間的字符,默認為 > |
如果還需要更多控制,可以通過樣式和模板重定義 SiteMapPath 控件和 HTML:
樣 式 |
模 板 |
應 用 於 |
NodeStyle | NodeTemplete | 除了根節點和當前節點的所有路徑部分 |
CurrentNodeStyle | CurrentNodeTemplete | 當前頁面的節點 |
RootNodeStyle | RootNodeTemplete | 根節點。如果根節點就是當前頁面,使用當前頁面的模板和樣式 |
PathSeparatorStyle | PathSepararorTemplete | 每個節點間的分隔符 |
下面的 SiteMapPath 使用一個箭頭圖片做分隔符並把一個加粗的固定字符串作為根節點,當前頁面使用斜體:
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
<PathSeparatorTemplate>
<asp:Image ID="Image1" runat="server" ImageUrl="~/images/arrow.jpg" />
</PathSeparatorTemplate>
<RootNodeTemplate>
<b>Root</b>
</RootNodeTemplate>
<CurrentNodeTemplate>
<i><asp:Label ID="Label1" runat="server" Text='<%# Eval("title") %>'></asp:Label></i>
</CurrentNodeTemplate>
</asp:SiteMapPath>
這里用的數據綁定表達式獲取綁定到當前節點的標題屬性,同樣也可以獲取 url 特性和 description 特性。
顯示站點地圖的一部分
在目前所有的示例中,頁面控件都是完全復制站點地圖文件的結構的。不過,這並不一定總是你所期望的。一個大型站點地圖可能會分散用戶對當前頁面相關部分的注意力。此外,太多的層級有可能導致整個樹不能夠巧妙的加載到頁面上。
1. 跳過根節點
你可能並不喜歡 Home 節點突出的方式,如果要對其進行清理,可以設置 SiteMapDataSource.ShowStartingNode 為 false。如果還是要顯示 Home 入口,可以修改站點地圖文件讓它在頁面的第一組里定義 Home 節點。而真正的根節點因為不會顯示,所以也不需要任何 URL:
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode title="Root" description="Root" >
<siteMapNode title="Home" description="Root" url="~/Default.aspx"></siteMapNode>
<siteMapNode title="Products" description="Our products" url="~/Products.aspx" >
<siteMapNode title="Hardware" description="Hardware choices" url="~/Hardware.aspx" />
...
</siteMap>
這個結果可能更漂亮,整個層次少了最外層的一個嵌套(Home)。
2. 從當前節點開始
另一個方法時從當前節點開始只顯示站點地圖的一部分。例如,可以用某個控件(如 TreeView)顯示從當前節點開始的層次中的所有內容,如果要向上跳,可以結合 SiteMapPath 控件一起使用。
只需簡單的設置 SiteMapDataSource.StartFromCurrentNode 為 true 。SiteMapPath 仍將顯示完整的層次結構,因為它不使用 SiteMapDataSource 。仍然可以選擇是否用 ShowStartingNode,不過現在它決定是否顯示當前節點,因為該節點現在是導航樹的起點。
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" StartFromCurrentNode="true" ShowStartingNode="false" />
3. 從指定節點開始
SiteMapDataSource 還有兩個屬性可以幫助配置導航樹:
StartingNodeUrl:
它接受應該成為數中第一個節點的 URL (相當於重定義了一個根節點)。不過這個 URL 必須和 Web.sitemap 文件中該節點的 url 特性完全匹配。在幾個位數不多的站點地圖間切換時,StartingNodeUrl 屬性特別有用。最理想的方案是定義多個站點地圖文件並綁定到你希望使用的那個文件。遺憾的是,默認的 XmlSiteMapProvider 只支持一個站點地圖文件,所以你需要一個不同的機制。遇到這種情況時,解決方法是把不同的站點地圖分解到 Web.sitemap 文件的獨立分支中。
假設,網站包括零售商和雇員部分。你可以把它們分解成 2 個結構並在同一個文件的不同分支里定義它們。
還可以通過 siteMapFile 特性把一個站點地圖文件分解成多個單獨的文件,不過即使這樣,你還是只有一個站點地圖樹,並且它總是從 Web.sitemap 文件開始,不過你可以更輕松的管理站點地圖了。
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode title="Home" description="Root" url="~/Default.aspx" >
<siteMapNode siteMapFile="Dealers.sitemap" />
<siteMapNode siteMapFile="Employees.sitemap" />
</siteMapNode>
</siteMap>
注解:
因為 XmlSiteMapProvider 不允許重復 URL,所以這項技術的應用會受到很大的限制。雖然可以創建多個等效的 URL 來解決這一問題(例如,結尾處加入查詢字符串參數),不過,這會帶來更多令人頭痛的問題。最好的辦法還是設計自己的站點地圖提供程序。
StartingNodeOffset:
它接受一個整數,用來指示 SiteMapDataSource 向下(整數)或向上(負數)移動樹的多少層。這個屬性的使用有很多需要注意的地方。
下面的示例可以幫助理解它是如何工作的。假設你在站點的這個地方:
Home > Products > Software > Custom > Contact Us
如果 SiteMapDataSource 從 Home(默認)節點開始,並且你把 StartingNodeOffset 設置為 2,它將沿着樹向下移動 2 級並從那個節點向下綁定這個樹。在本示例中,那個節點是 Software:
Software > Custom > Contact Us
也就是說,你能夠跳轉到 Software 組或 Custom 組里的任意鏈接,但除此之外,哪里也去不了。
如果試圖向下移動太多層(例如,用戶位於第 2 層,而你提供的數值是 3),SiteMapDataSource 會超出層級,綁定控件會顯式為空。
另一項有用的技術是從當前節點向上移動。例如設置為 -3,那么 SiteMapDataSource 將會從當前頁面(Contact Us)向上移動 3 層並綁定到這棵樹:
Products > Software > Custom > Contact Us
這項技術更有用一些,因為它保證了導航控件總是顯示相同個數的層級。如果試圖超越根節點,將只會看到最大的層級數。
注意:
確定希望使用的 SiteMapDataSource 正確組合需要做幾次嘗試。這 2 個屬性是很多網站從沒有使用的特殊屬性。如果站點地圖樹很復雜,嵌套層次很深,它們就會非常有用。用這些屬性可以減少一次顯示的層級數。這樣的導航鏈更方便閱讀和理解(至少更緊湊,不會浪費寶貴的 Web 頁面空間)。
如果要用 SiteMapPath(它不使用 SiteMapDataSource) 獲得同樣的效果,可以設置 SiteMapPath.ParentLevelsDisplayed 屬性。
站點地圖對象
要顯示導航結構,除了使用無代碼的數據綁定技術,還可以通過編程和導航信息交互。有 2 個原因會使你這樣做:
- 修改頁面的顯示。例如,可以獲取當前節點的信息並用它配置頁頭和標題之類的細節。
- 實現不同的導航邏輯。例如,在新聞閱讀器里可能只希望顯示當前頁面的部分子節點。
站點地圖 API 非常直觀。你需要借助 System.Web 命名空間的兩個類來使用它。入口點是 SiteMap 類,它提供靜態屬性 CurrentNode(當前頁面的站點地圖節點)和 RootNode(根節點地圖節點),這 2 個屬性都返回 SiteMapNode 對象,你可以獲取來自站點地圖的信息,包括標題、描述、URL等。
還可以用當前對象 SiteMapProvider 的方法來查找節點,這些方法可通過 SiteMap.Provider 靜態屬性獲得。例如,SiteMap.Provider.FindSiteMapNode()允許通過 URL 搜索節點。
SiteMapNode 的導航屬性:ParentNode、ChildNodes、PreviousSibling、NextSibling。
看以下代碼,頁面上有兩個標簽,分別顯示從當前節點獲得的標題和信息:
protected void Page_Load(object sender, EventArgs e)
{
lblHead.Text = SiteMap.CurrentNode.Title;
lblDescription.Text = SiteMap.CurrentNode.Description;
}
下一個示例提供下一個按鈕,允許用戶遍歷整個子節點集:
protected void Page_Load(object sender, EventArgs e)
{
if (SiteMap.CurrentNode.NextSibling != null)
{
lnkNext.NavigateUrl = SiteMap.CurrentNode.NextSibling.Url;
lnkNext.Visible = true;
}
else
{
lnkNext.Visible = false;
}
}