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);
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Aug 15, 2015 at 20:34
\$\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" })
answered Sep 9, 2015 at 20:08
\$\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\$ 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\$ Commented Sep 11, 2015 at 15:09

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.