I modified @Alexander Puchkov answer in order to automatically create an ExtendedSelectList that contains a collection of ExtendedSelectListItems that is automatically generated from the properties of the Items being passed into it without having to specify the attributes for each SelectListItem manually. It creates the attributes as "data-*". You can exclude some properties using the function parms or by having them listed in excludedProperties.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
namespace App.Infrastructure.Helpers.Extensions
{
public class ExtendedSelectList : SelectList
{
static readonly string[] excludedProperties = new string[] { "DateInsert", "DateUpdate" }; // Write here properties you want to exclude for every ExtendedSelectList
public ICollection<ExtendedSelectListItem> ExtendedSelectListItems { get; set; }
public ExtendedSelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue, params string[] exclude) : base(items, dataValueField, dataTextField, selectedValue)
{
ExtendedSelectListItems = new List<ExtendedSelectListItem>();
exclude = exclude.Concat(new string[] { dataValueField, dataTextField }).ToArray();
exclude = exclude.Concat(excludedProperties).ToArray();
foreach (var selectListItem in this.AsEnumerable())
{
var extendedItem = new ExtendedSelectListItem() { Value = selectListItem.Value, Text = selectListItem.Text, Selected = selectListItem.Selected, Disabled = selectListItem.Disabled, Group = selectListItem.Group };
var htmlAttributes = new Dictionary<string, object>();
var item = items.Cast<object>().FirstOrDefault(x =>
{
string valueItem = DataBinder.Eval(x, DataValueField).ToString();
string valueSelectListItem = DataBinder.Eval(selectListItem, "Value").ToString();
return valueItem == valueSelectListItem;
});
foreach (PropertyInfo property in item.GetType().GetProperties())
{
if (!property.CanRead || (property.GetIndexParameters().Length > 0) || (exclude != null && exclude.Contains(property.Name)))
continue;
htmlAttributes.Add("data-" + property.Name.ToLower(), property.GetValue(item));
}
extendedItem.HtmlAttributesDict = htmlAttributes;
ExtendedSelectListItems.Add(extendedItem);
}
}
}
public class ExtendedSelectListItem : SelectListItem
{
public object HtmlAttributes { get; set; }
public Dictionary<string, object> HtmlAttributesDict { get; set; }
}
public static class ExtendedSelectExtensions
{
internal static object GetModelStateValue(this HtmlHelper htmlHelper, string key, Type destinationType)
{
System.Web.Mvc.ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(key, out modelState))
{
if (modelState.Value != null)
{
return modelState.Value.ConvertTo(destinationType, null /* culture */);
}
}
return null;
}
public static MvcHtmlString ExtendedDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList)
{
return ExtendedDropDownList(htmlHelper, name, selectList, (string)null, (IDictionary<string, object>)null);
}
public static MvcHtmlString ExtendedDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
return ExtendedDropDownListHelper(htmlHelper, null, name, selectList, optionLabel, htmlAttributes);
}
public static MvcHtmlString ExtendedDropDownListHelper(this HtmlHelper htmlHelper, ModelMetadata metadata, string expression, IEnumerable<ExtendedSelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
return SelectInternal(htmlHelper, metadata, optionLabel, expression, selectList, false, htmlAttributes);
}
public static MvcHtmlString ExtendedDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression, IEnumerable<ExtendedSelectListItem> selectList,
string optionLabel, object htmlAttributes)
{
if (expression == null)
throw new ArgumentNullException("expression");
ModelMetadata metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
return SelectInternal(htmlHelper, metadata, optionLabel, ExpressionHelper.GetExpressionText(expression), selectList,
false /* allowMultiple */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata, string optionLabel, string name,
IEnumerable<ExtendedSelectListItem> selectList, bool allowMultiple,
IDictionary<string, object> htmlAttributes)
{
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
if (String.IsNullOrEmpty(fullName))
throw new ArgumentException("No name");
if (selectList == null)
throw new ArgumentException("No selectlist");
object defaultValue = (allowMultiple)
? htmlHelper.GetModelStateValue(fullName, typeof(string[]))
: htmlHelper.GetModelStateValue(fullName, typeof(string));
// If we haven't already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.
if (defaultValue == null)
defaultValue = htmlHelper.ViewData.Eval(fullName);
if (defaultValue != null)
{
IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
IEnumerable<string> values = from object value in defaultValues
select Convert.ToString(value, CultureInfo.CurrentCulture);
HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
List<ExtendedSelectListItem> newSelectList = new List<ExtendedSelectListItem>();
foreach (ExtendedSelectListItem item in selectList)
{
item.Selected = (item.Value != null)
? selectedValues.Contains(item.Value)
: selectedValues.Contains(item.Text);
newSelectList.Add(item);
}
selectList = newSelectList;
}
// Convert each ListItem to an <option> tag
StringBuilder listItemBuilder = new StringBuilder();
// Make optionLabel the first item that gets rendered.
if (optionLabel != null)
listItemBuilder.Append(
ListItemToOption(new ExtendedSelectListItem()
{
Text = optionLabel,
Value = String.Empty,
Selected = false
}));
foreach (ExtendedSelectListItem item in selectList)
{
listItemBuilder.Append(ListItemToOption(item));
}
TagBuilder tagBuilder = new TagBuilder("select")
{
InnerHtml = listItemBuilder.ToString()
};
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
tagBuilder.GenerateId(fullName);
if (allowMultiple)
tagBuilder.MergeAttribute("multiple", "multiple");
// If there are any errors for a named field, we add the css attribute.
System.Web.Mvc.ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
{
if (modelState.Errors.Count > 0)
{
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
}
tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(fullName, metadata));
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
internal static string ListItemToOption(ExtendedSelectListItem item)
{
TagBuilder builder = new TagBuilder("option")
{
InnerHtml = HttpUtility.HtmlEncode(item.Text)
};
if (item.Value != null)
{
builder.Attributes["value"] = item.Value;
}
if (item.Selected)
{
builder.Attributes["selected"] = "selected";
}
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(item.HtmlAttributes);
builder.MergeAttributes(attributes);
if (item.HtmlAttributesDict != null)
{
foreach (var attribute in item.HtmlAttributesDict)
{
var key = attribute.Key.ToLower(); // We call ToLower to keep the same naming convention used by MVC's HtmlHelper.AnonymousObjectToHtmlAttributes
var value = attribute.Value?.ToString() ?? "";
builder.Attributes[key] = value;
}
}
return builder.ToString(TagRenderMode.Normal);
}
}
}
To use it in Razor you do:
@{
var availableLocations = new ExtendedSelectList(Model.AvailableLocations, "Id", "Value", Model.LocationId);
}
@Html.ExtendedDropDownListFor(m => Model.LocationId, availableLocations.ExtendedSelectListItems, null, new { id = "ddLocation" })
Notice the "availableLocations.ExtendedSelectListItems" because you can't use the Enumerable items directly as they are simple SelectListItem