HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Helper for DropDownLists with extension method

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
methodhelperwithextensiondropdownlistsfor

Problem

Is the following helper idiomatic ASP.NET MVC? All the built-in DropDownListFor helpers only accept IEnumerable. If the Model doesn't have this list, but an IEnumerable, avoiding this conversion in the Razor template seems to require some more of its helpers.

namespace CompanyName.ProjectName.PresentationLayer.Helpers
{
    // ...documentation omitted...
    public static MvcHtmlString DropDownListFor(
        this HtmlHelper htmlHelper,
        Expression> expression,
        IEnumerable elements,
        Func valueFunc,
        Func textFunc
    )
    {
        ModelMetadata metadata =
            ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        IEnumerable items =
            elements.Select(elem => new SelectListItem()
            {
                Value = valueFunc(elem),
                Text = textFunc(elem),
                Selected = elem.Equals(metadata.Model)
            });

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

Solution

The native MVC solution is to pass your collection into the SelectList class.

@Html.DropDownListFor(m => m.Item, new SelectList(Model.MyList, "ValueProperty", "TextProperty"))


This would make your helper method unnecessary.

However, the downside to the native approach is that there is no compile-time safety on the Text/Value properties, so you may still want to have a helper method similar to what you've written for this added benefit.

The DropDownListFor methods handle which item is selected for you, so you don't need to concern yourself with populating the Selected property for your SelectListItems.

Your extension method is not idiomatic to DropDownListFor because you're not using a generic type for the given property. You would at least want to keep TProperty as a generic type, and add a third generic type to your extension method for the collection type. This would change your method signature to DropDownListFor and your second parameter to Expression> expression.

However, your approach has one major design issue: you lose out on all the overloads that exist for DropDownListFor. While you could reimplement all of them, it would be tackling the wrong problem.

You essentially want an elegant way to create a SelectList, so that's what your extension method should do. A Linq extension that works similarly to the ToDictionary extension method would look like the following:

public static IEnumerable ToSelectList(this IEnumerable source, Func valueFunc, Func textFunc)
{
    return source.Select(x => new SelectListItem
    {
        Value = valueFunc(x),
        Text = textFunc(x)
    });
}


Then you can use the native MVC DropDownListFor on any collection easily:

@Html.DropDownListFor(m => m.Item, Model.MyList.ToSelectList(x => x.ValueProperty, x => x.TextProperty))
@Html.DropDownListFor(m => m.Item, Model.MyList.ToSelectList(x => x.ValueProperty, x => x.TextProperty), "Choose...", new { @class = "form-control dropdown" })

Code Snippets

@Html.DropDownListFor(m => m.Item, new SelectList(Model.MyList, "ValueProperty", "TextProperty"))
public static IEnumerable<SelectListItem> ToSelectList<T>(this IEnumerable<T> source, Func<T, string> valueFunc, Func<T, string> textFunc)
{
    return source.Select(x => new SelectListItem
    {
        Value = valueFunc(x),
        Text = textFunc(x)
    });
}
@Html.DropDownListFor(m => m.Item, Model.MyList.ToSelectList(x => x.ValueProperty, x => x.TextProperty))
@Html.DropDownListFor(m => m.Item, Model.MyList.ToSelectList(x => x.ValueProperty, x => x.TextProperty), "Choose...", new { @class = "form-control dropdown" })

Context

StackExchange Code Review Q#101061, answer score: 2

Revisions (0)

No revisions yet.