Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bind Enum to DropdownList in ASP.NET MVC

0.00/5 (No votes)
23 Apr 2016 3  
Better approach to bind dropdownlist with enum in MVC

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 // correct one
    {

    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 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,
EnumDisplayNameAttribute>(attr=>attr.DisplayName,(int)eUserRole.OfficeAdmin))

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here