A common situation you can find in MVC, is to have an Enum in your model and want that rendered as a dropdown list in the browser.
If you are lucky enough to be using MVC5.1 (or higher), I’d recommend you look at this post that shows how to do this using the new Enum Support that’s part of MVC5.1. If not, then read on.
There are a number of ways of solving this, Currently on StackoverFlow there are 20 answers to this question, but none of them consider what I consider the more elegant solution of using templating.
For anyone not familiar with templating, I’d recommend reading Brad Wilson’s excellent blog on it. The following post is how I like to do it, using Asp.Net MVC with C# and the Razor view engine.
For a model that looks like this
public class PersonViewModel
{
[Display(Name="First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Airline Preference")]
public AirlinePreference? AirlinePreference { get; set; }
}
With our Enum defined as:
public enum AirlinePreference
{
BritishAirways,
EasyJet,
RyanAir,
Virgin
}
Wouldn’t it be cool to have view that looks like this
And for the view to have just one line to render the form content:
@Html.EditorForModel()
This post explains one way of achieving this.
The model should be easy enough to read. The controller action is also, so boring, it’s barely worth mentioning, but here it is:
[HttpGet]
public ActionResult Add()
{
var viewModel = new PersonViewModel();
return View(viewModel);
}
Enum Template
Out of the box, MVC will render a textbox if the model type is type string and your View for that element is @Html.EditorFor(x => x.FirstName) (for our model). However, it doesn’t know how to render an Enum, so defaults it to being a textbox. For our model, this would result in a view like this
We need to provide a template for MVC to handle Enums in order for it to render them as a dropdown list. We could provide a template whose name matches our Enum (AirlinePreference) and in that view render our dropdown. We could also supply a UIHint attribute on our model property, to help MVC find our template. I prefer to alter the string template, to look for a property type of Enum and render all Enums in my models by the same piece of code without having to be explicit about it with each Enum in each model. This helps have a consistent way of rendering an Enum the same way on all pages with the least amount of code.
We need to provide the string template, and its important we put it in the right folder. As I want all Enums to use this by default, I’m putting mine here:
/Views/Shared/EditorTemplates/
String.cshtml template
@using MVCProject.Extensions
@{
var type = Nullable.GetUnderlyingType(ViewData.ModelMetadata.ModelType) ?? ViewData.ModelMetadata.ModelType;
@(typeof (Enum).IsAssignableFrom(type) ? Html.EnumDropDownListFor(x => x) : Html.TextBoxFor(x => x))
}
The first line gets the type of our property, taking care to cater for a value that may be null. The second line will render a EnumDropDownListFor for an Enum or a TextBoxFor for a (non) Enum, e.g. a string.
EnumDropDownListFor
EnumDropDownListFor is an HtmlHelper extension. Here it is
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 = from enumValue in enumValues
select new SelectListItem
{
Text = enumValue.ToString(),
Value = ((int)enumValue).ToString(),
Selected = enumValue.Equals(metadata.Model)
};
return html.DropDownListFor(expression, items, string.Empty, null);
}
First couple of lines determine the enum type (taking care of the null case too). Then we get an array of Enum values and cast it into a enumerable collection of objects. From here we can easily convert that to an enumerable collection of SelectListItems with a linq query. Once we have the collection of SelectListItems, it’s passed into the DropDownListFor helper to render it as a select list for the client.
In the browser our page now looks like
Much improved, but not very user friendly to see BrittishAirways, displayed with no space in between.
Format DropDownList Values
We could add a Description Attribute or a Display Attribute to the enum values and update the linq query to display that instead of enumValue.ToString().
However, I’m going to go with a convention over code style approach and use a resx file to store the value of what we want to render to the screen. Also, as we are using a resx file to store the value that get’s displayed to the user we can easily make this support more than one language.
Let’s create a resx file and call it Enums.resx. And add some values in that look like:
So, here our convention is the names of the resources follow the pattern
[Enum Name]_[Enum Value]
The final thing to do is update the EnumDropDownFor extension to use these resource values, instead of the enum value.
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 = from enumValue in enumValues
select new SelectListItem
{
Text = GetResourceValueForEnumValue(enumValue),
Value = ((int)enumValue).ToString(),
Selected = enumValue.Equals(metadata.Model)
};
return html.DropDownListFor(expression, items, string.Empty, null);
}
private static string GetResourceValueForEnumValue<TEnum>(TEnum enumValue)
{
var key = string.Format("{0}_{1}", enumValue.GetType().Name, enumValue);
return Enums.ResourceManager.GetString(key) ?? enumValue.ToString();
}
We’ve added the static method GetResourceValueForEnumValue, which simply creates a key, based on our convention of [Enum Name]_[Enum Value] and then uses the ResourceManager to lookup the corresponding value from the Enums resource.
Finally, earlier I mentioned that I was using @Html.EditorForModel to create the form in the view. You could also call @Html.EditorFor(x => x.AirlinePreference) if you need to have more control over where to place the dropdown on your form. You can also explicitly call the EnumDropDownListFor of course.
All source code available to download here:
https://github.com/paulthecyclist/EnumBlog