ASP.NET Html Helper for Enumeration Based Drop Downs

Not a particularly mind-blowing post today, but I found this little bit of code helpful and it might help someone else. So here you go. A simple html helper for ASP.NET MVC for creating drop downs based on Enumeration values. The nice bit is that it will respect Data Annotations Describe attributes.

The helper:

public static class HtmlEnumHelper
{
    private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
    {
        var realModelType = modelMetadata.ModelType;

        var underlyingType = Nullable.GetUnderlyingType(realModelType);
        if (underlyingType != null)
        {
            realModelType = underlyingType;
        }
        return realModelType;
    }

    private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };

    public static string GetEnumDescription<TEnum>(TEnum value)
    {
        var fi = value.GetType().GetField(value.ToString());

        var attributes =
            (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

        return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
    }

    public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper,
                                                                   Expression<Func<TModel, TEnum>> expression,
                                                                   object htmlAttributes = null)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        var enumType = GetNonNullableModelType(metadata);
        var values = Enum.GetValues(enumType).Cast<TEnum>();

        var items = from value in values
                    select new SelectListItem
                        {
                            Text = GetEnumDescription(value),
                            Value = value.ToString(),
                            Selected = value.Equals(metadata.Model)
                        };

        if (metadata.IsNullableValueType)
            items = SingleEmptyItem.Concat(items);

        return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
    }
}

And some tests that demonstrate it's usage:

[TestFixture]
public class HtmlEnumHelperTests
{
    [Test]
    public void CanGetDropDownForDecoratedEnum()
    {
       var result = CreateHtmlHelper<SimpleModel>()
                       .EnumDropDownListFor(x => x.DecoratedType)
                       .ToHtmlString();

        Assert.That(result, Contains.Substring("foo"));
        Assert.That(result, Contains.Substring("bar"));
        Assert.That(result, Contains.Substring("baz"));

    }

    [Test]
    public void CanGetDropDownForUndecoratedEnum()
    {
        var result = CreateHtmlHelper<SimpleModel>()
                        .EnumDropDownListFor(x => x.UndecoratedType)
                        .ToHtmlString();

        Assert.That(result, Contains.Substring("Fiz"));
        Assert.That(result, Contains.Substring("Buz"));
        Assert.That(result, Contains.Substring("FizBuz"));

    }

    [Test]
    public void CanGetDropDownForMixedEnum()
    {
        var result = CreateHtmlHelper<SimpleModel>()
                        .EnumDropDownListFor(x => x.MixedType)
                        .ToHtmlString();

        Assert.That(result, Contains.Substring("foo"));
        Assert.That(result, Contains.Substring("Buz"));
        Assert.That(result, Contains.Substring("FizBuz"));

    }

    [Test]
    public void CanGetDropDownForNullableEnum()
    {
        var result = CreateHtmlHelper<SimpleModel>()
                        .EnumDropDownListFor(x => x.NullableType)
                        .ToHtmlString();

        Assert.That(result, Contains.Substring("<option value=\"\">"));

    }

    public static HtmlHelper<TModel> CreateHtmlHelper<TModel>()
    {
      var viewContext = new ViewContext
           {
               HttpContext = new FakeHttpContext(),
               ViewData = new ViewDataDictionary()
           };

        return new HtmlHelper<TModel>(viewContext, new FakeViewDataContainer());
    }
}

public class FakeViewDataContainer : IViewDataContainer
{
    public FakeViewDataContainer()
    {
        ViewData = new ViewDataDictionary();
    }

    public ViewDataDictionary ViewData { get; set; }
}

public class FakeHttpContext : HttpContextBase
{
    private readonly Dictionary<object, object> items = new Dictionary<object, object>();

    public override IDictionary Items
    {
        get
        {
            return items;
        }
    }
}

public class SimpleModel
{
    public DecoratedType DecoratedType { get; set; }
    public UndecoratedType UndecoratedType { get; set; }
    public MixedType MixedType { get; set; }
    public UndecoratedType? NullableType { get; set; }
}

public enum DecoratedType
{
    [Description("foo")]
    Fiz,
    [Description("bar")]
    Buz,
    [Description("baz")]
    FizBuz
}

public enum UndecoratedType
{
    Fiz,
    Buz,
    FizBuz
}

public enum MixedType
{
    [Description("foo")]
    Fiz,
    Buz,
    FizBuz
}

See, I can still write C#. 8)

Follow me on Mastodon!