Monday, July 27, 2009

ASP.NET MVC DropDownList from Enum

I sometimes have the requirement to create a drop down list on a web page from an Enum. Each time I do this I seem to jump through the same set of hoops and it always seems to take longer than it should to code this up.

To simplify this procedure I need the Enum converted to an IDictionary<int, string> which is easy to drop into an MVC View. To get there I turned to a ubiquitous project that I include in almost all solutions that I work on: The Extensions Project:

Here is the extension method that I wrote to convert an Enum to an IDictionary<int, string>:

/// <summary>
/// Converts an enum type to an IDictionary&lt;int,string&gt;
/// Although this will appear as an extension method on all type objects
/// it will throw an exception on all types except enum
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IDictionary<int, string> EnumToDictionary(this Type type)
{
    if (!type.IsEnum)
    {
        throw new InvalidCastException("The EnumToDictionary() extension method can only be used on types of enum. All other types will throw this exception.");
    }

    // The Enum.GetValues() function returns an array of objects which are actually ints,
    // that's why we cast them to ints before calling the ToDictionary function.
    // The ToDictionary() extension method takes two lambda expressions each of which
    // returns the type that corresponds to the dictionary's types. Because we've cast
    // the array to ints the first param in the ToDictionary is used as is. The second
    // param gets the name of the Enum.
    Dictionary<int, string> dictionary =
        Enum.GetValues(type).Cast<int>().ToDictionary(a => a, a => Enum.GetName(type,a));

    return dictionary;
}

Note that I have to make sure that someone doesn't try and call this on a type that's not an Enum because this will show up as an extension method in intellisense for all types and not just enum types.

Here is the unit test and an example of how to use the extension method:

[TestMethod]
public void test_enum_to_dictionary_of_int_and_string()
{
    IDictionary<int, string> actual = typeof(UnitTestEnum).EnumToDictionary();
    Assert.AreEqual(4, actual.Count);
    Assert.AreEqual("Five", actual[5]);
    Assert.AreEqual("Ten", actual[10]);
    Assert.AreEqual("Fifteen", actual[15]);
    Assert.AreEqual("Twenty", actual[20]);
}

Example enum in unit test:

enum UnitTestEnum { Five = 5, Ten = 10, Fifteen = 15, Twenty = 20 };

In the ASP.NET MVC project, in the Action method of the Controller I create a view model for my view and set the dictionary property on that view model with the newly created extension method:

MyViewModel mvm = new MyViewModel();
mvm.MyTypes = typeof(MyEnum).EnumToDictionary();

This model is then passed into the view by returning it as the second parameter to the View() call...

return View("MyView", mvm);

...and in the view page I have the following:

<% =Html.DropDownList("MyType", new SelectList(Model.MyTypes, "Key", "Value")) %>

In the MyViewModel object I also have an Enum property:

public MyEnum MyType { get; set; }

Note that the enum variable for MyEnum is named MyType. This matches the first paramter to the Html.DropDownList() helper method. The reason that this is important is because when you receive a POST to your controller and you call the UpdateModel() method this will cast the value returned from the drop down to the Enum for you.

This happens because the Html.DropDownList() helper generates a <select> html input field like this:
<select id="MyType" name="MyType">...</select>
so when the form POSTs back the named form value is "MyType" and the MVC UpdateModel() call finds this value when iterating through the public properties of the MyViewModel class and knows that it has to cast it to a MyEnum type because that's the type in the class.

2 comments:

  1. Nice article. This article is also addressing similar problem where the casting of ilist is required to List<SelectListItem>:
    www.altafkhatri.com/.../How_to_bind_ILi

    ReplyDelete
  2. Both winforms and asp.net tutorial:
    www.docstorus.com/.../viewer.aspx www.docstorus.com/.../viewer.aspx

    ReplyDelete