Introduction
Sometimes, we have a scenario during development where we need to populate dropdownlist
from Enum
. There are a couple of ways to do it. But every way has its pros and cons. One should always keep in mind the SOLID and DRY principle while writing code. I will show two ways to do it and you will be able to understand which one is better and why it is better.
Approach 1 (Typical Way)
The first approach in my point of view is not very elegant, but it will surely do what we want it to.
Consider the following Enum
which we want to populate in the drop down:
public enum eUserRole : int
{
SuperAdmin = 0,
PhoenixAdmin = 1,
OfficeAdmin = 2,
ReportUser = 3,
BillingUser = 4
}
So normally, what we do is create SelectList
by adding each Enum
value in the following way in the action:
var enumData = from eUserRole e in Enum.GetValues(typeof(eUserRole))
select new
{
ID = (int)e,
Name = e.ToString()
};
Now, set it in ViewBag
so that we can use it in View
:
ViewBag.EnumList = new SelectList(enumData,"ID","Name");
and now in View
:
@Html.DropDownList("EnumDropDown",ViewBag.EnumList as SelectList)
Problem in Approach 1
The problem with the above method is that whenever we have Enum
to be binded with some Html Helper, we have to write the above Linq query code to get the enum
all values in the action which is a bit of a pain to rewrite one thing again and again. We will see next how we can make it better and reusable.
Approach 2
Now, here is an elegant way to achieve it using Extension Method and Generics, which will return Enum
values as a SelectList
for any type of Enum
:
public static class ExtensionMethods
{
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
{
Text = Enum.GetName(typeof(TEnum), x),
Value = (Convert.ToInt32(x)).ToString()
}), "Value", "Text");
}
}
and now, we just need to call it on any Enum
in action this way:
ViewBag.EnumList = eUserRole.SuperAdmin.ToSelectList();
We can also use it directly in the View
, we only have to include namespace in case it's in a separate namespace:
@Html.DropDownList("EnumDropDown",eUserRole.SuperAdmin.ToSelectList())
You will probably need to set the selected value of dropdownlist
in the case when user is editing record.
We can extend the extension method according to our requirements.
Overload with Selected Value Parameter
Here is the extension method overload to pass selected value in case we want to set selected value, you can write other overloads as well according to the need:
public static class ExtensionMethods
{
public static System.Web.Mvc.SelectList ToSelectList<TEnum>(this TEnum obj,object selectedValue)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
return new SelectList(Enum.GetValues(typeof(TEnum)).OfType<Enum>()
.Select(x =>
new SelectListItem
{
Text = Enum.GetName(typeof(TEnum), x),
Value = (Convert.ToInt32(x)).ToString()
}), "Value", "Text",selectedValue);
}
}
and usage in View
this way:
@Html.DropDownList("EnumDropDownWithSelected",
eUserRole.SuperAdmin.ToSelectList((int)eUserRole.OfficeAdmin))
Now the dropdown will have OfficeAdmin
selected by default.
In most cases, we don't want to show Enum
value in dropdown list instead of that we want to show user friendly term as dropdown text. For that purpose, we can write our Attribute
for Enum
in the following way:
Create a custom class which inherits from Attribute
type:
public class EnumDisplayNameAttribute : Attribute
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set { _displayName = value; }
}
}
and now use attribute
on Enum
:
public enum eUserRole : int
{
[EnumDisplayName(DisplayName="Super Admin")]
SuperAdmin = 0,
[EnumDisplayName(DisplayName = "Phoenix Admin")]
PhoenixAdmin = 1,
[EnumDisplayName(DisplayName = "Office Admin")]
OfficeAdmin = 2,
[EnumDisplayName(DisplayName = "Report User")]
ReportUser = 3,
[EnumDisplayName(DisplayName = "Billing User")]
BillingUser = 4
}
Now, we will need to modify or write another extension method as now we need to pick value of DisplayName
attribute.
We have two extension methods now, one which returns specific Enum
value DisplayName Attribute
value and the second which returns SelectList
against for Enum
:
public static class ExtensionMethods
{
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
{
Text = x.DisplayName(),
Value = (Convert.ToInt32(x)).ToString()
}), "Value", "Text");
}
public static string DisplayName(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
EnumDisplayNameAttribute attribute
= Attribute.GetCustomAttribute(field, typeof(EnumDisplayNameAttribute))
as EnumDisplayNameAttribute;
return attribute == null ? value.ToString() : attribute.DisplayName;
}
}
Problem with Approach 2
The second approach is much better than the first one, but there is one problem left in approach 2 which is that we have hard coded Attribute
type in the extension, it is quite possible that we have multiple Enum
attributes and we can have them decorated with different Enum
s and calling this extension method on those would not work well.
Approach 3
So, I came up with a better implementation so that consumers can pass the attribute type and property of attribute which needs to be used.
We will add another extension method which returns the attribute value and it will be generic so user can specify the attribute type itself:
public static string AttributeValue<TEnum,TAttribute>(this TEnum value,Func<TAttribute,string> func)
where T : Attribute
{
FieldInfo field = value.GetType().GetField(value.ToString());
T attribute = Attribute.GetCustomAttribute(field, typeof(T)) as T;
return attribute == null ? value.ToString() : func(attribute);
}
This extension we will consume inside the extension method which returns Enum
as SelectList
:
public static System.Web.Mvc.SelectList ToSelectList<TEnum,TAttribute>
(this TEnum obj,Func<TAttribute,string> func,object selectedValue=null)
where TEnum : struct, IComparable, IFormattable, IConvertible
where TAttribute : Attribute
{
return new SelectList(Enum.GetValues(typeof(TEnum)).OfType<Enum>()
.Select(x =>
new SelectListItem
{
Text = x.AttributeValue<TEnum,TAttribute>(func),
Value = (Convert.ToInt32(x)).ToString()
}),
"Value",
"Text",
selectedValue);
}
and now consumer can use it by passing the attribute and its property which to use for Display name, our View code would now look like:
@Html.DropDownList("EnumDropDownWithSelected", eUserRole.SuperAdmin.ToSelectList<eUserRole,
EnumDisplayNameAttribute>(attr=>attr.DisplayName,(int)eUserRole.OfficeAdmin))