7
\$\begingroup\$

Is the following helper idiomatic ASP.NET MVC? All the built-in DropDownListFor helpers only accept IEnumerable<SelectListItem>. If the Model doesn't have this list, but an IEnumerable<SomeEntity>, 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<TModel, TElem>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, string>> expression,
        IEnumerable<TElem> elements,
        Func<TElem, string> valueFunc,
        Func<TElem, string> textFunc
    )
    {
        ModelMetadata metadata =
            ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

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

        return htmlHelper.DropDownListFor<TModel, string>(expression, items);
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

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<TModel, TProperty> 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<TModel, TProperty, TElem> and your second parameter to Expression<Func<TModel, TProperty>> 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<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)
    });
}

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" })
\$\endgroup\$
2
  • \$\begingroup\$ I'm not sure I understand why TProperty is not generic. And yes, I did not like losing all the overloads. Thank you for the suggestion on building a SelectList helper instead, and for pointing out that DropDownListFor can infer what item is selected without my aid. This is pinpointing exactly what I want to abstract away from the Razor template. \$\endgroup\$
    – sshine
    Commented Sep 11, 2015 at 8:12
  • 1
    \$\begingroup\$ Regarding the generic property: in your method, you use Expression<Func<TModel, string>>, which forces the model property to be a string. Changing it to Expression<Func<TModel, TProperty>> would make it work like the native MVC methods. \$\endgroup\$
    – Gallant
    Commented Sep 11, 2015 at 15:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.