兩大 Helper 核心差異
| 特性 | @Html.DropDownList | @Html.DropDownListFor |
| 型別安全 | 字串名稱(Magic String) | Lambda 運算式(編譯期檢查) |
Model 綁定 | 手動對應 name 屬性 | 自動綁定至 Model 屬性 |
驗證訊息 | 需手動 @Html.ValidationMessage("name") | 自動 @Html.ValidationMessageFor(m => m.Prop) |
適用場景 | 簡單頁面、非強型別 View、動態表單 | 標準強型別 View、表單編輯、資料註解驗證 |
一、非強型別:@Html.DropDownList
Controller
public ActionResult Index()
{
// 方式 1:IEnumerable
ViewBag.AreaId = _repo.GetAreas()
.Select(a => new SelectListItem { Text = a.AreaName, Value = a.AreaId.ToString() });
// 方式 2:SelectList(更簡潔)
// ViewBag.AreaId = new SelectList(_repo.GetAreas(), "AreaId", "AreaName");
return View();
}
View
@* 基本用法 *@
@Html.DropDownList("AreaId")
@* 加入預設提示項 *@
@Html.DropDownList("AreaId", "== 請選擇縣市 ==")
@* 自訂 HTML 屬性 *@
@Html.DropDownList("AreaId", null, "== 請選擇 ==", new { @class = "form-control", id = "ddlArea" })
二、強型別:@Html.DropDownListFor
ViewModel 設計(推薦將下拉資料來源放入 ViewModel)
public class SettingsViewModel
{
// 表單繫結目標屬性
public string SelectedAreaId { get; set; }
// 下拉選單資料來源(唯讀)
public IEnumerable AreaOptions { get; set; }
}
Controller
public ActionResult Index()
{
var vm = new SettingsViewModel
{
AreaOptions = _repo.GetAreas()
.Select(a => new SelectListItem { Text = a.AreaName, Value = a.AreaId.ToString() })
};
return View(vm);
}
[HttpPost]
public ActionResult Index(SettingsViewModel vm)
{
if (!ModelState.IsValid)
{
// 重新填充下拉資料(必要!)
vm.AreaOptions = _repo.GetAreas().Select(a => new SelectListItem { Text = a.AreaName, Value = a.AreaId.ToString() });
return View(vm);
}
// 處理業務邏輯
return RedirectToAction("Success");
}
View
@model SettingsViewModel
@* 兩參數重載:expression + IEnumerable *@
@Html.DropDownListFor(m => m.SelectedAreaId, Model.AreaOptions, "== 請選擇 ==", new { @class = "form-select" })
@* 驗證訊息自動對應 *@
@Html.ValidationMessageFor(m => m.SelectedAreaId, "", new { @class = "text-danger" })
三、進階實戰技巧
1. Enum 直接綁定下拉框
public enum OrderStatus { Pending = 1, Paid = 2, Shipped = 3, Completed = 4 }
public static class EnumExtensions
{
public static IEnumerable ToSelectList(this TEnum enumObj, string selectedValue = null) where TEnum : struct
{
return Enum.GetValues(typeof(TEnum))
.Cast()
.Select(e => new SelectListItem
{
Text = e.GetDisplayName(), // 需自行實作或用 DescriptionAttribute
Value = Convert.ToInt32(e).ToString(),
Selected = e.ToString() == selectedValue
});
}
}
// View
@Html.DropDownListFor(m => m.Status, Model.Status.ToSelectList(Model.Status.ToString()))
2. 級聯下拉框
// View
@Html.DropDownListFor(m => m.ProvinceId, Model.Provinces, "請選擇省份", new { @class = "form-select", id = "ddlProvince" })
@Html.DropDownListFor(m => m.CityId, Enumerable.Empty(), "請先選擇省份", new { @class = "form-select", id = "ddlCity", disabled = "disabled" })
@section Scripts {
}
3. 選項分組
// Controller
var groups = new List
{
new SelectListGroup { Name = "北部" },
new SelectListGroup { Name = "中部" },
new SelectListGroup { Name = "南部" }
};
ViewBag.Areas = _repo.GetAreas()
.Select(a => new SelectListItem
{
Text = a.Name,
Value = a.Id.ToString(),
Group = groups.First(g => g.Name == a.Region)
});
// View 會自動渲染
四、常見問題速查表
| 問題 | 原因 | 解決 |
| 下拉框無資料 | ViewBag 鍵名拼寫錯誤、或未轉型 (IEnumerable)ViewBag.Key | 檢查鍵名一致性;強型別 ViewModel 避免此類錯誤 |
| 驗證失敗回傳 View 時下拉框空白 | POST Action 未重新填充 ViewModel 的集合屬性 | 抽取 PopulateViewModel(vm) 方法,GET/POST 共用 |
| Model 屬性有值但下拉框未選中 | 集合中 Value 型別不匹配(如 int vs string) | 確保 Value 為字串,且與 Model 屬性 ToString() 一致 |
| 多選綁定失敗 | 使用 DropDownListFor 而非 ListBoxFor | Model 屬性為陣列/清單時改用 @Html.ListBoxFor |
五、最佳實踐總結
- 預設使用強型別
DropDownListFor + ViewModel,享受編譯期檢查與自動驗證。
- 下拉資料來源集中管理:建立
IDropdownService 或靜態 DropdownHelper,避免 Controller 重複查詢邏輯。
- POST 回傳 View 前必重填集合屬性,或使用
ModelState 自動保留(需 HtmlHelper 版本支援)。
- 複雜 UI 需求(分組、停用、圖示、搜尋)改用 Select2 / Choices.js 等前端元件,後端僅提供 JSON API。