1、要實現一個功能,在不一樣的頁面放置一段以下的內容,用於採集用戶行爲信息:javascript
<input type='hidden' id='page_id' value='xxxx' /> <script type="text/javascript"> //balabala... </script>
[1] 需求中還藏着一點,有些頁面加,有些頁面不加。html
2、方案java
方案一:固然能夠這樣作:找到須要採集的頁面,一個個打開將採集代碼拷貝進去,而後把xxxxx修改成分配給各頁面的值。但如此顯然違背DRY原則。web
方案二:不但願採集代碼到處被粘貼的話,那麼從View的角度,備選的機制有partial view和layout pages。若是使用前者,仍然須要被採集頁面,重複的引用包含採集代碼的partial view。若是使用後者,假使網站已經使用了多個layout pages(這很常見),依然會須要重複的粘帖採集代碼。mvc
1. 那麼咱們將二者結合起來,而且作一點折衷,用paritial view包含採集代碼,而後在各layout pages中引用partial view:ide
@Html.Partial("_TraceByPageIdScript")
讓採集代碼就能夠安然存在於_TraceByPageIdScript這個partial view中。
網站
2. 做爲page_id值的xxxx怎麼辦?google
開始的想法是,在被採集的頁面中,對ViewBag.PageId賦值(set),保證在該變量被使用前有值(不爲null)。而後覺得在_TraceByPageIdScript中就能夠使用ViewBag.Page了(get)。url
實驗可恥的失敗鳥。spa
發現pages的ViewBag和layout pages的ViewBag是不一樣的對象,從屬於不一樣的WebViewPage實例。
相似的,包括MVC3以前的ViewData,都不是用在"pages須要和layout pages共享數據"這種場景的。
[it] Gets or sets a dictionary that contains data to pass between the controller and the view.
http://msdn.microsoft.com/en-us/library/system.web.mvc.viewpage.viewdata.aspx
google到了PageData
[it] Provides array-like access to page data that is shared between pages, layout pages, and partial pages.
http://msdn.microsoft.com/en-us/library/system.web.webpages.webpagebase.pagedata(v=VS.99).aspx
因此採集代碼中的xxxx,能夠替換爲PageData["xxxx"]了
3. 這個時候發現犯了一個錯誤,斷點看了下,PageData["xxxx"]在partial pages中的值是null。(這個和俺對MSDN註釋的理解不大一致啊)
換個方式吧:
@Html.Partial("_TraceByPageIdScript", (int?)PageData["PageId"], new ViewDataDictionary())
注意:若是這裏省掉了第三個參數,那麼當null被傳遞到partial view時,會致使實際傳入的對象類型是@model指明的類型,進而致使異常。這是ASP.NET MVC 3的一個古怪的地方,尚不清楚後續版本是否有調整。
以下兩篇資料給自給了一種解法:
a. http://stackoverflow.com/questions/650393/renderpartial-with-null-model-gets-passed-the-wrong-type
@Html.Partial("_TraceByPageIdScript", new ViewDataDictionary(PageData["PageId"]))
b. http://stackoverflow.com/questions/9292852/how-do-i-invoke-a-partial-view-with-null-for-its-model
@Html.Partial("_TraceByPageIdScript", (int?)PageData["PageId"], new ViewDataDictionary())
4. 還有前面的需求[1]被丟下了,在_TraceByPageIdScript.cshtml中,對採集代碼包一下:
@model int? @if(Model.HasValue) { <input type='hidden' id='page_id' value='@Model.Value' /> <script type="text/javascript"> //balabala... </script> }
3、結語
小經周折,至此完整的實現了該需求。
除了ASP.NET MVC相關的知識外,「值即開關」模式的使用,實現了隨需加載採集代碼的效果,這樣從此若是其它頁面須要加入採集,只須要在相應頁面,經過賦值便可;再也不須要採集的頁面,賦值爲null或者注視掉賦值語句便可。
更理想的方案,實際上是被採集的頁面,徹底不用爲了被採集而進行任何修改。
作法之一,以配置的形式維護一個字典,value是page_id,key則要求能夠惟一標識某個特定頁面。若是有了這個字典,程序能夠在運行時根據請求,來找到key,進而就能找到page_id。以此做爲基礎,就能夠利用一個能共享給layout pages的變量,來告訴layout pages是否打開"輸出採集代碼"的開關。
這裏有兩個比較複雜的點:
1. 以何載體來做爲這裏說的共享變量?這也牽扯到根據請求找到page_id的時機。時機方面能夠考慮Action Filter機制,這樣天然就能利用上Controller和Pages間的橋樑:ViewBag/ViewData。layout pages拿到它,內心必定很樂呵。
http://www.manasinc.com/setting-a-viewbag-property-in-the-onresultexecuting-action-filter-in-asp-net-mvc/
2. 關鍵的難點在於,字典的key的選取。若是使用url,則當route發生變化時,相應頁面的採集就會失效。若是使用route,還要考慮參數取值的變化。
考慮到這個實現方案的複雜性,開發和維護都須要付出更多的精力,就沒有進一步的探索下去了。
P.S. PageData["XXX"] 等效於 Page.XXX