Enum Support in MVC 5.1

MVC 5.1 introduced some really great support for enums as announced in the MVC 5.1 release notes.

Given the following enumeration and model class...

public enum Heroes 
{
     Batman,
     Superman,
     WonderWoman
} 

public class EnumSample 
{
     public Heroes Hero { get; set; }
}

using the following built in HTML helper method @Html.EnumDropDownListFor(m => m.Hero) renders a drop down with the enumeration names:

Cool! Exactly what we expected: a drop down list with the enumeration options. What if we wanted to show the enumeration names in a more friendly manner, like putting a space in WonderWoman?

Turns out, this is no problem for the new helper. The text that is displayed honors any Display Attributes that have been applied to the enum values.

public enum Heroes 
{
     Batman,
     Superman,
     [Display(Name="Wonder Woman")]
     WonderWoman 
} 

public class EnumSample 
{
    public Heroes Hero { get; set; } 
}

Line 5 above is where the magic happens. Notice that I did not use a DisplayNameAttribute as you might have expected. The reason is that the DisplayNameAttribute is not valid for Fields and will throw a compiler error. @Html.EnumDropDownListFor(m => m.Hero) now renders the following:

Sweet! Now we have nice, friendly display values.

Where it breaks down

You would think that the added support for creating drop down lists and honoring display attributes would also apply to simply displaying a enumeration value @Html.DisplayFor(m => m.Hero) and... you would be wrong:

The solution

There are a few solutions available if you google it (with bing, of course!). Unfortunately, most of them are related to creating a drop down list for enum values. There are a couple of answers on stackoverflow that provide simple utility classes/methods to wrap up the reflection required to get the name from the attribute.

I was really looking for something cleaner and easily discoverable. So, I created my own HtmlHelper specifically for displaying the names of enum values. @Html.EnumDisplayFor(m => m.Hero) Let's take a look at the code:

public static MvcHtmlString EnumDisplayFor<TModel, TValue>(this HtmlHelper html, Expression<Func<TModel, TValue>> expression) 
{
    return EnumDisplayNameFor(html, expression); 
} 

public static MvcHtmlString EnumDisplayNameFor<TModel, TValue>(this HtmlHelper html, Expression<Func<TModel, TValue> expression) 
{
    if (html.ViewData == null || html.ViewData.Model == null)
        return MvcHtmlString.Empty;

    var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);

    if (EnumHelper.IsValidForEnumHelper(metaData))
    {
        var enumType = metaData.ModelType;
        var enumName = Enum.GetName(enumType, metaData.Model);
        var enumField = enumType.GetField(enumName, BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public);

        if (enumField != null)
        {
            var attr = enumField.GetCustomAttribute(inherit: false);
            if (attr != null)
            {
                string name = attr.GetName();
                if (!string.IsNullOrWhiteSpace(name))
                   return MvcHtmlString.Create(name);
            }
        }
    }
    return DisplayExtensions.DisplayFor(html, expression);
}

Basically, all we're doing is checking to see if the enum value in the model has a DisplayAttribute, and if so, try to grab the Name from it.

The EnumHelper.IsValidForEnumHelper() method is part of the framework, and I used it here to make sure that the model value is an enum and that it does not contain flags. I'm not sure why having flags would cause an issue, but I included the check to maintain consistency with the EnumDropDownListFor method.

The DisplayExtensions.DisplayFor<T>() method was used here for consistency with the framework as well.

What about discoverability?

Adding the namespace to the web.config file did not make these extension methods available in my views. I'm not sure why. The only way I could get them to be available was to add the explicit using at the top of each view where I needed to use them. Needless to say, not very good for discoverability.

After fighting with this for some time, I ultimately decided to put these in the System.Web.Mvc.Html namespace. As dirty as it feels to do that, the nice side effect is that by simply referencing the DLL, these methods instantly become available to all of your views without having to muck with web.config or add additional namespaces to your views. The ultimate in discoverability!

In conclusion

I'm pretty satisfied with the solution that I've come up with. These extension methods are available on NuGet:

Install-Package RedWall.MVCSugar

The source, including a sample project, is available on GitHub.