2012年9月16日 星期日

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


怎麼突然要講起這麼基本的設定方式呢?

說實在話,我很少用 ASP.NET MVC 所預設的 DropDownList 方法,常看我 Blog 的朋友應該都知道我的 DropDownList 都是用我自己的方法來產出的,而這個設定並產出 DropDownList 的方法, 我在 Blog 中分享出來過:「ASP.NET MVC 後端產生DropDownList」。

不過近來時常都被問到有關 Html.DropDowbnList 的相關問題以及一個讓人疑惑的地方,於是我就著手寫下這一篇,這一篇跟之前「ASP.NET MVC 3 - 加入 ASP.NET MVC 3 原始碼來偵錯」是一樣的,都是用來鋪路的,因為必須要先把基本以及關鍵的地方先點出來,這樣下一篇的重點就會讓大家更加明白了。

 


廢話不多說,首先來看看 MSDN 官方文件對於 Html.DropDownList 方法與 Html.DropDownListFor 方法的說明,

MSDN - SelectExtensions.DropDownList 方法

image

MSDN - SelectExtensions.DropDownListFor 方法

image

前者 Html.DropDownList 為一般表單所使用,而後者的 Html.DropDownListFor 是要做 Model Binding 用的。

 

 

方式一:

首先來看看最典型的一個基本方法使用,使用 IEnumerable<SelectListItem>,

很多人都會預設使用這樣的方式來設定 DropDownList 的項目內容:

DropDownList(HtmlHelper, String, IEnumerable<SelectListItem>)

可以到 MSDN 了解一下 SelectListItem 類別:MSDN - SelectListItem 類別

 

後端 Controller 的操作程式內容:

image

這段程式應該無須太多解釋,categoryService.GetAll() 這個方法是我用來取得所產品類別的方法,完成 List<SelectListItem> 的資料後,把資料放到 ViewBag.CategoryItems 裡,這樣 View 就可以取得資料。

 

View:

image

而 View 這邊的設定就是使用 DropDownList(HtmlHelper, String, IEnumerable<SelectListItem>) 來完成,或許有些人會有疑問,資料放在ViewBag 中,而 ViewBag 不是說 dynamic 嗎?程式執行時不就會自動判別類型嗎?為什麼還需要去把 ViewBag.CategoryItems 做指定轉型為 IEnumerable<SelectListItem> 呢?

這是一個基本觀念的問題, 因為 HtmlHelper 都是「擴充方法」,而動態的值不能夠當成是一個參數來傳遞給擴充方法,編譯器為了要能夠選擇正確的擴充方法,在進行編譯的時候必須要知道每個參數的真正類型,如果我們所傳遞的參數是動態的,那麼編譯的時候就會產生錯誤,所以必須要將 ViewBag.CategoryItems 進行轉型,轉換為 IEnumerable<SelectListItem> 類型。

 

最後我們可以得到這樣的顯示結果:

image

HTML Code:

image

 

 

方式二:

假如我們想要一開始就指定某一個項目為預設的選項,在 Controller Action 方法的程式寫法可以用以下的方式,

image

這邊我是讓分類編號為 6 的項目為預設選擇的項目,這樣在 View 的設定是不用作任何的改變,

image

最後所得到的結果:

image

Html Code:

image

 


 

方式三:

如果你的下拉選單的項目資料來源是集合物件,其實我會比較建議使用 SelectList 來取代 SelectListItem,

MSDN - SelectList 類別

因為本質上 SelectList 其實就是 IEnumerable<SelectListItem>,而且 SelectList 的設定方式更加的簡單,我們拿前面的例子來做示範,

 

Controller Action 方法的程式內容:

image

取得產品分類資料的集合後,建立一個型別為 SelectList 的物件,我們使用以下 SelectList 建構方式來完成物件的建立,

image

第二個參數就指定要作為下拉選單項目資料值的欄位名稱,

第三個參數為指定要作為下拉選單項目顯示文字的欄位名稱,

最後一樣是把 SelectList 物件給放到 ViewBag.CategorySelectList 中。

 

View:

image

這邊 Html.DropDownList 設定方式與前面例子的設定是同樣的,但不需要去轉為 IEnumerabel<SelectListItem >,

因為 SelectList 的最底層就是繼承 IEnumerable<SelectListItem>。

 

最後得到的結果:

image

至於 Html Code 都差不多,所以就不顯示了。

 

 

方式四:

假如使用了SelectList 之後,要如何去指定下拉選單的預設選擇項目呢?

另一個 SelectList 的建構項目就有提供這樣的設定,

SelectList 建構函式 (IEnumerable, String, String, Object)

image

最後一個參數 selectedValue,就是可以用來指定為下拉選單預設的選擇項目,

 

Controller Action 方法的程式:

image

這邊我指定分類編號為「3」的項目為預設選擇項目

 

View:

View 的設定仍然是沒有變動。

image

 

最後的顯示結果:

image

Html Code:

image

 

 

方式五:

其實還有另外一種方式可以讓 View 去設定下拉選單的項目,在 View 的 Html.DropDownList 可以直接指定有裝載 SelectListItem 或 SelectList 的 ViewData 或是ViewBag名稱,很多人都知道這個方法,但相對的也有很多人並不知道可以這麼做,

Controller Action 方法的程式:

image

其實在Controller 的程式內容並沒有什麼變化,有變化的則是在 View 的設定。

View:

image

這邊的 Html.DropDownList 方法中就只需要去設定 ViewBag 的名稱「CategorySelectList」就可以了。

最後的顯示結果:

image

 

 

方式六:

接續方式五的設定方式,假如我們要去指定預設選擇項目,View 的設定還是一樣可以只設定 ViewBag 的名稱嗎?

當然可以!完全沒有影響!

 

Controller Action 方法的內容:

image

這邊的程式中指定了分類編號「6」的資料為預設選擇的項目。

View:

image

這邊的設定方式完全沒有變動,一樣只有指定  ViewBag 的名稱。

最後顯示結果:

image

Html Code:

image

這邊特別說明一下,因為我們只有指定 ViewBag 的名稱,而沒有指定下拉選單的 Tag Name,所以只有單獨指定 ViewBag 名稱的情況下,所產出的 DropDownList Tag Name 就會使用 ViewBag 的名稱,

為什麼可以只接指定有裝 SelectList 或 IEnumerable<SelectListItem> 的 ViewData 或 ViewBag 名稱呢?

問了很多人,很多人都不知道,有些人是看到別人這樣用也就跟著用,有些人是誤打誤撞,而有些人則認為這是一個 Bug …

其實這不是 Bug,要說的話…… 應該可以說這是一種「語法糖(Syntactic Sugar)」,提供程式設計時的便利性,這個只要指定 ViewData or ViewBag 名稱就可以產出下拉選單項目的方法,其實在 ASP.NET MVC 1.0 時就有了,以下為 ASP.NET MVC 原始碼中關於這段程式的內容:

 

ASP.NET NET MVC 1.0

image

image

 

ASP.NET MVC 2.0

image

image

 

ASP.NET MVC 3.0

image

image

 

看來 ASP.NET MVC 1 ~ 3 都是一樣的處理方式,所以要知道是有這樣的處理方式,但是這樣取巧而只有設定 ViewData 名稱的方式雖然有時候會覺得方便,但如果一不小心採到這個雷,例如剛剛好就有這麼一個 ViewData 名稱跟你在 View 中的下拉選單名稱一樣時,在產出的 View 就會讓人傻眼,因為有時 Html.DropDownList 只有設定名稱,是要先產出一個空的下拉選單,這下剛好在 Controller 中有設定了一個同樣名稱又剛好內容是 IEnumerable<SelectListItem> 的 ViewData,而得到的結果就會讓人出乎意外,但這並不是什麼意外或是例外還是 Bug,都只是還沒有了解背後的詳細原因所引發的誤解或是誤會而已。

 

Html.DropDownListFor

最後就來講資料繫結時會用到的 Html.DropDownListFor() 方法,我這邊的作法並不是使用 ViewData 或 ViewBag 來裝載 IEnumerable<SelectListItem>,而是另外建立一個 ViewModel 類別,而類別中有個屬性就是 IEnumerable<SelectListItem> 類別,

 

ProductViewModel:

image

而 Controller Action 方法的程式內容如下:

在程式設定 SelectList 的地方有去指定分類下拉選單的預設選項為 Product 的 CategoryID

image

View:

分別使用 Html.DropDownListFor() 與 Html.DropDownList() 來做設定上的比較,

image

最後產出的結果:

image

Html Code:

image

在上面的產出結果與 Html Code 裡面,各位有無看出什麼端倪呢?

使用 Html.DropDownListFor() 方法所產出的下拉選單並沒有對預設選項產出 selected 的屬性,而另一個使用 Html.DropDownList() 方法的卻是有產出與設選項的 Selected 屬性。

 

很多人遇到這樣的情況就會問我,他們的程式哪邊出了錯誤?還是設定上有什麼樣的問題呢?看了看這樣的設定,真的應該不會有問題呀,於是我將 View 的 Html.DropDownListFor() 的設定改成以下的內容:

image

但是這樣的修改卻是對於結果沒有什麼樣的變化…

image

 

或許是用了 SelectList 的緣故吧,所以我把程式內容就另外做了以下的修改:

使用 IEnumerable<SelectListItem>

SNAGHTML69c4f98

image

改為使用 IEnumerable<SelectListItem> 之後所產生的結果更是讓我傻眼了…

image

image

使用 IEnumerable<SelectListItem> 所產出的結果在 Html.DropDownListFor() 方法不會去設定預設選項的 Selected…

Html.DropDownList() 可以,而 Html.DropDownListFor() 就是不可以,

這就真的很弔詭呀~~~~

 

最後我就去追了 ASP.NET MVC 3 的原始碼,才發現到 Html.DropDownListFor() 的處理方式是會有不同的地方,

而如何正確的使用 Html.DropDownListFor() 方法,讓設定的預設選項可以被正確的  Selected 呢?

還有 ASP.NET MVC 3 的原始碼中是如何處理這一段呢?

下回分曉……

 

以上

22 則留言:

  1. 文章真的太讚啦,把dropdownlist用法包山包海都包進來..肝溫阿!

    回覆刪除
    回覆
    1. 感謝 Bibby 的回應,有太多人對於 ASP.NET MVC DropDownList 的使用存有很多疑慮,所以才會寫下這一篇。

      刪除
  2. 大哥您好

    請問一下,我按照您寫的試做一下,出現 名稱categoryService不存在目前內容中,請問這是什麼意思呢?

    回覆刪除
    回覆
    1. Hello, 我在文章的前面有交代「categoryService.GetAll() 這個方法是我用來取得所產品類別的方法」,
      這個 categoryService 是作為 Category 這個類別的資料操作 Service,
      所以你這邊需要置換為你自己的資料存取方法,而不是將這邊的程式一自不漏的拿過去用喔.

      刪除
    2. 不好意思我是MVC新手 在 "Category 這個類別的資料操作 Service" 我有點不清楚
      這個categoryService不存在的問題 我還是不太清楚怎麼解決

      刪除
    3. https://github.com/kevintsengtw/MVC-DropDownList-Samples
      有範例,自己找找

      刪除
  3. 這篇文章針的受益匪淺,感謝MRKT大大得詳細分析

    回覆刪除
  4. 請問這在mvc4中有改變嗎?
    我用@Html.DropDownList("DestinationId")沒有問題,但是改成@Html.DropDownList("DestinationId",(SelectList)ViewBag.DestinationId, "請選擇", new { onchange = "DestinationChange(this);" })
    selected的項目又不出現了

    回覆刪除
    回覆
    1. 從來沒有改變過,但是在這篇文章的「方式六」有說到,假如 DropDownList 名稱與 ViewBag 名稱相同時,
      會把 ViewBag 裡的值拿來放在同名稱的 DropDownList 的 Tag 裡,
      而且我在最後有說到,直接將 SelectList 或是 IEnumerable 的 SelectListItem 放到 ViewBag 然後下拉選單使用同名稱,
      這樣是一種取巧的方式,雖然方便,但容易採到雷,
      所以我會建議你將放置 SelectList 的 ViewBag 名稱與 DropDownList 名稱最好是不要一樣。

      另外我會建議你,不要直接在 Tag 裡放置事件,onchange = "DestinationChange(this);"
      建議使用 unobtrusive 的方式會比較好。

      刪除
  5. 太棒了,對我於這個初學者很有幫助,謝謝您的教學。

    回覆刪除
  6. 請問html.dropdownlist 若選單上面的值沒有使用者要的選項,可以讓使用者直接輸入嗎?再把使用者輸入的值帶入到資料庫中,下次再進入就多了一個選項

    回覆刪除
    回覆
    1. 可以呀,只是介面就不是單純使用 DropDownList,你可以 google 一些前端套件,
      例如使用 Combobox, Editable Combobox, Editable Dropdownlist 等關鍵字,
      總之這並非單純的 HTML Tag Element 或是 ASP.NET MVC 就有提供,
      必須包含前端 Javascript 的功能,也會使用到 AJAX 等技術,
      總之這並非難事。

      刪除
    2. 謝謝大大的回答,現在就去Google一下,看能找到那些可以套用,非常感謝您

      刪除
    3. 我到是覺得這個功能應該要單純化比較好,我們可以利用一些套件讓 Dropdownlist 更改為 Combo Box 的樣式,可以讓使用者直接輸入以選擇項目,如果所輸入的文字並不在下拉選單裡,那麼就應該在另一個介面裡做新增,而不建議將新增的功能也做在同一個下拉選單中。
      因為你做了新增的功能,那是不是也要做更新的功能,那也意味著刪除的功能也要跟著做,如此一來一個簡單的下拉選單變得如此複雜,是不是給它太多的責任,而使用者是否能夠適應這樣的操作方式呢?而程式設計師在開發這功能所要花的時間絕對不會少,甚至會複雜很多,耗費的時間成本是否值得呢?

      刪除
  7. kevin 大大 關於上次從資料庫連結下拉選單參考你的程式後已經可以了 非常感謝
    但是現在用 selectitem 自己給他 text value 執行就會說 無法將 string 型別轉換為 Ienumerable
    我的viewbag 跟 DROPDOWNLIST的名稱不一樣
    想問怎麼會有這樣的情形? 非常感謝

    回覆刪除
    回覆
    1. 請使用左邊的「詢問與建議」功能,將你大概怎麼寫的程式貼給我看,不然我沒有天眼通也無法意會你的描述,
      我很難判斷你的問題點到底是什麼

      刪除
    2. 謝謝K大 我有再多去爬文看書研究 已經可以了 也可以存了!!!!
      真的非常謝謝你 對我這種菜鳥真的很有幫助

      刪除
  8. K大你好,想請問一下 如果我有兩個下拉式選單,然後在第一個下拉選了選項後,第二個下拉就會同步更新
    這是使用onchange的方法嗎?還是有別的方法呢?謝謝!

    回覆刪除
    回覆
    1. 好久沒有寫前端程式了,所以自己找一下囉
      http://kevintsengtw.blogspot.tw/search/label/DropDownList
      http://kevintsengtw.blogspot.tw/2012/07/aspnet-mvc-3.html
      https://github.com/kevintsengtw/MVC-DropDownList-Samples

      刪除
  9. 好文,釐清了很多觀念~

    回覆刪除

提醒

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