1

I'm using ASP.NET MVC3 and I'd like to populate a DropDownList with enum values and select one or more option(s).

I customized the DropDownList in order to it looks like to a "CheckedDropDownList", and, in a very basic Edit form, I need to check the actual values.

Here is how my Enum is constructed :

public enum MyEnum
{
    [Display(Name = "Undefined")]
    UNDEFINED = -1, //I really need this "detail"

    [Display(Name = "First Value")]
    VALUE1,

    [Display(Name = "Second Value")]
    VALUE2

    [Display(Name = "Other")]
    OTHER,
}

I'm also using this ToLocalizedSelectList<>() method, which I tried to adapt from this SO question, but I'm unable to modifiy it to accept multiple selected values.

public static SelectList ToLocalizedSelectList<T>(this T enumeration, string selected)
{
    var source = Enum.GetValues(typeof(T)).Cast<T>().OrderBy(x => x); //I always need UNDEFINED as first element
    var items = new Dictionary<object, string>();
    var displayAttributeType = typeof(DisplayAttribute);

    foreach (var value in source)
    {
        FieldInfo field = value.GetType().GetField(value.ToString());
        DisplayAttribute attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).FirstOrDefault();
        items.Add(value, attrs != null ? attrs.GetName() : value.ToString());
    }

    return new SelectList(items, "Key", "Value", selected);
}

Here is how I use it :

@Html.DropDownListFor(m => m.EnumValues, 
                      AttributesHelperExtension.ToLocalizedSelectList<MyEnum>(MyEnum.UNDEFINED, Enum.GetName(typeof(MyEnum), Model.EnumValues.First())
                      Enum.GetName(typeof(MyEnum), Model.EnumValues.First())),
                      new { @id = "ddlEnumValue" }
                     )

With EnumValues is defined as :

public MyEnum[] EnumValues { get; set; }

The problem is, I think, on the third argument, I need to pass Model.EnumValues and not Model.EnumValues.First(), which always preselect only the first item of my MyEnum enumeration.

The second argument could also be a problem, because ToLocalizedSelectList<>() needs a this MyEnum enumeration as a first parameter, so I passed it the first item of my enum, but I'm not sure this is a good solution.

How could I transform this ToLocalizedSelectList<>() method in order to it accept multiple selected values, and how could I use it in an @Html.DropDownListFor (or @Html.ListBoxFor) ?

Thanks a lot


Just FYI, I also tried this code :

@Html.ListBoxFor(m => m.EnumValues, 
                  new SelectList(Enum.GetValues(typeof(MyEnum))
                     .Cast<MyEnum>()
                     .OrderBy(e => (int)e)
                  ), new { @id = "ddlMyEnum" }
                 )

and it pre-select the good valueS, however, it displays enum names, like UNDEFINED, VALUE1 instead of their Display Name attribute (Undefined, First Value) etc...

Community
  • 1
  • 1
AlexB
  • 6,664
  • 12
  • 46
  • 66
  • Was there a reason not to go for something exist already there e.g. http://harvesthq.github.io/chosen/ ??? – SBirthare Aug 07 '14 at 10:55
  • I didn't see in the plugin you linked how to transform a simple DropDownList into a CheckedDropDownList with Checkbox in front of each option as I did, so I think it's a good reason not to use it. To be honnest, I use jQuery multiselect plugin to do this : http://www.erichynds.com/blog/jquery-ui-multiselect-widget. 99% of the work is done, I'd just like an answer to the question below to get the missing 1% and close this problem. – AlexB Aug 07 '14 at 11:04
  • Fair enough, was curious. – SBirthare Aug 07 '14 at 11:06
  • I see many problems here. Firstly you need `@Html.ListBoxFor()`, not `@HtmlDropDownListFor()` if you want to select multiple values. The first parameter of `ToLocalizedSelectList()` is type enum but your passing an enum value. The second parameter seems pointless since the `@Html.ListBoxFor()` method will mark the values as selected based on the values in yot property `EnumValues`. And then there's the logic of using an enum for this. Perhaps you should be using the `[Flags]` attribute so you can assign a collection of enum values (flags) to property rather than an array of enum values –  Aug 08 '14 at 01:50
  • @StephenMuecke, I tried to change the first parameter to `typeof(MyEnum)`, or `MyEnum`, without success. Could you provide a sample of code ? I'm not familiar with the `[Flags]` attribute, I'll take a look to it. – AlexB Aug 08 '14 at 08:12
  • I'm not necessarily recommending using `[Flags]` because it would probably involve some javascript to format the value for postback (i.e as a comma separated list which can be bound), but the answers to [this question](http://stackoverflow.com/questions/8447/what-does-the-flags-enum-attribute-mean-in-c) should be helpful. In the meantime I'll post an answer to how you could create the `SelectList` –  Aug 08 '14 at 08:28

2 Answers2

1

Note: This only addresses part of the problems with your code - displaying a 'friendly' enum description.

Create an extension method for getting a friendly description. Note this uses the [Description] attribute rather than the [Display] attribute

public static string ToDescription(this Enum value)
{
  FieldInfo field = value.GetType().GetField(value.ToString());
  DescriptionAttribute[] attributes = (DescriptionAttribute[])field
    .GetCustomAttributes(typeof(DescriptionAttribute), false);
  if (attributes.Length > 0)
  {
    return attributes[0].Description;
  }
  return value.ToString();
}

which can be used with

public enum ProjectStatus
{
  Draft,
  [Description("In limbo")]
  OnHold,
  Completed
}

then create an extension method to create a select list

public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
  where TEnum : struct, IComparable, IFormattable, IConvertible
{
  var values = from TEnum e in Enum.GetValues(typeof(TEnum))
    select new { ID = e, Name = e.ToDescription() }; // or ID = (int)e, depending on what you want the as the value
  return new SelectList(values, "ID", "Name", enumObj);
}

and use it like this, where you model has property public ProjectStatus status { get; set; }

@Html.DropDownListFor(m => m.Status, Model.Status.ToSelectList())

As for the rest, I don't understand you approach and how creating an array of enum values would be of any benefit or how the results would be stored in a database. My approach would have been to create boolean properties in a view model and then let the user check which options they want.

  • Your code is working so I upvote ; however, as you told it yourself, it doesn't really answers the problem because I already know how to display `[Display]` attribute in a DropDownListFor (see the last part of my first post). What I really need is just how to pre-select multiple elements displayed by their `Display` attribute in a `ListBoxFor` – AlexB Aug 08 '14 at 09:53
  • Thanks (wasn't expecting tick). This might work. If `EnumValues` was `int[]` and its values were set to say `[1, 3]` and the `SelectList` creates option values as `int` (in my extension method it would be `select new { ID = (int)e, Name = e.ToDescription() }` then `@Html.ListBoxFor(m => m.EnumValues, Model.X)` should (maybe) select the 2nd and 4th values. Note `Model.X` is a property in your view model `public MyEnum Something { get; set; }` that defaults to `UNDEFINED` so that you can generate the select list with friendly names and provides a default selection if `EnumValues` is empty –  Aug 08 '14 at 10:09
0

I just corrected this old bug today using :

@Html.ListBoxFor(m => m.EnumValues, 
                 Model.EnumValues.ToLocalizedSelectList<MyEnum>(Model.EnumValues.ToList())
                )

Where ToLocalizedSelectList<>() is the same as posted above, except the signature :

public static SelectList ToLocalizedSelectList<T>(this T[] enumeration, List<T> selected)

Hope it helps someone

AlexB
  • 6,664
  • 12
  • 46
  • 66