Using Html Helper and Lambda expression for creating dropdown list and applying attributes on options tag of dropdown list
Traditional way of doing it.
<select name="listbox" id="listbox">
@foreach (var item in Model)
{
<option value="@item.UserRoleId" data-name="@item.Name" data-class="@item.ClassName">
@item.UserRole
</option>
}
</select>
Instead of iterating through and attaching attributes inside foreach look
We can do like below
@Html.DropDownList(x => x.ReportName, Model.AvailableReports, x => x.TextField, x => x.NameField, "-- Select --",
new Dictionary<string, Expression<Func<ReportSummaryModel, object>>>{ {"data-name", x => x.ReportActualName}, {"data-class", x => x.ReportClassName}})
It will output the select list with option tag and attribute on each option tag
Output
var s = new string();
<select>
<option>--Select--</option>
<option value="1" data-name="NameA" data-class="classA">A</option>
<option value="2" data-name="NameB" data-class="classB">B</option>
<option value="3" data-name="NameC" data-class="classC">C</option>
<option value="4" data-name="NameD" data-class="classD">D</option>
</select>
Generic Tempelate
DropDownList<TPageModel, TSelectListModel, TProperty>
- TPageModel : Model of Page
- TSelectListModel : Model of Select List Item
- TProperty: Will be object so can use any type
The parameters of custom dropdown list
public static MvcHtmlString DropDownList<TPageModel, TSelectListModel, TProperty>
(this HtmlHelper<TPageModel> htmlHelper,
Expression<Func<TSelectListModel, TProperty>> expression, IEnumerable<TSelectListModel> selectList,
Expression<Func<TSelectListModel, TProperty>> textField,
Expression<Func<TSelectListModel, TProperty>> valueField, string optionLabel, IDictionary<string,
Expression<Func<TSelectListModel, TProperty>>> optionsAttributes)
Parameters Descriptions
Expression<Func<TSelectListModel, TProperty>> expression
This is to provide the input property for which we are showing dropdown (Rename if want)
IEnumerable<TSelectListModel> selectList
List of class of which text and value field you will be using
Expression<Func<TSelectListModel, TProperty>> textField
Provide the text field property from the TSelectListModel
Expression<Func<TSelectListModel, TProperty>> valueField
Provide the value field property from the TSelectListModel
string optionLabel
Default Option Text for dropdown list box
IDictionary<string, Expression<Func<TSelectListModel, TProperty>>> optionsAttributes
This is to provide the attributes to be appear on option tag from the SelectListModel
public static MvcHtmlString DropDownList<TPageModel, TSelectListModel, TProperty>(this HtmlHelper<TPageModel> htmlHelper, Expression<Func<TSelectListModel, TProperty>> expression, IEnumerable<TSelectListModel> selectList, Expression<Func<TSelectListModel, TProperty>> textField, Expression<Func<TSelectListModel, TProperty>> valueField, string optionLabel, IDictionary<string, Expression<Func<TSelectListModel, TProperty>>> optionsAttributes)
{
string name = ExpressionHelper.GetExpressionText(expression);
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
string defaultValue = string.Empty;
ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
{
if (modelState.Value != null)
{
defaultValue = (string)modelState.Value.ConvertTo(typeof(string), null /* culture */);
}
}
var listItemBuilder = new StringBuilder();
// Make optionLabel the first item that gets rendered.
if (optionLabel != null)
{
listItemBuilder.AppendLine(ListItemToOption(optionLabel, string.Empty, false, null));
}
/* Loop through each options
* Convert each ListItem to an <option> tag */
foreach (var listItem in selectList)
{
string text = listItem.GetType()
.GetProperties()
.Single(p => p.Name.Equals(ClassHelper.PropertyName(textField)))
.GetValue(listItem, null)
.ToString();
string value = listItem.GetType()
.GetProperties()
.Single(p => p.Name.Equals(ClassHelper.PropertyName(valueField)))
.GetValue(listItem, null)
.ToString();
bool isSelected = value.Equals(defaultValue);
var htmlAttributes = new Dictionary<string, string>();
foreach (var option in optionsAttributes)
{
string propertyName = ClassHelper.PropertyName(option.Value);
htmlAttributes.Add(option.Key, listItem.GetType()
.GetProperties()
.Single(p => p.Name.Equals(propertyName))
.GetValue(listItem, null)
.ToString());
}
listItemBuilder.AppendLine(ListItemToOption(text, value, isSelected, htmlAttributes));
}
var tagBuilder = new TagBuilder("select")
{
InnerHtml = listItemBuilder.ToString()
};
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
Private method copied from mvc built in helper to generate option attribute
internal static string ListItemToOption(string text, string value, bool isSelected, IDictionary<string, string> htmlAttributes)
{
var builder = new TagBuilder("option")
{
InnerHtml = HttpUtility.HtmlEncode(text)
};
if (value != null)
{
builder.Attributes["value"] = value;
}
if (isSelected)
{
builder.Attributes["selected"] = "selected";
}
if (htmlAttributes != null)
{
builder.MergeAttributes(htmlAttributes);
}
return builder.ToString(TagRenderMode.Normal);
}
Utility function to get property name
public class ClassHelper
{
public static string PropertyName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
{
var body = expression.Body as MemberExpression;
if (body == null)
{
body = ((UnaryExpression)expression.Body).Operand as MemberExpression;
}
return body.Member.Name;
}
}