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?
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");
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.
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.