Passing enum values to a ListBoxFor in ASP.NET MVC

2019-06-07 17:14发布

问题:

I've read up a good bit on creating a SelectList from an Enum to populate a drop down list, and found many solutions. Here are a few examples

public static IEnumerable<SelectListItem> GetEnumSelectList<T>()
{
    return (Enum.GetValues(typeof(T)).Cast<T>().Select(
        enu => new SelectListItem() { Text = enu.ToString(), Value = enu.ToString() })).ToList();
}

public static SelectList ToSelectList<TEnum>(this TEnum enumObj) 
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    var values = from TEnum e in Enum.GetValues(typeof(TEnum))
                 select new { Id = e, Name = e.ToString() };
    return new SelectList(values, "Id", "Name", enumObj);
}

Both of these (and most others) return the names of the enum values for both Text and Value.

Example

public enum WeatherInIreland
{
     NotGreat = 0,
     Bad = 1,
     Awful = 2
} 

Results from the first two methods

<select id="Weather" name="Weather">
    <option value="NotGreat">NotGreat</option>
    <option value="Bad">Bad</option>
    <option value="Awful">Awful</option>
</select>

However, I wanted to return the name for Text and the int value for the Value.

<select id="Weather" name="Weather">
    <option value="0">NotGreat</option>
    <option value="1">Bad</option>
    <option value="2">Awful</option>
</select>

This is the code I came up with.

public static System.Web.Mvc.SelectList ToSelectList<TEnum>(this TEnum enumObj) where TEnum : struct, IComparable, IFormattable, IConvertible
{
    List<SelectListItem> selist = new List<SelectListItem>();
    foreach (int value in Enum.GetValues(typeof(TEnum)))
    {
        TEnum type = (TEnum)Enum.Parse(typeof(TEnum), value.ToString());
        selist.Add(new SelectListItem { Value = value.ToString(), Text = type.ToString() });
    }
    return new System.Web.Mvc.SelectList(selist, "Value", "Text");
}

As you can see it's a modified version of the last method above. It works, but it is ugly. I have to iterate over the Enum values as ints and then separately parse each one to return the name. Surely there's a better way of doing this?

回答1:

Unfortunately there's not really a better way to do this. In a perfect world, Enum would implement IEnumerable and you could iterate through the collection. Instead you can only either iterate through Enum.GetValues or Enum.GetNames. Whichever you choose, you'll still need to call Enum.GetName or Enum.GetValue to get the other piece of data.

Similiar StackOverflow question: C# Iterating through an enum? (Indexing a System.Array)

You can however make the code a bit more readable and linq-y:

 return
      new SelectList(
          Enum.GetValues(typeof (TEnum))
              .Select(e => new SelectListItem
              {
                 Text = Enum.GetName(typeof (TEnum), e),
                 Value = e.ToString()
              })
              .ToList(),
          "Value",
          "Text");


回答2:

You have to write it this way:

public static System.Web.Mvc.SelectList ToSelectList<T>(this Enum TEnum) where T : struct, IComparable, IFormattable, IConvertible
{

    return new SelectList( Enum.GetValues(typeof(T)).OfType<T>()
                                                .Select(x =>
                                                   new SelectListItem
                                                   {
                                                       Text = x.ToString(),
                                                       Value = ((T)x).ToString()
                                                   }));
}

REVISED ONE:

public static System.Web.Mvc.SelectList ToSelectList<TEnum>(this TEnum obj) where TEnum : struct, IComparable, IFormattable, IConvertible
{

  return new SelectList(Enum.GetValues(typeof(TEnum))
                                      .OfType<Enum>()
                                      .Select(x =>
                                      new SelectListItem
                                         {
                                          Value = (Convert.ToInt32(x)).ToString(),
                                          Text = Enum.GetName(typeof(TEnum),x)
                                         }
                                         ),"Value","Text");

}

I tested it on my machine and it works fine.



回答3:

We made a helper at work which does this really well

public static class EnumDescriptionDropDownList
{
    private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
    {
        Type realModelType = modelMetadata.ModelType;

        Type underlyingType = Nullable.GetUnderlyingType(realModelType);
        if (underlyingType != null)
        {
            realModelType = underlyingType;
        }
        return realModelType;
    }

    private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };

    public static string GetEnumDescription<TEnum>(TEnum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());

        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

        if ((attributes != null) && (attributes.Length > 0))
            return attributes[0].Description;
        else
            return value.ToString();
    }

    public static MvcHtmlString EnumDescriptionDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
    {
        return EnumDescriptionDropDownListFor(htmlHelper, expression, null);
    }

    public static MvcHtmlString EnumDescriptionDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
    {
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        Type enumType = GetNonNullableModelType(metadata);
        IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

        IEnumerable<SelectListItem> items = from value in values
                                            select new SelectListItem
                                            {
                                                Text = GetEnumDescription(value),
                                                Value = value.ToString(),
                                                Selected = value.Equals(metadata.Model)
                                            };

        // If the enum is nullable, add an 'empty' item to the collection
        if (metadata.IsNullableValueType)
            items = SingleEmptyItem.Concat(items);

        return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
    }
}

Then you simply call it on your view for the enum in the Model or ViewModel (whichever your using)

@Html.EnumDescriptionDropDownListFor(model => model.Type)    

Also if you put a Description attribute on each enum option you also get a nice user friendly string, if not then it just uses the regular string value of the enum.

[Description("MOT Checker")]    

Finally if you change the value in the select list to the following it will output the int value instead of the string also (which is what what you want i believe?);

Value = Convert.ToInt32(value).ToString(),

<option value="1">e1</option>
<option value="2">e2</option>
<option value="3">e3</option>
<option value="4">e4</option>
<option value="5">e5</option>
<option value="6">e6</option>

Hope this helps.