有 EnumDropDownListFor 為什麼還要客製 Enum to Dropdownlist ?

enum 可以讓資料儲存更有效率(string –> int), 擺脫字串比對時的不便(e.g. 手誤..),也可以有效降低無用資料進入資料庫的機會,為了讓開發人員擁有更大的開發效率 ASP.NET MVC 5 也提供了 EnumDropDownListFor 的 HTML helper,那為什麼我還想要客製呢? 就讓我細說分明

EnumDropDownListFor 不足之處

  1. 預設使用 Value 當做下拉選單選項文字

    • enum 的值無法有空白字元,移除空白字元後意思就不同了
  2. 無法自訂 html name

    • name 是 model binding 的基礎,在需要 post list 時,無法自訂 name 會造成程式複雜度提高

如何解決

大家可能都有不同想法,以下是我自己常用的方式,提供給大家參考,如果大家有更好的方式請教我幾招

  • 原始 enum

    public enum RegionEnum
    {
        Default,
        ROC,
        SouthKorea,
        USA,
        UK,
        Japan
    }
    
  1. 前置作業

    • 為 enum 加上正確顯示名稱

      以下兩種方法擇一即可

      • Description(“showName”)

        • using System.ComponentModel;

          public enum RegionEnum
          {
              Default,
              [Description("Description:R.O.C")]
              ROC,
              [Description("Description:South Korea")]
              SouthKorea,
              [Description("Description:U.S.A")]
              USA,
              [Description("Description:U.K.")]
              UK,
              Japan
          }
          
      • Display(Name=“showName”)

        • using System.ComponentModel.DataAnnotations;

          public enum RegionEnum
          {
              Default,
              [Display(Name= "DisplayName:R.O.C")]
              ROC,
              [Display(Name = "DisplayName:South Korea")]
              SouthKorea,
              [Display(Name = "DisplayName:U.S.A")]
              USA,
              [Display(Name = "DisplayName:U.K.")]
              UK,
              Japan
          }
          
    • 加上取得正確名稱的 helper

      • Description("showName") 使用 GetEnumDescription
      • Display(Name="showName") 則使用 GetEnumDisplayName

        public static class CustomerEnumHelper
        {
            /// <summary>
            /// 取得 Enum 的 Description
            /// </summary>
            /// <param name="value">Enum</param>
            /// <returns>Enum 的 Description</returns>
            public static string GetEnumDescription(System.Enum value)
            {
                FieldInfo field = value.GetType().GetField(value.ToString());
                
                DescriptionAttribute customAttribute = field.GetCustomAttribute<DescriptionAttribute>(false);
                
                if (customAttribute != null)
                {
                    string name = string.IsNullOrWhiteSpace(customAttribute.Description) ? string.Empty : customAttribute.Description;
                    if (!string.IsNullOrEmpty(name))
                        return name;
                }
                return value.ToString();
            }
            /// <summary>
            /// 取得 Enum 的 DisplayName
            /// </summary>
            /// <param name="value">Enum</param>
            /// <returns>Enum 的 DisplayName</returns>
            public static string GetEnumDisplayName(System.Enum value)
            {
                FieldInfo field = value.GetType().GetField(value.ToString());
                
                DisplayAttribute customAttribute = field.GetCustomAttribute<DisplayAttribute>(false);
                if (customAttribute != null)
                {
                    string name = string.IsNullOrWhiteSpace(customAttribute.GetName()) ? string.Empty : customAttribute.GetName();
                    if (!string.IsNullOrEmpty(name))
                        return name;
                }
                return value.ToString();
            }
        }
        
  2. 自行組裝 SelectList

    • Controller(請記得選用正確方法)

      IList<SelectListItem> list = Enum.GetValues(typeof(RegionEnum))
                              .Cast<RegionEnum>()
                              .Select(x => new SelectListItem
                              {
                                  Text = CustomerEnumHelper.GetEnumDisplayName(x),//CustomerEnumHelper.GetEnumDescription(x) ,
                                  Value = ((int)x).ToString()
                              })
                              .ToList();
          ViewBag.list = list;
      
    • View

       @{
          var list= ViewBag.list as List<SelectListItem>;
        }
      @Html.DropDownList("a", list)
      
    • 效果

      1selectlist

  3. 前端 html 組裝

    • 需注意用量,避免影響效能問題
    • View (請記得選用正確方法)

       <select name="a">
      @foreach (RegionEnum enumItem in Enum.GetValues(typeof(RegionEnum)))
      {
          <option @((enumItem == RegionEnum.Default) ? "selected='selected'" : String.Empty) value="@(enumItem)">@CustomerEnumHelper.GetEnumDisplayName(enumItem)</option>
      }
      </select>
      
    • 效果

      2view

    1. 擴充 html helper
    2. 程式碼是參考 Html.EnumDropDownListFor 原始碼來的,出處在 ASP-NET-MVC/aspnetwebstack/src/System.Web.Mvc/Html/SelectExtensions.cs
    3. 擴充 helper (請記得選用正確方法)

      public static MvcHtmlString EnumDropDownList(this HtmlHelper htmlHelper, string name, Type enumType, string defaultValue = "")
      {
          IList<SelectListItem> selectList = GetSelectList(enumType, defaultValue);
          return SelectExtensions.DropDownList(htmlHelper, name, selectList, defaultValue);
      
      }
      
      public static IList<SelectListItem> GetSelectList(Type type, string defaultValue)
      {
          IList<SelectListItem> selectList = GetSelectList(type);
          if (selectList.Count != 0 && string.IsNullOrEmpty(defaultValue))
          {
              selectList[0].Selected = true;
          }
          else
          {
              for (int index = selectList.Count - 1; index >= 0; --index)
              {
                  SelectListItem selectListItem = selectList[index];
                  selectListItem.Selected = selectListItem.Text == defaultValue;
              }
          }
          return selectList;
      }
      
      public static IList<SelectListItem> GetSelectList(Type type)
      {
          IList<SelectListItem> selectListItemList = (IList<SelectListItem>)new List<SelectListItem>();
          Type type1 = Nullable.GetUnderlyingType(type);
          if ((object)type1 == null)
              type1 = type;
          Type type2 = type1;
          if (type2 != type)
              selectListItemList.Add(new SelectListItem()
              {
                  Text = string.Empty,
                  Value = string.Empty
              });
          foreach (FieldInfo field in type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField))
          {
              object rawConstantValue = field.GetRawConstantValue();
              Enum enumValue = (Enum)(Enum.Parse(type,field.Name));
              selectListItemList.Add(new SelectListItem()
              {
                  Text = GetEnumDisplayName(enumValue),//GetEnumDescription(enumValue)
                  Value = rawConstantValue.ToString()
              });
          }
          return selectListItemList;
      }
      
    • View

      @Html.EnumDropDownList("a", typeof(RegionEnum), " - Choice - ")
      
    • 效果

      2view

    參考資料

    1. SelectExtensions.EnumDropDownListFor 方法
    2. ASP-NET-MVC/aspnetwebstack/src/System.Web.Mvc/Html/SelectExtensions.cs