Enum to Drop Down List Html Helper Extension for ASP MVC 3

Monday, December 24, 2012

0

In this post I'll go over how to build a MVC3 HtmlHelper extension to map Enum model properties to a dropdown listbox.

The extension will follow the same pattern as out-of-box generic helper functions like @Html.LabelFor<> and @Html.TextBoxFor<>.

Say we have a simple Employee model with Name (string) and Department (nullable enum) fields, and need to make a form for creating new employees:

public enum Department
{
Development,
Sales,
HR
}

public class Employee
{
public string Name { get; set; }
public Department? Dept { get; set; }
}

We can create a label and textbox for the Name with this View cshtml:

@model Employee

<h2>New Employee</h2>

@Html.LabelFor(model => model.Name)
<br />
@Html.TextBoxFor(model => model.Name)

Which renders into:


There is, however, no way to handle the Department field.  Our goal is to build a custom HtmlHelper to map enum fields like Department to a drop down list.  If the field is Nullable, the dropdown needs to include an empty option.  The end product will work like this:

@Html.LabelFor(model => model.Dept)
<br />
@Html.DropDownEnumListFor(model => model.Dept, Department.Sales)

and render into:

Create Extension Method

Luckily, we don't have to start from scratch.  MVC3 already has a DropDownListFor<> helper, which takes as input a SelectList object containing objects to populate the dropdown.

The bulk of our work will be in building an appropriate SelectList from a given enum.

First, let's create the DropDownEnumListFor<> extension method.  Add a static class to your MVC project.  It doesn't matter where you put it or what it's called.  Remember to import System.Web.Mvc, System.Web.Mvc.Html, and System.Linq.Expressions namespaces.

public static class Extensions
{
public static MvcHtmlString DropDownEnumListFor<TModel, TValue>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TValue>> expr,
TValue selectedItem)
{
return helper.DropDownListFor(expr, ToSelectList<TValue>(selectedItem));
}

public static SelectList ToSelectList<T>(T selectedValue)
{
throw new NotImplementedException();
}
}

The expr parameter is a lamba function that takes as input the model and returns your property value.  On the View page, this is the model => model.Dept part.  selectedItem is the enum value that should be selected initially.

At this point, the new method should be available in Intellisense.  Remember to add a using statement in the View cshtml for the namespace your extension lives under.


Now we have to implement ToSelectList<T>, which converts any given Enum type T to a SelectList.

Handle Nullable Enums

Let's start by creating two helper methods to deal with Nullable enum types.

public static bool IsTypeNullable(Type type)
{
return (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(Nullable<>));
}

public static Type GetBaseType(Type type)
{
return IsTypeNullable(type) ? type.GetGenericArguments()[0] : type;
}

IsTypeNullable simply checks if the specified type is Nullable.  GetBaseType converts a Nullable type to it's non-nullable type (ie decimal? to decimal).

Implement ToSelectList

Finally, implement ToSelectList<T>:

  1. If T is Nullable, retrieve the Enum type with GetBaseType
  2. If T is Nullable, add an empty option
  3. Get list of enum values using Enum.GetValues
  4. Build an ArrayList pairing each enum value with its display name
  5. Create a SelectList from the ArrayList

Code:

public static SelectList ToSelectList<T>(T selectedValue)
{
bool isNullable = IsTypeNullable( typeof(T) );
Type enumType = GetBaseType( typeof(T) );

if (enumType.IsEnum)
{
ArrayList items = new ArrayList();

// Add empty option
if (isNullable)
{
items.Add( new { Name = "", Value = default(T) } );
}

// Add enum values
var enumValues= Enum.GetValues(enumType);

foreach (T value in enumValues)
{
string displayName = Enum.GetName(enumType, value);
items.Add( new { Name = displayName, Value = value } );
}

return new SelectList(items, "Value", "Name", selectedValue);
}

return null;
}

Done!


0 comments:

Post a Comment