使用 Google 搜尋本站文章:
首頁 | CodeCharge 討論板 | 部落格 | ASP.NET 電子書 | 相簿 | News | 網站導讀 | 聯絡 Allen | 訂閱 RSS
 
since 2004 
 


SiteMapPath 控制項, SiteMapProvider 簡介

By Allen Kuo, 建立日期:2010/09/01 09:49 ,最後修改日期:2010/09/01 10:41
...
    加入書籤: 收進你的MyShare個人書籤

這算是一篇陰錯陽差的文章, 前幾天有網友問我物件導向及設計模式的問題, 為了方便說明, 我請他先做一個程式練習, 我隨意地出個一題目, 請他想想看, 網頁裡巡覽列要如何寫程式顯示它, 在網友練習的同時, 我也做了相同的練習, 不過我寫了好幾個小時, 才大致寫好, 我原本想寫文章介紹它, 而文章的開頭, 我想先介紹一下 ASP.NET 內建 SiteMapPath 這個我從沒用過的控制項, 經由介紹它的不足, 最後再自己寫程式來解決實務上會遇到的問題, 但是, 我總不能連試都沒試就說 SiteMapPath 做不到吧, 至少要先 google 一下別人有沒有相同問題與解決方式, 結果... 造就了這篇文章, 各位看它時, 就當它是個美麗的錯誤吧。

這文章是昨天開始寫的, 所以我其實對它的研究也不深, 如果有講錯的, 請隨時跟我說, 謝謝。

下載範例程式


 

為了方便使用者明白自己目前身在網站的什麼位置, 有時會在網頁放上
 
ASP.NET 裡提供了現成的控制項 – SiteMapPath。您可以參考範例裡的 WebSite01 網站, 由於我在網站根目錄放了 web.sitemap 檔案, 並在裡面定義了各網頁的上下層關係, 所以只需要在Default.aspx, Pet.aspx, Bird.aspx 裡加入SiteMapPath 控制項, 不必寫程式就可以產生巡覽列了。
 

Default.aspx 
Pet.aspx
Bird.aspx


 但接下來會遇到什麼問題呢? 我舉一個例子, 網頁
http://www.argox.com/tw/content.php?sno=0000007
當您在這網站 click 上方的 “關於立象”, 網站裡其實並沒有這一頁, 所以它是直接連結到 “公司簡介”, 這網站將 "公司簡介"、"大事紀"、"環保立象"、"獎項與認證" 都歸納到 "關於立象", 我猜是為了比較好分類才這麼做。如果您試著點選下圖紅框裡的超連結, 可以發現它其實也是連結到 "公司簡介" 這一頁
 
那麼, 如果用 SiteMapPath 要如何達成呢? 您可以參考範例裡的 WebSite02 網站, 就我的想像, 我只需要如下圖所示, 在 web.sitemap 裡的第四,五行這麼寫就 ok 啦, 我來執行看看….
 
執行結果,發生錯誤
 
從錯誤訊息得知, 其實程式是根據目前網址去 web.sitemap 裡找 url 屬性值, 如果找到了, 當然就可以向上推論出它是屬於哪一個節點, 最後呈現出巡覽列, 也因為如此, 您不可以在 web.sitemap 裡有二個節點填入相同的 url 值, 不然它就搞不清楚了。我是為了寫這文章才第一次使用 SiteMapPath 控制項的, 我不確定有沒有比較好的解決方法。我自己是胡亂試了一個有點矬的方法, 請參考範例裡的 WebSite03 網站, 既然它規定 url 不能相同,我就在 url 後面亂加一些沒有用的參數, 讓它看起來不同試試看
 
執行結果, OK !!, 算是騙過它了
 

 

第二個問題, 請參考這一個網頁
http://www.bcc.com.tw/program.asp
它的巡覽列
 
其實每一個節點都是可以點選的, 雖然最後一個節點通常就是目前網頁, 但這還是要看客戶的意思, 但從我第一個範例看來, 它最後一個節點似乎自動顯示成純文字, 若遇到這需求時, 也要設法解決,
<asp:SiteMapPath ID="SiteMapPath1" runat="server" RenderCurrentNodeAsLink="True" />

您只需要將這控制項的 RenderCurrentNodeAsLink屬性設成 True即可, 執行結果如下圖所示
 


第三個問題, 您可以參考範例裡的 WebSite04 網站, 以這個網頁為例
http://www.blueshop.com.tw/board/more.asp?typ=NEW&fumcde=
 
這問題我想到的解決方法是在 web.sitemap 第四行不要指定 url, 就可以解決了。
 
但是如果有另一個節點也沒有 url, 可以嗎? 我們來試試看, 從下圖得知, 雖然不能二個節點俱備相同 url, 但允許一個以上的 url 值是空的。後來看了一些文章, 有隱約提到部份資訊:

---------------------
沒有特別指定 SiteMapProvider 時, ASP.NET 預設是採用 XmlSiteMapProvider 這類別(class) 來產生巡覽列, 它會讀取根目錄下的 web.sitemap 並將裡面的節點存成一個個 SiteMapNode 物件, SiteMapNode 物件裡面除了包含 url, title, description 等,也包含 key 值,其中 key 值不可以重覆, XmlSiteMapProvider 預設狀況會將 url 的值填進 key 欄位裡, 所以我先前測試時才會發生 error, 不允許有二個節點有相同 url, 而當 url 沒有值時, XmlSiteMapProvider會產生一個 GUID 當作該 SiteMapNode 物件的 key, 所以先前測試包含二個沒有 url 節點的狀況並不會發生 error(因為GUID不太會重覆)。

 


第四個問題, 我在做 website04 時想到一個問題, 其實有時候巡覽列不想從首頁開始呈現, 要如何做呢? 您可以參考範例裡的 WebSite05 網站, web.sitemap 檔案內容如下
 
如果不做任何設定,執行結果如下
 
如果不想從第一個節點開始顯示, 如下圖所示, 可以設定 ParentLevelsDisplayed, 我設定為 2,表示只顯示目前節點的上二層就好了, 所以您可以從下圖看到它只向上推算到 "討論區列表" 就停止了
 

第五個問題, 如果網站寫好了, 客戶要求節點中間的分隔符號改成斜線怎麼辦? SiteMapPath 控制項的分隔符號預設值用大於( > ), 如果要改成斜線 ,可以加入PathSeparator=" / " 屬性即可, 如下圖所示
 
不過就需要每一頁去改了, 要解決這個問題, 可以從 skin 著手, 您可以參考範例裡的 WebSite06 網站,首先在網站裡加入 App_Themes 資料夾, 並在它之下加入 default 資料夾, (資料夾名稱可以自訂不一定要取我目前使用的這個名字), 接下來在 web.config 裡的 <pages> 加入 styleSheetTheme="default",其中 default 這個名稱就是資料夾名稱, 這麼做的用意是希望 aspx 如果沒有指定要套用哪一個佈景主題 (theme) 就一律套用名稱為 default 的這個佈景主題。
接下來在 default 資料夾下新增一個 *.skin 檔,檔案名稱自行決定即可, 並在裡面加入一行 SiteMapPath 控制項的格式(如下圖), 如此一來,只要網頁裡的 SiteMapPath 沒有特別指定用什麼 skin, 就會套用這個設定, 那麼就不必一頁頁去修改,所有網頁裡的 SiteMapPath 自然就會改用斜線來區隔了。至於每個節點的 CSS 格式我就不介紹了, 各位自行去調整它的屬性應該不難測試出來才對。
 


第六個問題, 到目前為止, 我們的節點資訊都存放在 web.sitemap 裡, 這是固定的嗎?
不是的!
在預設狀況下, 網站是利用 XmlSiteMapProvider 這個類別去讀取 web.sitemap 檔案裡的設定值來生成巡覽列, 如果您想換檔案名稱, 可以參考 http://www.dotnetspider.com/resources/23945-Changing-SiteMap-File-name.aspx ,如下圖, 指定 SiteMapFile 即可
 

也就是說, 網站裡其實可以有一份以上的 sitemap 檔案, 您可以在 aspx 裡拉入 SiteMapPath 控制項之後, 設定它的 SiteMapProvider 即可, 以上圖為例,您只要指定 SiteMapPath 控制項的SiteMapProvider=CustomXmlSiteMapProvider, 它就會去讀 Site.sitemap , 而不是讀取 web.sitemap 檔案了。不過雖然檔名可以改,但副檔名只能是 sitemap (我看文章這麼寫的, 沒親自試, 有興趣的人自己試試吧)

第七個問題, 原本 sitemap 檔案裡的資訊,能不能存到 database 裡? 可以的! 但要寫程式
.NET Framework 裡有幾個預設的類別, 如果你要自己寫 SiteMapProvider, 可以繼承 SiteMapProvider class, 不過微軟事先幫你寫好一個, 叫做 StaticSiteMapProvider, 如果你要自己寫, 繼承StaticSiteMapProvider ,那麼您要寫的程式碼會少很多。此外, 微軟提供一個 XmlSiteMapProvider 類別,若沒指定, 預設值就是用它來提供 SiteMapPath資訊, 它會去讀 web.sitemap 檔案裡的資訊, 為了效能問題, 它讀取一次就不會再讀了, 直到 web.sitemap 檔案有異動, 才會取消快取並重讀一次。
所以, 如果您不想使用 web.sitemap 這類檔案形式,想將整份 xml 存到 database, 或者寫程式讀取相關記錄才組合出一份 xml,是可以的, http://www.dotblogs.com.tw/huanlin/archive/2008/04/23/3256.aspx 有介紹, 需要的人可以參考它,而 http://msdn.microsoft.com/en-us/magazine/cc163657.aspx 這篇是包含了快取的部份, 也就是說,當sql server資料有異動時, 才重覆讀取內容, 否則就不再重覆自 database 擷取資訊。
不過這裡提供的方式, 只是將 sitemap 檔案裡的內容移到 database中, 自己寫的 SiteMapProvider程式仍是"一次將所有節點資訊讀完,快取起來", 並不是每頁才 runtime且只生成目前網頁所需的節點。這觀念的差別是若一次生成全部節點, 那麼就不必每一頁都重新生成, 資料量可能會比較多一點就是了。


第八個問題, 有時節點的文字需要從 database 擷取,例如
http://www.blueshop.com.tw/board/FUM200410061532262QU/BRD20100829194858W6F.html
 
最後一個節點並不是寫成”檢視單篇留言”這類固定文字,而是呈現留言的標題, 它的上一個節點”心情分享”, 也是根據這篇留言的分類從database 擷取出來的文字, 如果留言很少則, 我們可以設法 runtime 去建立 web.sitemap, 例如每當有一個人發言,就去修改一次 web.sitemap, 方法可以參考
http://www.codeproject.com/KB/aspnet/DynamicSiteMapPath.aspx
裡的程式, 不過, 這方法好像不太實用吧, 有些論壇留言逾萬篇, 難道每新增一個留言就重新建立 web.sitemap 檔案? 反觀, 如果您是打算用在中小企業入口網站裡, 想要每當新增一個產品分類時就主動去更動 web.sitemap, 由於產品分類不太會變動, 所以還比較可能採用這個方法, 無論如何, runtime才去 database 擷取某些節點的文字、超連結, 應該是無可避免的需求, 要怎麼做到呢?
讓我們換個角度來想這個問題, 雖然網站留言很多篇, 但其實我們並不必真的產生這一大坨的 xml 節點, 但您可以發現無論是哪一篇文章, 巡覽列其實都只有三個節點, 只是第二、三節點的文字,網址需要變化罷了, 所以, 只要節點數量是固定的, 我們可以用以下方法來解決
在網頁裡, 當 SiteMapPath 控制項要生成時, SiteMap 會發生 SiteMapResolve 事件, 我們可以參考 http://msdn.microsoft.com/zh-tw/library/system.web.sitemap.sitemapresolve(VS.80).aspx 這篇, 它有提到如何runtime 地修改節點的網址, 想當然爾,也可以修改文字囉, 所以其實您可以在 web.sitemap 檔案裡,先寫好網址,然後利用這篇介紹的方法, runtime 去修改網址(例如補入QueryString 值, 或者重新給一個新 url ) , 就可以解決這個問題了。

第九個問題, 在網頁 http://www.argox.com/tw/content.php?sno=0000170&P_ID=21 
 

裡, 呈現單一產品資訊, 巡覽列顯示這產品所屬分類, 如果產品分類有一定層數,例如公司產品一定先分大類、中類、小類,而產品一定屬於某一個小類, 那麼由於巡覽列的節點數量固定, 我們在問題八已經介紹過如何解決了, 但如果產品分類是無限層的呢? 如果產品可能直接屬於某一中類呢? 節點數量就變得不固定了, 怎麼辦呢?
我們可以沿用問題八的技巧, 在 SiteMap. SiteMapResolve 事件寫程式解決:
首先, http://netpl.blogspot.com/2008/04/sitemapresolve-and-dynamic-site-map.html 這篇有介紹如何 runtime 修改節點,文章裡的範例是直接 runtime 給新的節點(如下圖), 但稍後我們小改就可以用了
 
再來, 我們可以經由 SiteMap.CurrentNode 取得目前網頁所在位置的節點
結合這二個小技巧, 我們可以將程式寫成
 

如此一來, 網頁呈現時, 就可以在目前節點的前面 runtime 地插入二個節點了, 這技巧可以用來解決這個問題。
不過我在測試時, 是自己寫 SiteMapProvider 類別的, 並沒有直接用 XmlSiteMapProvider 類別, 而上述技巧有一個隱含的問題, 我之前有提過, 為了效能著想, XmlSiteMapProvider有快取, 只有web.sitemap 檔案變動時, 才會重新讀取, 因此, 你如果在 pageA.aspx 裡寫了上述的程式, 會不會造成 XmlSiteMapProvider 裡預先讀取好的節點有所變化呢? 我沒試, 有需要的人自己去試試看吧, 如果不會是最好, 如果會, 看來也只好自己寫 SiteMapProvider 了, 下方程式是我參考網路上資訊寫的
 
您可以看到上圖裡, 我建立節點之後將它存放在 _root 變數裡, 稍後若有人要取用, 我就將 _root  複製一份傳回去,確保 _root 裡的節點資訊不會被某網頁因為增減節點而影響其他頁的結果。
上述程式由於只是示範, 並沒有從 database 擷取節點資訊, 各位自己小改一下吧。


ASP.NET 裡的 SiteMapProvider 類別傾向一次就先將所有節點蒐集好, 每一頁就不必再重做一次, 而我從以前寫程式的習慣, 就是每一頁才 runtime 生成巡覽列所需資訊而且也只擷取本網頁所需的節點而已, 所以我的重點放在如何在不同專案之間能共用程式碼。對初學者而言呢, 大多是每一頁硬寫程式, 然後將它們 copy 到下一頁, 小修改後就 OK 了, 最好是不要每頁重寫啦, 至於要自己寫, 或用 ASP.NET 現成的控制項,就由各位去決定了。


回應


姓名: (必填)
Email:
內容: (必填)
驗證碼:   (請輸入圖片中的文字,不區分大小寫)
Copyright © AllenKuo.com , 2000 - 2012, Version 11.08