2012年9月22日 星期六

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


上一篇「ASP.NET MVC 3 - DropDownList 的基本設定方式」的最後有提到了 Html.DropDownListFor() 的設定,最後的內容說到了 Html.DropDownListFor() 為何有時候會無法正確的顯示預設選取項目的問題,如果是對於ASP.NET MVC 熟悉的朋友應該可以一眼看出我設定的方法是有明顯錯誤的,至於哪個地方設定錯誤?而又為什麼設定錯誤會有不一樣的顯示結果呢?

在這一篇文章中會來做個說明。


看一下前一篇文章中的 Html.DropDownListFor() 哪邊出現了錯誤,

image

圖中所標示的地方就是設定錯誤的地方,不應該去指定 Model 的關聯物件集合,因為是用了強型別的 Html 輔助方法,所以必須指定 Model 的屬性名稱才對,讓我們來看看 MSDN 中對於 Html.DropDownListFor() 的敘述,

SelectExtensions.DropDownListFor<TModel, TProperty> 方法 (HtmlHelper<TModel>, Expression<Func<TModel, TProperty>>, IEnumerable<SelectListItem>)

image

MSDN 中所說的是「運算式,可識別包含要轉譯之屬性的物件」……

說實在話,MSDN 的描述很多都讓人有看沒有懂,雖然是用中文描述,但我還是看不懂意思,畢竟這是機器翻譯的,所以看看英文的描述(如下),英文的描述就讓人比較能、夠了解意思了,

image

總之強型別的 Html 輔助方法,必須去指定 Model 的物件本身的屬性。

 

現在我們將錯誤的設定給修改為指定物件本身的屬性,

image

顯示結果:

image

image

 

大家可以注意一下產出結果的 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() 方法,只是兩者所傳近方法的參數值是有不同的,從以下兩個方法所傳進來的參數就可以看出來,

image

上面的是 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 輔助方法。

 

以上

1 則留言:

  1. 您的CategoryID 沒解釋的很清楚, 基本上就是 Selected 在Controller 要先設定 Product.CategoryID=3

    回覆刪除

提醒

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