ASP.NET MVC - PageData的應用


一、要實現一個功能,在不同的頁面放置一段如下的內容,用於采集用戶行為信息:

<input type='hidden' id='page_id' value='xxxx' />
<script type="text/javascript">
    //balabala...
</script>

[1] 需求中還藏着一點,有些頁面加,有些頁面不加。

二、方案

方案一:當然可以這樣做:找到需要采集的頁面,一個個打開將采集代碼拷貝進去,然后把xxxxx修改為分配給各頁面的值。但如此顯然違背DRY原則。

方案二:不希望采集代碼處處被粘貼的話,那么從View的角度,備選的機制有partial viewlayout pages。如果使用前者,仍然需要被采集頁面,重復的引用包含采集代碼的partial view。如果使用后者,假使網站已經使用了多個layout pages(這很常見),依然會需要重復的粘帖采集代碼。

1. 那么我們將兩者結合起來,並且做一點折衷,用paritial view包含采集代碼,然后在各layout pages中引用partial view:

@Html.Partial("_TraceByPageIdScript")

讓采集代碼就可以安然存在於_TraceByPageIdScript這個partial view中。

2. 作為page_id值的xxxx怎么辦?

開始的想法是,在被采集的頁面中,對ViewBag.PageId賦值(set),保證在該變量被使用前有值(不為null)。然后以為在_TraceByPageIdScript中就可以使用ViewBag.Page了(get)。

實驗可恥的失敗鳥。

發現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>
}

三、結語

小經周折,至此完整的實現了該需求。

除了ASP.NET MVC相關的知識外,“值即開關”模式的使用,實現了隨需加載采集代碼的效果,這樣今后如果其它頁面需要加入采集,只需要在相應頁面,通過賦值即可;不再需要采集的頁面,賦值為null或者注視掉賦值語句即可。

更理想的方案,其實是被采集的頁面,完全不用為了被采集而進行任何修改。

做法之一,以配置的形式維護一個字典,value是page_id,key則要求可以唯一標識某個特定頁面。如果有了這個字典,程序可以在運行時根據請求,來找到key,進而就能找到page_id。以此作為基礎,就可以利用一個能共享給layout pages的變量,來告訴layout pages是否打開"輸出采集代碼"的開關。

這里有兩個比較復雜的點:

  1. 以何載體來作為這里說的共享變量?這也牽扯到根據請求找到page_id的時機。時機方面可以考慮Action Filter機制,這樣自然就能利用上Controller和Pages間的橋梁:ViewBag/ViewDatalayout 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM