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,
- 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 bind 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 us
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 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 now have two extension methods, one which returns specific Enum value DisplayName Attribute value and the second which return SelectList again 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 in 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 Enums 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,
- numDisplayNameAttribute>(attr=>attr.DisplayName,(int)eUserRole.OfficeAdmin))