上一篇「ASP.NET MVC 3 - DropDownList 的基本設定方式」的最後有提到了 Html.DropDownListFor() 的設定,最後的內容說到了 Html.DropDownListFor() 為何有時候會無法正確的顯示預設選取項目的問題,如果是對於ASP.NET MVC 熟悉的朋友應該可以一眼看出我設定的方法是有明顯錯誤的,至於哪個地方設定錯誤?而又為什麼設定錯誤會有不一樣的顯示結果呢?
在這一篇文章中會來做個說明。
看一下前一篇文章中的 Html.DropDownListFor() 哪邊出現了錯誤,
圖中所標示的地方就是設定錯誤的地方,不應該去指定 Model 的關聯物件集合,因為是用了強型別的 Html 輔助方法,所以必須指定 Model 的屬性名稱才對,讓我們來看看 MSDN 中對於 Html.DropDownListFor() 的敘述,
SelectExtensions.DropDownListFor<TModel, TProperty> 方法 (HtmlHelper<TModel>, Expression<Func<TModel, TProperty>>, IEnumerable<SelectListItem>)
MSDN 中所說的是「運算式,可識別包含要轉譯之屬性的物件」……
說實在話,MSDN 的描述很多都讓人有看沒有懂,雖然是用中文描述,但我還是看不懂意思,畢竟這是機器翻譯的,所以看看英文的描述(如下),英文的描述就讓人比較能、夠了解意思了,
總之強型別的 Html 輔助方法,必須去指定 Model 的物件本身的屬性。
現在我們將錯誤的設定給修改為指定物件本身的屬性,
顯示結果:
大家可以注意一下產出結果的 HTML Code,雖然在 Htmk.DropDownListFor() 的設定中,於 htmlAttributes 裡有去指定產出 HTML 時下拉選單要用的 id 與 name,但是最後產出的 Html Code 裡卻只有 id 有使用我所指定的值,而 name 卻沒有使用指定的值,而是只接使用 Model 的 expession 字串,也就是「Product.CategoryID」,關於這一點,可以直接追一下 ASP.NET MVC 的原始碼,看了之後就可以了解 Html.DropDownListFor() 的產出結果為何式這樣的,
我們所使用的是基本的 DropDownListFor() 方法:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
{
return DropDownListFor(htmlHelper, expression, selectList, null /* optionLabel */, null /* htmlAttributes */);
}
進入另一個 DropDownListFor() 方法:
在這邊可以看到樣進入 DropDownListHelper() 方法時,所傳入的第二個參數是給 ExpressionHelper.GetExpressionText(expression) 的值,
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
return DropDownListHelper(htmlHelper, ExpressionHelper.GetExpressionText(expression), selectList, optionLabel, htmlAttributes);
}
DropDownListHelper() 方法:
private static MvcHtmlString DropDownListHelper(HtmlHelper htmlHelper, string expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
return SelectInternal(htmlHelper, optionLabel, expression, selectList, false /* allowMultiple */, htmlAttributes);
}
無論是 DropDownList() 或是 DropDownListFor() 都會使用到 DropDonListHelper() 方法,只是兩者所傳近方法的參數值是有不同的,從以下兩個方法所傳進來的參數就可以看出來,
上面的是 DropDownList() 的使用方式,第二個參數是傳入指定的 name,下面的是 DropDownListFor() 的使用方式,第二個參數是傳入ExpressionHelper.GetExpressionText(expression) 的值。
SelectInternal() 方法:
這個方法就是最後產出下拉選單 HTML Code 的地方,由於這個方法會牽涉很多層面,所以我就不細說這個方法的運作,有興趣的朋友可以直接到 ASP.NET MVC 原始碼中去做了解。
1: private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple, IDictionary<string, object> htmlAttributes)
2: {
3: string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
4: if (String.IsNullOrEmpty(fullName))
5: {
6: throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
7: }
8:
9: bool usedViewData = false;
10:
11: // If we got a null selectList, try to use ViewData to get the list of items.
12: if (selectList == null)
13: {
14: selectList = htmlHelper.GetSelectData(fullName);
15: usedViewData = true;
16: }
17:
18: object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
19:
20: // If we haven't already used ViewData to get the entire list of items then we need to
21: // use the ViewData-supplied value before using the parameter-supplied value.
22: if (!usedViewData)
23: {
24: if (defaultValue == null)
25: {
26: defaultValue = htmlHelper.ViewData.Eval(fullName);
27: }
28: }
29:
30: if (defaultValue != null)
31: {
32: IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
33: IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
34: HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
35: List<SelectListItem> newSelectList = new List<SelectListItem>();
36:
37: foreach (SelectListItem item in selectList)
38: {
39: item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
40: newSelectList.Add(item);
41: }
42: selectList = newSelectList;
43: }
44:
45: // Convert each ListItem to an <option> tag
46: StringBuilder listItemBuilder = new StringBuilder();
47:
48: // Make optionLabel the first item that gets rendered.
49: if (optionLabel != null)
50: {
51: listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
52: }
53:
54: foreach (SelectListItem item in selectList)
55: {
56: listItemBuilder.AppendLine(ListItemToOption(item));
57: }
58:
59: TagBuilder tagBuilder = new TagBuilder("select")
60: {
61: InnerHtml = listItemBuilder.ToString()
62: };
63: tagBuilder.MergeAttributes(htmlAttributes);
64: tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
65: tagBuilder.GenerateId(fullName);
66: if (allowMultiple)
67: {
68: tagBuilder.MergeAttribute("multiple", "multiple");
69: }
70:
71: // If there are any errors for a named field, we add the css attribute.
72: ModelState modelState;
73: if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
74: {
75: if (modelState.Errors.Count > 0)
76: {
77: tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
78: }
79: }
80:
81: tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
82:
83: return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
84: }
會說這兩篇下拉選單的基本設定方式,因為有很多朋友遇到一些問題然後來問我,但我一直以來都是用我自己的下拉選單產生方法,所以對於 ASP.NET MVC 原生的下拉選單設定就有些生疏,所以也藉著這兩篇來做個複習,並且搭配 ASP.NET MVC 原始碼的使用,也讓我了解 HTML 輔助方法的細節,建議各位有想要製作自己的 HTML 輔助方法的朋友,可以先好好的了解原生的 HTML 輔助方法,如此可以幫助我們寫出更好用的 HTML 輔助方法。
以上
您的CategoryID 沒解釋的很清楚, 基本上就是 Selected 在Controller 要先設定 Product.CategoryID=3
回覆刪除