Resx

Dropdown list with MVC5.1 – now out of the box!

Last year I made this post showing a clean way to render a dropdown list in MVC.

Recently MS released MVC5.1 (prerelase) and excitingly it now has Enum support.

Jon Galloway has written an excellent post outlining this change. The .Net team have added a new HTMLHelper extension called EnumDropDownListFor that will render an Enum as a dropdown box (or radio button list).

How does this compare to the long winded solution I outlined last year, I hear no one asking?

To recap, we want to render a dropdown list that looks like:

EditPageFinal

with our view rendering our model with one line.

@Html.EditorForModel()      

We no longer need the custom Htmlhelper Extension EnumDropDownListFor used in the previous solution, as that now ships with MVC5.1 (well, not the same implementation!). We do, however, still want to be culture aware and use a Resx file to contain the resources of the actual names to display on screen. I’d now recommend using display attributes in the Enum to identify the resource. This will leave our Enum and model looking like:

public enum AirlinePreference
{
    [Display(Name = "AirlinePreference_BritishAirways",
                ResourceType = typeof(Resources.Enums))]
    BritishAirways,
    [Display(Name = "AirlinePreference_EasyJet", 
                ResourceType = typeof(Resources.Enums))]
    EasyJet,
    [Display(Name = "AirlinePreference_RyanAir",
                ResourceType = typeof(Resources.Enums))]
    RyanAir,
    [Display(Name = "AirlinePreference_Virgin",
                ResourceType = typeof(Resources.Enums))]
    Virgin
}

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; }
}    

For reference our resx file looks like this

Enums

Compile it, run it, go to the Add Person page and you’ll see the dropdown list as before…

5.1 is still prerelease and at the time of writing @Html.EditorForModel will not render an Enum dropdown list. It currently needs a push in the form of an Enum template (as described in Jon’s blog) to know how to display an Enum.
With my lean hat on, for the purpose of this blog the following will suffice.

Create a view called Enum.cshtml and put it in the EditorTemplate folder

enumtemplate

Add this one line

@Html.EnumDropDownListFor(x => x)

Now go the Add Person page and voila, a DropDownList is present.

Create a dropdown list from an Enum in Asp.Net MVC

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

Person Final Page

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

DefaultEditPage

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

Person Edit Page, non formattedMuch 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:

EnumsSo, 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