網頁

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

    回覆刪除