2013年9月15日 星期日

當 ASP.NET MVC 的 DropDownList 遇到了 ViewData or ViewBag

這個部落格有關 DropDownList 的文章有 10 篇,而主要在講解 DropDownList HtmlHelper 有以下兩篇:

ASP.NET MVC 3 - DropDownList 的基本設定方式

ASP.NET MVC 3 - DropDownListFor 的設定方式

另外進階一點的有:

ASP.NET MVC 3 - 基本三層連動式下拉選單應用

不過還是時常會遇到有人在問 ASP.NET MVC DropDownList 怎麼設定以及卡關的問題,而這些問題中就屬有關 ViewData or ViewBag 的最多,甚至於有很多遇到問題的人會視為 Bug 或是列為絕對不能用的作法。其實這都是沒有詳細去追查原因的擅自定論,如果花點時間去追查一下 ASP.NET MVC 的原始碼就會知道原因,而且也會知道怎麼去正確的設定 DropDownList。

 


我在「ASP.NET MVC 3 - DropDownList 的基本設定方式」這一篇的「方式六」有說明將 SelectList 或 IEnumerable<SelectListItem> 資料放在 ViewData 裡,然後再將 ViewPage 裡的 DropDownList 名稱設定與 ViewData 名稱相同,就可以直接讓 DropDownList 在沒有設定 helper 裡 selectList 的情況下,就會直接把 ViewData 裡的 selectList 給帶進去,如下所示:

Controller:

image

ViewPage:

image

結果:

image

 

如果要設定某個項目為已選擇的狀態,在 Controller 裡做修改,

image

而前端 ViewPage 設定不變的情況下,DropDownlist 就可以直接呈現已選選的項目,

image

image

 

如果你想要加上 optionLabel 與 htmlAttributes 的話,可以使用以下的方式:

image

結果:

image

 

BUT!你如果是用以下的方式,原本有已選擇項目的內容就會變成沒有選擇項目的狀況,

image

image

 

Why ? 難道這是 Bug 嗎?

不,我不認為這是一種 Bug,我在之前的文章也說過,Html.DropDownList 的名稱使用同樣名稱且裝有 SelectList 資料的 ViewData,就會直接帶入 ViewData 內的 SelectList 資料,而無須在 Html.DropDownList 裡去設定 SelectList。

再來看看 ASP.NET MVC 原始檔裡 SelectExtensions 內的 SelectInternal Method,

image

當沒有給 selectList 資料時,就會嘗試去找同樣名稱的 ViewData,以下是 GetSelectData 的程式內容,

image

以上的 Method 與程式在 ASP.NET MVC 1 ~ ASP.NET MVC 4 裡面都是大致相同的,如果是 Bug 的話,怎麼會經過這麼多版本還會維持一樣的作法呢?

 

如果不是 Bug 的話,那為何用這樣的方式:

image

跟用這樣的方式:

image

前面的做法就無法顯示已選擇項目,而後面的作法就可以正常顯示已選擇項目,兩種的結果會差這麼多呢?不是說可以直接使用 ViewData 的名稱嗎?

 

還是回到原始碼的程式裡,先說 DropDownList 名稱設定與 ViewData 相同而且 SelectList 為 null 的情況,在這樣的情況下,SelectInternal method 裡會因為 selectList 為 null 而去找相同名稱的 ViewData,

image

再來因為是從 ViewData 取得 SelectList 資料,所以 usedViewData 就會為 true,而不會執行接下來的程式,

image

image

接著執行後面的程式然後輸出結果,

image

 

而如果是 DropDownList 有設定與 ViewData 相同名稱,而且又去設定 SelectList 內容,

image

因為有在 DropDownList 裡設定 selectList,以下的程式就不會執行,

image

因為不會執行到上面的程式,所以就會執行下面的程式並且取得 defaultValue,

image

下面的程式裡的變數內容,有看出端倪了嗎?

image

但接下來要從 defaultValues 取得 value 的字串值時,就得到這樣的結果,

image

這樣的結果影響了接著要取得 selectedValues 的內容,

image

因為是用「System.Web.Mvc.SelectList」的字串去跟 selectList 內容做比對,

image

就造成原本在 Controller 有設定已選擇項目卻變成沒有選擇的狀況,

image

所以最後頁面上所呈現的下拉選單裡原本設定已選擇的項目就變成沒有選擇,

image

原本 Controller 裡是有設定已選擇項目的,

image

 

結論:

當 DropDownList 名稱與裝有 SelectList 內容的 ViewData 相同時,DropDownList 的 selectList 就不要給任何的資料,如果需要設定後面的 optionLabel 與 htmlAttributes 時,selectList 就設定為 null。

DropDownList 所設定的名稱應避免與裝有 SelectList 內容的 ViewData 名稱相同,而且盡量少用以下的作法,以免造成混亂與誤解。

image

image

 

建議的作法:

image

DropDownList 所使用的名稱與 ViewData 的名稱不同,並且自行設定 DropDownList 的 selectList。

image

 

延伸閱讀:

如果對於 ASP.NET MVC 的實作有任何疑問或是想知道運作的流程,可以下載 ASP.NET MVC 的原始碼然後自行做追蹤,用這樣的方式解開你的疑問與不了解,而不是聽信網路上的人云亦云或是片段之辭。

ASP.NET MVC 3 - 加入 ASP.NET MVC 3 原始碼來偵錯

KingKong Bruce記事: 使用ASP.NET MVC 3 RTM原始碼進行錯誤追蹤

ASP.NET MVC - 使用ASP.NET MVC 4的原始碼進行除錯 - 天空的垃圾桶- 點部落

The Will Will Web | 如何對 ASP.NET MVC 4 原始碼進行偵錯 (終極完整觀念版)

 

以上

4 則留言:

  1. 文章開頭「ASP.NET MVC 3 - DropDownListFor 的設定方式」超連結好像跑掉了

    回覆刪除
  2. 我試的心得是如果自己設定選取的項目,selectList可以放在同名的viewdata裡,而selectList參數要設為null
    一旦指定了selectList參數,同名的viewdata值會被用來設定選取選項,除非這個selectList要用viewdata值來設定選取項目,不然就不該存在這個viewdata值,不知是否有誤?

    回覆刪除
  3. Html.DropDownList 名稱與 ViewBag 名稱相同時,第二個 IEnumerable(SelectListItem) 參數就要用 null,不然有設定已選擇項目就會沒有作用,所以要設為 null,最好的方式就是避免同名稱。

    回覆刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。