I've made myself an extension method, which I now use in every project:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;
namespace App.Extensions
{
public static class EnumExtensions
{
public static SelectList ToSelectList(Type enumType)
{
return new SelectList(ToSelectListItems(enumType));
}
public static List<SelectListItem> ToSelectListItems(Type enumType, Func<object, bool> itemSelectedAction = null)
{
var arr = Enum.GetValues(enumType);
return (from object item in arr
select new SelectListItem
{
Text = ((Enum)item).GetDescriptionEx(typeof(MyResources)),
Value = ((int)item).ToString(),
Selected = itemSelectedAction != null && itemSelectedAction(item)
}).ToList();
}
public static string GetDescriptionEx(this Enum @this)
{
return GetDescriptionEx(@this, null);
}
public static string GetDescriptionEx(this Enum @this, Type resObjectType)
{
// If no DescriptionAttribute is present, set string with following name
// "Enum_<EnumType>_<EnumValue>" to be the default value.
// Could also make some code to load value from resource.
var defaultResult = $"Enum_{@this.GetType().Name}_{@this}";
var fi = @this.GetType().GetField(@this.ToString());
if (fi == null)
return defaultResult;
var customAttributes = fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (customAttributes.Length <= 0 || customAttributes.IsNot<DescriptionAttribute[]>())
{
if (resObjectType == null)
return defaultResult;
var res = GetFromResource(defaultResult, resObjectType);
return res ?? defaultResult;
}
var attributes = (DescriptionAttribute[])customAttributes;
var result = attributes.Length > 0 ? attributes[0].Description : defaultResult;
return result ?? defaultResult;
}
public static string GetFromResource(string defaultResult, Type resObjectType)
{
var searchingPropName = defaultResult;
var props = resObjectType.GetProperties();
var prop = props.FirstOrDefault(t => t.Name.Equals(searchingPropName, StringComparison.InvariantCultureIgnoreCase));
if (prop == null)
return defaultResult;
var res = prop.GetValue(resObjectType) as string;
return res;
}
public static bool IsNot<T>(this object @this)
{
return !(@this is T);
}
}
}
And then use it like this (in a View.cshtml, for example) (code is broken in two lines for clarity; could also make oneliner):
// A SelectList without default value selected
var list1 = EnumExtensions.ToSelectListItems(typeof(occupancyTimeline));
@Html.DropDownListFor(model => model.occupancyTimeline, new SelectList(list1), "")
// A SelectList with default value selected if equals "DesiredValue"
// Selection is determined by lambda expression as a second parameter to
// ToSelectListItems method which returns bool.
var list2 = EnumExtensions.ToSelectListItems(typeof(occupancyTimeline), item => (occupancyTimeline)item == occupancyTimeline.DesiredValue));
@Html.DropDownListFor(model => model.occupancyTimeline, new SelectList(list2), "")
Update
Based on Phil's suggestion, I've updated above code with possibility to read enum's display value from some resource (if you have any). Name of item in a resource should be in a form of Enum_<EnumType>_<EnumValue>
(e.g. Enum_occupancyTimeline_TwelveMonths
). This way you can provide text for your enum values in resource file without decorating your enum values with some attributes. Type of resource (MyResource) is included directly inside ToSelectItems method. You could extract it as a parameter of an extension method.
The other way of naming enum values is applying Description attribute (this works without adapting the code to the changes I've made). For example:
public enum occupancyTimeline
{
[Description("12 Months")]
TwelveMonths,
[Description("14 Months")]
FourteenMonths,
[Description("16 Months")]
SixteenMonths,
[Description("18 Months")]
EighteenMonths
}