Share via


ASP.NET MVC - Creating a DropDownList helper for enums

Do the types you work with in your ASP.NET MVC models ever have enums? Mine do from time to time and I’ve found myself needing to render a dropdown list to allow the user to select the enum value.

For the purposes of this post, I will be working with a Person class and a Color enum (I’ll direct you to some posts by my colleagues to help you decide whether you think Color makes a good enum: here, here and here), but we’ll go with it here :-)

 public enum Color
{
    Red,
    Green,
    Blue,
    BrightRed,
    BrightGreen,
    BrightBlue,
}
 public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Color FavoriteColor { get; set; }
}

Implementing EnumDropDownList

This post will look at how we can construct a Html Helper to allow us to easily achieve this in a view. Html Helpers are simply extension methods for HtmlHelper, so we will will start with a static class and extension method:

 public static class HtmlDropDownExtensions
{
    public static MvcHtmlString EnumDropDownList<TEnum>(this HtmlHelper htmlHelper, string name, TEnum selectedValue)
    {
    }
}

In the signature above, name is the name of the property we’re rendering and selectedValue is the current model value. The flow of our method will be

  • Get the list of possible enum values
  • Convert the enum values to SelectListItems
  • Use the in-built html helpers to render the SelectListItems as a drop down list

The following code does just that:

     public static MvcHtmlString EnumDropDownList<TEnum>(this HtmlHelper htmlHelper, string name, TEnum selectedValue)
    {
        IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum))
            .Cast<TEnum>();

        IEnumerable<SelectListItem> items =
            from value in values
            select new SelectListItem
                    {
                        Text = value.ToString(),
                        Value = value.ToString(),
                        Selected = (value.Equals(selectedValue))
                    };

        return htmlHelper.DropDownList(
            name,
            items
            );
    }

Note that we’re using the string value of the enum as the Value for the SelectListItem – fortunately the default model binding in ASP.NET MVC will handle that when the value gets POSTed back.

With this method in place, we can render a drop down using the following

 <%= Html.EnumDropDownList("Person.FavoriteColor", Model.Person.FavoriteColor) %>

Implementing EnumDropDownListFor

The helper above is a definite improvement compared to dealing with the enum values directly in the view. This helps to keep your views nice and clean, and the helper fits nicely in alongside some of the existing helpers e.g.

 <%= Html.TextBox("Person.Id", Model.Person.Id)%>

But what if you’re used to using the strongly-typed Html helpers, e.g.

 <%= Html.TextBoxFor(model => model.Person.Id)%>

If that’s the case then the helper that we just created might not feel quite right. So, let’s create another helper that will fit in better alongside the strongly-typed helpers. The first thing that we need to sort out is the function signature:

 public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)

Notice that we’re now taking an Expression<> that describes how to get from the model to the property that we’re interested in. With that in place we’re pretty much set. We’ll take advantage of the ModelMetadata.FromLambdaExpression function which allows us to retrieve the ModelMetadata for the property that is described by the expression, and we’ll use its Model property to get the current value:

 public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum)).Cast<TEnum>();

    IEnumerable<SelectListItem> items =
        values.Select(value => new SelectListItem
        {
            Text = value.ToString(),
            Value = value.ToString(),
            Selected = value.Equals(metadata.Model)
        });

    return htmlHelper.DropDownListFor(
        expression,
        items
        );
}

Now we can use the following syntax in our view:

 <%= Html.EnumDropDownListFor(model => model.Person.FavoriteColor)%>

Nullable Enum Support

The strongly typed version is pretty nice, but it turns out that there are a couple of limitations (ignoring the basics like argument validation ;-) ). The first issue is that it fails if we have a nullable enum, i.e. if our FavoriteColor property was defined as Color? rather than Color. This is the C# shorthand for Nullable<Color>, so we need to check for nullable types and pass the base type to Enum.GetValues. For this we’ll create a little helper function

 private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
{
    Type realModelType = modelMetadata.ModelType;

    Type underlyingType = Nullable.GetUnderlyingType(realModelType);
    if (underlyingType != null)
    {
        realModelType = underlyingType;
    }
    return realModelType;
}

And update our html helper

 public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    Type enumType = GetNonNullableModelType(metadata);
    IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

    IEnumerable<SelectListItem> items =
        values.Select(value => new SelectListItem
        {
            Text = value.ToString(),
            Value = value.ToString(),
            Selected = value.Equals(metadata.Model)
        });

    if (metadata.IsNullableValueType)
    {
        items = SingleEmptyItem.Concat(items);
    }

    return htmlHelper.DropDownListFor(
        expression,
        items
        );
}

We’re calling the GetNonNullableModelType in the second line to ensure that the type passed to Enum.GetValues is a non-nullable enum type. Also, notice that we’re checking whether the type is nullable and adding an empty item to the list if it is. For ease of use this is declared as a field on the class as

 private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };

The reason that this is important is that for a nullable enum you would want to support setting the value to null, i.e. “no value”!

We can use this version of the helper in the same way as the previous version, it’s just that it now supports nullable enums – notice the blank entry at the top of the list:

image

Adding flexibility

Our helper is really starting to take shape. We’ve got an old-school version that takes the name and value and a strongly-typed version that takes an expression and deals with both nullable and non-nullable enums. However, the screenshot above highlights another problem: enum names aren’t necessarily user friendly – “BrightRed” is probably now how you would want to present the value to the user. With that in mind we’ll make one final tweak to allow more flexibility over how the enum values are displayed to the user. To achieve this, we will simply make use of TypeConverters:

 public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    Type enumType = GetNonNullableModelType(metadata);
    IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();

    TypeConverter converter = TypeDescriptor.GetConverter(enumType);

    IEnumerable<SelectListItem> items =
        from value in values
        select new SelectListItem
                   {
                       Text = converter.ConvertToString(value), 
                       Value = value.ToString(), 
                       Selected = value.Equals(metadata.Model)
                   };

    if (metadata.IsNullableValueType)
    {
        items = SingleEmptyItem.Concat(items);
    }

    return htmlHelper.DropDownListFor(
        expression,
        items
        );
}

Note that we are using the converter only for the SelectListItem.Text. The value property needs to be the enum name otherwise the model binding will fail. If we apply these changes and re-run our site we’ll see that nothing has changed! That’s because the default type converter is simply calling ToString on the enum values. All we’ve done so far is to add the extensibility point to EnumDropDownListFor. To make of of it we need to apply a type converter to our enum

 [TypeConverter(typeof(PascalCaseWordSplittingEnumConverter))]
public enum Color
{
    Red,
    Green,
    Blue,
    BrightRed,
    BrightGreen,
    BrightBlue,
}

I’ve omitted the code for PascalCaseWordSplittingEnumConverter, but it simply splits words based on Pascal-casing rules as show below (note the space in “Bright Red” etc)

image

Summary

This started out as a pretty short post in my mind, but grew as I added support for a strongly-typed version, added support for nullable enums and then support for type converters.

The Pascal-casing type converter is just a simple example. You’d probably want to use a type converter that looked for attributes on your enum values to allow you to specify the display value. Better still would be a converter that looks for attributes that let you specify the resource to use for the display value so that you get the benefits of localisation etc.

Finally, a reminder that this is sample code and the usual disclaimers apply etc. If you do improve on it or find a bug then let me know:-)

Comments

  • Anonymous
    June 02, 2010
    Cool article.  I made some changes to the extension method: public static class HtmlExtensions    {        public static MvcHtmlString EnumDropDownListFor<TModel,TEnum>(this HtmlHelper<TModel> htmlHelper,Expression<Func<TModel,TEnum>> expression)        {            IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum))                .Cast<TEnum>();            TEnum prop = expression.Compile().Invoke(htmlHelper.ViewData.Model);            IEnumerable<SelectListItem> items =                from value in values                select new SelectListItem                        {                            Text = value.ToString(),                            Value = value.ToString(),                            Selected = (value.Equals(prop))                        };            return SelectExtensions.DropDownListFor<TModel,TEnum>(htmlHelper,expression,items);        }    }

  • Anonymous
    June 03, 2010
    Hi Bryan,   That's an interesting alternative approach. One thing you might want to check is the cost of the expression compilation. I think that the ASP.NET MVC team did some did some work to cache expression compilations (you can always have a look in the source to see!) The other thing that comes to mind is that the change won't cater for properties which are nullable enums. To do this you'd need to take a similar approach to the one from the post:        public static MvcHtmlString EnumDropDownListForX2<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)        {            Type enumType = typeof(TEnum);            Type actualEnumType = Nullable.GetUnderlyingType(enumType); // handle nulls            IEnumerable<TEnum> values = Enum.GetValues(actualEnumType).Cast<TEnum>();            TEnum prop = expression.Compile().Invoke(htmlHelper.ViewData.Model);            IEnumerable<SelectListItem> items = from value in values                                                select                                                    new SelectListItem                                                    {                                                        Text = value.ToString(),                                                        Value = value.ToString(),                                                        Selected = (value.Equals(prop))                                                    };            if (enumType != actualEnumType)            {                items = SingleEmptyItem.Concat(items);            }            return SelectExtensions.DropDownListFor(htmlHelper, expression, items);        } Thanks for your comments - Stuart

  • Anonymous
    June 04, 2010
    Yea...I totally need to download the source and start getting into it.  I'm relatively a newbie in the MVC world.  Thanks for the heads up on the nullable enums.  I have to admit I make the mistake of considering the "coolness" of the code before I consider the performance often.  I'll have to check and see how they are caching expressions.

  • Anonymous
    July 12, 2010
    Stuart,  This was a great post,  very helpful.  The only change I had to make was to change the value section in the select to "Value = ((int) Enum.Parse(enumType, Enum.GetName(enumType,value))).ToString(),"  as I needed the integer value.  I was wondering if you might have a better way. Thanks again!!!

  • Anonymous
    July 25, 2010
    Hi Matt, I think that you can probably simplify that slightly with something along the lines of Value =  Convert.ToInt32(value).ToString()

  • Stuart
  • Anonymous
    December 06, 2010
    when value is "int", the dropdown doesn't select the SelectListItem with Selected = true.

  • Anonymous
    December 15, 2010
    Hi rajesh, I'm not entirely clear what you mean - can you give a bit more of an example? Thanks, Stuart

  • Anonymous
    February 07, 2011
    I get a compilation error. The error is about htmlHelper.DropDownListFor and htmlHelper.DropDownList. 'System.Web.Mvc.HtmlHelper' does not contain a definition for 'DropDownList' and no extension method 'DropDownList' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?)

  • Anonymous
    February 07, 2011
    The comment has been removed

  • Anonymous
    February 07, 2011
    Done compiling. I'll read the next time better before commenting. Sorry.

  • Anonymous
    February 09, 2011
    In addition to what stuartle said for converting to the enum's actual value, not name, you can use: Type baseEnumType = Enum.GetUnderlyingType(enumType); ... then set the value of the SelectListItem to: Value = Convert.ChangeType(value, baseEnumType).ToString(), ... For those instances where you don't actually know if the underlying type of you Enum is Int :)

  • Anonymous
    June 15, 2011
    Great post Stuart. Thanks very much. How about posting the code for PascalCaseWordSplittingEnumConverter? I've had a look at creating a custom type converter and it seems non-trivial. cheers. DS

  • Anonymous
    June 16, 2011
    @ Damo123 - the code I used for the blog post is below. I've not tested it beyond the needs of this blog post though, so use with extreme caution :-) public class PascalCaseWordSplittingEnumConverter : EnumConverter    {        public PascalCaseWordSplittingEnumConverter(Type type)            : base(type)        {        }        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)        {            if (destinationType == typeof(string))            {                string stringValue = (string) base.ConvertTo(context, culture, value, destinationType);                stringValue = SplitString(stringValue);                return stringValue;            }            return base.ConvertTo(context, culture, value, destinationType);        }        public string SplitString(string stringValue)        {            StringBuilder buf = new StringBuilder(stringValue);            // assume first letter is upper!            bool lastWasUpper = true;            int lastSpaceIndex = -1;            for (int i = 1; i < buf.Length; i++)            {                bool isUpper = char.IsUpper(buf[i]);                if (isUpper & !lastWasUpper)                {                    buf.Insert(i, ' ');                    lastSpaceIndex = i;                }                if (!isUpper && lastWasUpper)                {                    if (lastSpaceIndex != i - 2)                    {                        buf.Insert(i - 1, ' ');                        lastSpaceIndex = i - 1;                    }                }                lastWasUpper = isUpper;            }            return buf.ToString();        }    }

  • Anonymous
    March 16, 2012
    The comment has been removed

  • Anonymous
    April 13, 2012
    I liked this post. Thanks Stuart. I made some changes in order to use DisplayAttribute annotations instead of a TypeConverter. I like to define my enums this way:    public enum TransferFrequency    {        [Display(Name = "One time, immediately")]        OneTime,        [Display(Name = "One time, on...")]        OneTimeOn,        [Display(Name = "Weekly")]        Weekly    } Here's the code in case anyone is interested (semi tested so use at your own risk):        public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)        {            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);            Type enumType = GetNonNullableModelType(metadata);            Type baseEnumType = Enum.GetUnderlyingType(enumType);            List<SelectListItem> items = new List<SelectListItem>();            foreach (FieldInfo field in enumType.GetFields(BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public))            {                string text = field.Name;                string value = Convert.ChangeType(field.GetValue(null), baseEnumType).ToString();                bool selected = field.GetValue(null).Equals(metadata.Model);                foreach (var displayAttribute in field.GetCustomAttributes(true).OfType<DisplayAttribute>())                {                    text = displayAttribute.GetName();                                    }                items.Add(new SelectListItem()                {                    Text = text,                    Value = value,                    Selected = selected                });            }            if (metadata.IsNullableValueType)            {                items.Insert(0, new SelectListItem { Text = "", Value = "" });            }            return SelectExtensions.DropDownListFor(htmlHelper, expression, items, htmlAttributes);        }

  • Anonymous
    June 20, 2012
    Did my own implementation, created a new gist for people to fork: gist.github.com/2965119 Summary: Generic expression support Nullable<Enum> support Optional custom display text Full documentation

  • Anonymous
    February 19, 2013
    Hi Stuart, thank you for the great tutorial! I tried Shea Strickland suggestion but it didn't work for me (I wanted to save the integer in my DB). So I kept the Helper as explained in the tutorial and did as suggested here in my model class: stackoverflow.com/.../best-method-to-store-enum-in-database hope it helps, I lost a few hours to figure this out...

  • Anonymous
    March 19, 2013
    The comment has been removed

  • Anonymous
    April 24, 2013
    What about flags Enum??

  • Anonymous
    October 11, 2013
    when i use it in edit templet the drop down not appear with selected value.

  • Anonymous
    February 11, 2014
    iam having same problem as nimesh it is not getting the selected value in edit template

  • Anonymous
    May 20, 2014
    I'm having the same issue in edit template. Did anyone find a solution?

  • Anonymous
    May 20, 2014
    Not sure why that would fail. Can you paste in your enum definition and model definition? Also, what version of MVC are you using?

  • Anonymous
    July 22, 2014
    Hi, good new. I read this article paulthecyclist.com/2014/04/03/enum-dropdown-mvc5-1. dropdownlist include in asp.net mvc 5.1

  • Anonymous
    December 30, 2014
    I must use mvc 4 with code first approach and I use EnumDropDownListFor as above. The model is built as the following approach. stackoverflow.com/.../how-is-interpreted-an-enum-type-with-ef-code-first. I want to create a EnumDropDownListFor to input FavoriteColor  and stored as FavoriteColorValue in database. I am getting an error of "Unable to cast object of type 'System.Int32' to type 'System.String'." I am trying to solve by changing Value = value.ToString(), into Value = Convert.ToInt32(value).ToString(). However, I still get an error of "Unable to cast object of type 'System.Int32' to type 'System.String'." Can you help me?? Thank you very much.

  • Anonymous
    December 30, 2014
    Sorry. Please delete the above question. The helper extension, EnumDropDownListFor, work well with MVC 4 Code first. It work well with the model, FavoriteColor, inside the following link. stackoverflow.com/.../how-is-interpreted-an-enum-type-with-ef-code-first No any additional modification, say Value = value.ToString(), is needed to implement this solution. Thank you very much. Unable to cast object of type 'System.Int32' to type 'System.String'." is due to error in data annotation in model class.

  • Anonymous
    April 23, 2015
    Nice article with very good examples, for more detail www.advancesharp.com/.../mvc-dropdown-binding-best-ways www.advancesharp.com/.../mvc-dropdownlistfor-fill-on-selection-change-of-another-dropdown

  • Anonymous
    September 27, 2015
    The comment has been removed

  • Anonymous
    October 22, 2015
    Red Green Blue Bright Red Bright Green Bright Blue


i want this list like : Red Bright Red Blue Bright Blue Green Bright Green is there any helper extension?