27

I've been finding all over the place that the common way to bind Enums to DropDowns is through helper methods, which seems a bit overbearing for such a seemingly simple task.

What is the best way to bind Enums to DropDownLists in ASP.Net MVC 4?

keeehlan
  • 7,874
  • 16
  • 56
  • 104
  • @Sheridan A little late on that, wouldn't you agree? – keeehlan Nov 04 '14 at 19:04
  • 3
    No, I wouldn't agree at all. I couldn't have marked your question as a duplicate before I'd seen it, could I? If you have some issue with that, perhaps you should read the [Why are some questions marked as duplicate?](http://stackoverflow.com/help/duplicates) page of the Stack Overflow Help Center instead of leaving pointless comments? – Sheridan Nov 04 '14 at 20:44

7 Answers7

29

You can to this:

@Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
Mr. Pumpkin
  • 6,212
  • 6
  • 44
  • 60
20

I think it is about the only (clean) way, which is a pity, but at least there are a few options out there. I'd recommend having a look at this blog: http://paulthecyclist.com/2013/05/24/enum-dropdown/

Sorry, it's too long to copy here, but the gist is that he created a new HTML helper method for this.

All the source code is available on GitHub.

Bernhard Hofmann
  • 10,321
  • 12
  • 59
  • 78
  • 2
    The gist of this answer is answered in this SO question: http://stackoverflow.com/questions/388483/how-do-you-create-a-dropdownlist-from-an-enum-in-asp-net-mvc along with many other solutions to this problem. – PaulTheCyclist Jun 25 '13 at 00:48
  • 1
    In case anyone's interested (I know the OP specifies MVC 4) MVC 5.1 has enum support: http://www.asp.net/mvc/overview/releases/mvc51-release-notes#Enum – Bernhard Hofmann Jan 22 '14 at 13:53
  • much better solution here. http://stackoverflow.com/questions/388483/how-do-you-create-a-dropdownlist-from-an-enum-in-asp-net-mvc/5255108#5255108 – RasikaSam Feb 25 '14 at 06:11
  • As Bernard says MVC5.1 now covers this, if you are able to use that version. I have made a new blog entry http://paulthecyclist.com/2014/04/03/enum-dropdown-mvc5-1/ based on my previous one showing one way you can utilise this and still get the dropdown values from a resx file. – PaulTheCyclist Apr 03 '14 at 09:57
  • paulthecyclist post is super clean and easy to follow. check the github repo also for sure. Thanks for resources. – Aryan Firouzian Jan 10 '19 at 08:55
15

Enums are supported by the framework since MVC 5.1:

@Html.EnumDropDownListFor(m => m.Palette)

Displayed text can be customized:

public enum Palette
{
    [Display(Name = "Black & White")]
    BlackAndWhite,

    Colour
}

MSDN link: http://www.asp.net/mvc/overview/releases/mvc51-release-notes#Enum

Mihkel Müür
  • 2,162
  • 1
  • 20
  • 15
10

In my Controller:

var feedTypeList = new Dictionary<short, string>();
foreach (var item in Enum.GetValues(typeof(FeedType)))
{
    feedTypeList.Add((short)item, Enum.GetName(typeof(FeedType), item));
}
ViewBag.FeedTypeList = new SelectList(feedTypeList, "Key", "Value", feed.FeedType);

In my View:

@Html.DropDownList("FeedType", (SelectList)ViewBag.FeedTypeList)
keeehlan
  • 7,874
  • 16
  • 56
  • 104
5

The solution from PaulTheCyclist is spot on. But I wouldn't use RESX (I'd have to add a new .resx file for each new enum??)

Here is my HtmlHelper Expression:

public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TEnum>> expression, object attributes = null)
{
    //Get metadata from enum
    var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    var enumType = GetNonNullableModelType(metadata);
    var values = Enum.GetValues(enumType).Cast<TEnum>();

    //Convert enumeration items into SelectListItems
    var items =
        from value in values
        select new SelectListItem
        {
            Text = value.ToDescription(),
            Value = value.ToString(),
            Selected = value.Equals(metadata.Model)
        };

    //Check for nullable value types
    if (metadata.IsNullableValueType)
    {
        var emptyItem = new List<SelectListItem>
        {
            new SelectListItem {Text = string.Empty, Value = string.Empty}
        };
        items = emptyItem.Concat(items);
    }

    //Return the regular DropDownlist helper
    return htmlHelper.DropDownListFor(expression, items, attributes);
}

Here is how I declare my enums:

[Flags]
public enum LoanApplicationType
{
    [Description("Undefined")]
    Undefined = 0,

    [Description("Personal Loan")]
    PersonalLoan = 1,

    [Description("Mortgage Loan")]
    MortgageLoan = 2,

    [Description("Vehicle Loan")]
    VehicleLoan = 4,

    [Description("Small Business")]
    SmallBusiness = 8,
}

And here is the call from a Razor View:

<div class="control-group span2">
    <div class="controls">
        @Html.EnumDropDownListFor(m => m.LoanType, new { @class = "span2" })
    </div>
</div>

Where @Model.LoanType is an model property of the LoanApplicationType type

UPDATE: Sorry, forgot to include code for the helper function ToDescription()

/// <summary>
/// Returns Description Attribute information for an Enum value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string ToDescription(this Enum value)
{
    if (value == null)
    {
        return string.Empty;
    }
    var attributes = (DescriptionAttribute[]) value.GetType().GetField(
        Convert.ToString(value)).GetCustomAttributes(typeof (DescriptionAttribute), false);
    return attributes.Length > 0 ? attributes[0].Description : Convert.ToString(value);
}
amhed
  • 3,649
  • 2
  • 31
  • 56
  • 3
    Hi, if you were to use a solution based on my blog you'd only need one resx file for all your Enums and just use a convention to distinguish between enums. e.g If you have two enums, Colours and Numbers say. some resx entries might have names like: Colour_Blue, Colour_Red, Colour_White, LoanApplicationType_Undefined, LoanApplicationType_Personal. Description attribute works fine though if your web application doesn't need to consider localisation – PaulTheCyclist Jun 25 '13 at 00:55
  • 1
    I guess it makes sense if you need to localize for many languages. The times I've had to use a RESX file I usually decor the enumeration with a custom DataAnnotationAttribute and set the resource file entry directly on the enum. But I guess your approach would be more comfortable. I'm gonna try it out in the near future (thanks) – amhed Jun 25 '13 at 11:55
4

Technically, you don't need a helper method, since Html.DropdownListFor only requires a SelectList or Ienumerable<SelectListItem>. You can just turn your enums into such an output and feed it in that way.

I use a static library method to convert enums into List<SelectListItem> with a few params/options:

public static List<SelectListItem> GetEnumsByType<T>(bool useFriendlyName = false, List<T> exclude = null,
    List<T> eachSelected = null, bool useIntValue = true) where T : struct, IConvertible
{
    var enumList = from enumItem in EnumUtil.GetEnumValuesFor<T>()
                    where (exclude == null || !exclude.Contains(enumItem))
                    select enumItem;

    var list = new List<SelectListItem>();

    foreach (var item in enumList)
    {
        var selItem = new SelectListItem();

        selItem.Text = (useFriendlyName) ? item.ToFriendlyString() : item.ToString();
        selItem.Value = (useIntValue) ? item.To<int>().ToString() : item.ToString();

        if (eachSelected != null && eachSelected.Contains(item))
            selItem.Selected = true;

        list.Add(selItem);
    }

    return list;
}

public static class EnumUtil
{
    public static IEnumerable<T> GetEnumValuesFor<T>()
    {
        return Enum.GetValues(typeof(T)).Cast<T>();
    }
    // other stuff in here too...
}


/// <summary>
/// Turns Camelcase or underscore separated phrases into properly spaces phrases
/// "DogWithMustard".ToFriendlyString() == "Dog With Mustard"
/// </summary>
public static string ToFriendlyString(this object o)
{
    var s = o.ToString();
    s = s.Replace("__", " / ").Replace("_", " ");

    char[] origArray = s.ToCharArray();
    List<char> newCharList = new List<char>();

    for (int i = 0; i < origArray.Count(); i++)
    {
        if (origArray[i].ToString() == origArray[i].ToString().ToUpper())
        {
            newCharList.Add(' ');
        }
        newCharList.Add(origArray[i]);
    }

    s = new string(newCharList.ToArray()).TrimStart();
    return s;
}

Your ViewModel can pass in the options you want. Here's a fairly complex one:

public IEnumerable<SelectListItem> PaymentMethodChoices 
{ 
    get 
    { 
        var exclusions = new List<Membership.Payment.PaymentMethod> { Membership.Payment.PaymentMethod.Unknown, Membership.Payment.PaymentMethod.Reversal };
        var selected = new List<Membership.Payment.PaymentMethod> { this.SelectedPaymentMethod };
        return GetEnumsByType<Membership.Payment.PaymentMethod>(useFriendlyName: true, exclude: exclusions, eachSelected: selected); 
    }
}

So you wire your View's DropDownList against that IEnumerable<SelectListItem> property.

Graham
  • 3,217
  • 1
  • 27
  • 29
0

Extending the html helper to do it works well, but if you'd like to be able to change the text of the drop down values based on DisplayAttribute mappings, then you would need to modify it similar to this,

(Do this pre MVC 5.1, it's included in 5.1+)

public static IHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> html, Expression<Func<TModel, TEnum>> expression)
{
    var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
    var enumType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;
    var enumValues = Enum.GetValues(enumType).Cast<object>();
    var items = enumValues.Select(item =>
    {
        var type = item.GetType();
        var member = type.GetMember(item.ToString());
        var attribute = member[0].GetCustomAttribute<DisplayAttribute>();
        string text = attribute != null ? ((DisplayAttribute)attribute).Name : item.ToString();
        string value = ((int)item).ToString();
        bool selected = item.Equals(metadata.Model);
        return new SelectListItem
        {
            Text = text,
            Value = value,
            Selected = selected
        };
    });
    return html.DropDownListFor(expression, items, string.Empty, null);
}
Ryan Mann
  • 5,178
  • 32
  • 42