Skip to content

Instantly share code, notes, and snippets.

@nickalbrecht
Last active August 21, 2026 22:06
Show Gist options
  • Select an option

  • Save nickalbrecht/3813767 to your computer and use it in GitHub Desktop.

Select an option

Save nickalbrecht/3813767 to your computer and use it in GitHub Desktop.
SelectListItem Extension Methods for DropDowns in MVC Core. Added ability to specify the option's Group, and for some bug fixes, more XML docs
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc.Rendering;
///For adding DebuggerDisplay support within VisualStudio for <see cref="SelectListItem"/>
[assembly: DebuggerDisplay("[Text={Text}, Value={Value}, Selected={Selected}, Group={Group?.Name}]", Target = typeof(SelectListItem))]
/// <summary>
/// Generic set of functions for building a <see cref="List&lt;SelectListItem&gt;"/> (<c>T</c> is <see cref="SelectListItem"/>) for use in rendering HTML <c>select</c> elements
/// </summary>
/// <remarks>Used <see cref="List&lt;&gt;"/> because of the implied order being important, and because <see cref="IList&lt;&gt;"/> and <see cref="IReadOnlyList&lt;&gt;"/> don't implement each other, but <see cref="List&lt;&gt;"/> implements both.</remarks>
public static class SelectListExtensionMethods
{
/// <summary>
/// (Default) A SelectListItem to use when not picking a value is exclusive of all other options
/// </summary>
public static readonly SelectListItem PickOneSelectListItem = new() { Text = "-- Pick One --", Value = string.Empty };
/// <summary>
/// A SelectListItem to use when not picking a value is inclusive of all other options (for example, as a filter)
/// </summary>
public static readonly SelectListItem AnySelectListItem = new() { Text = "-- Any --", Value = string.Empty };
/// <summary>
/// A SelectList item to indicate that not picking a value is an appropriate choice
/// </summary>
public static readonly SelectListItem NoneSelectListItem = new() { Text = "-- None --", Value = string.Empty };
public const string EphemeralGroupName = "Ephemeral (will disappear if changed)";
private static bool IsDefault(object value)
{
if (value is Guid guid && guid == Guid.Empty)
return true;
if (value is string text && string.IsNullOrWhiteSpace(text))
return true;
else
return value == default;
}
private static SelectListGroup? GetGroup<TGroup>(TGroup? groupValue, Dictionary<TGroup, SelectListGroup> groups) where TGroup : notnull
{
if (groupValue == null)
return null;
var hit = groups.TryGetValue(groupValue, out var selectListGroup);
if (!hit) {
if (groupValue == null)
return null;
selectListGroup = new SelectListGroup() { Name = groupValue.ToString() };
groups.Add(groupValue, selectListGroup);
}
return selectListGroup;
}
/// <summary>
/// Allows you to set the group of any given SelectListItem based off of the groups already in use in a given collection of SelectListItems.
/// </summary>
/// <param name="item">The item you want to set the Group for</param>
/// <param name="groupName">The Group name you want to set</param>
/// <param name="selectListItems">The list to check if the new Group exists and thus use, or to create a new one</param>
public static void SetGroup(this SelectListItem item, string groupName, IEnumerable<SelectListItem> selectListItems)
{
item.Group = selectListItems.FirstOrDefault(x => x.Group?.Name == groupName)?.Group ?? new SelectListGroup() { Name = groupName };
}
#region Primitive Keys
/// <summary>
/// Returns a collection of SelectListItem for each of the items in the collection passed in.
/// </summary>
/// <example>
/// people.ToSelectList(x => x.PersonId, x => x.FullName);
/// </example>
/// <param name="enumerable">The collection to generate the list from</param>
/// <param name="key">The property to use as the value attribute of each list item.</param>
/// <param name="text">The property to use as the text attribute of each list item.</param>
public static List<SelectListItem> ToSelectList<TType, TKey>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text) where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, (Func<TType, string>?)null, null, PickOneSelectListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each of the items in the collection passed in.
/// </summary>
/// <example>
/// people.ToSelectList(x => x.PersonId, x => x.FullName);
/// </example>
/// <param name="enumerable">The collection to generate the list from</param>
/// <param name="key">The property to use as the value attribute of each list item.</param>
/// <param name="text">The property to use as the text attribute of each list item.</param>
/// <param name="group">The property to use as the group attribute of each list item.</param>
public static List<SelectListItem> ToSelectList<TType, TKey, TGroup>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, Func<TType, TGroup?> group) where TGroup : notnull where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, group, null, PickOneSelectListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each of the items in the collection passed in, with a specific list item selected and optionally an empty list item.
/// </summary>
/// <example>
/// <code>
/// people.ToSelectList(x => x.PersonId, x => x.Name, 2345);
/// // or
/// people.ToSelectList(x => x.PersonId, x => x.Name, 2345, false); if you don't want the empty list item
/// </code>
/// </example>
/// <param name="enumerable">The collection to generate the list from</param>
/// <param name="key">The property to use as the value attribute of each list item.</param>
/// <param name="text">The property to use as the text attribute of each list item.</param>
/// <param name="currentKey">The String value of the list item that should be selected by default.</param>
/// <param name="includeEmptyListItem">Whether or not a default list item should be the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TType, TKey>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, TKey? currentKey, bool includeEmptyListItem = true) where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, (Func<TType, string>?)null, currentKey != null ? new[] { currentKey } : null, includeEmptyListItem ? PickOneSelectListItem : null);
}
public static List<SelectListItem> ToSelectList<TType, TKey, TGroup>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, Func<TType, TGroup?> group, TKey? currentKey, bool includeEmptyListItem = true) where TGroup : notnull where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, group, currentKey != null ? new[] { currentKey } : null, includeEmptyListItem ? PickOneSelectListItem : null);
}
/// <summary>
/// Returns a collection of SelectListItem for each of the items in the collection passed in, with a specific list item selected and a custom empty list item.
/// </summary>
/// <example>
/// <code>
/// people.ToSelectList(x => x.PersonId, x => x.Name, 2345, new SelectListItem() {Text = Resources.Views.Shared.PickOne Value = ""});
/// </code>
/// </example>
/// <param name="enumerable">The collection to generate the list from</param>
/// <param name="key">The property to use as the value attribute of each list item.</param>
/// <param name="text">The property to use as the text attribute of each list item.</param>
/// <param name="currentKey">The String value of the list item that should be selected by default.</param>
/// <param name="emptyListItem">The list item to use as the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TType, TKey>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, TKey? currentKey, SelectListItem emptyListItem) where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, (Func<TType, string>?)null, currentKey != null ? new[] { currentKey } : null, emptyListItem);
}
public static List<SelectListItem> ToSelectList<TType, TKey, TGroup>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, Func<TType, TGroup?> group, TKey? currentKey, SelectListItem emptyListItem) where TGroup : notnull where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, group, currentKey != null ? new[] { currentKey } : null, emptyListItem);
}
public static List<SelectListItem> ToSelectList<TType, TKey>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, IEnumerable<TKey>? currentKeys, bool includeEmptyListItem = true) where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, (Func<TType, string>?)null, currentKeys, includeEmptyListItem ? PickOneSelectListItem : null);
}
public static List<SelectListItem> ToSelectList<TType, TKey, TGroup>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, Func<TType, TGroup?> group, IEnumerable<TKey>? currentKeys, bool includeEmptyListItem = true) where TGroup : notnull where TType : notnull// where TKey : notnull
{
return ToSelectList(enumerable, key, text, group, currentKeys, includeEmptyListItem ? PickOneSelectListItem : null);
}
public static List<SelectListItem> ToSelectList<TType, TKey>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, string> text, IEnumerable<TKey>? currentKeys, SelectListItem emptyListItem) where TType : notnull // where TKey : notnull
{
return ToSelectList(enumerable, key, text, (Func<TType, string>?)null, currentKeys, emptyListItem);
}
public static List<SelectListItem> ToSelectList<TType, TKey, TText, TGroup>(this IEnumerable<TType> enumerable, Func<TType, TKey> key, Func<TType, TText> text, Func<TType, TGroup?>? group, IEnumerable<TKey>? currentKeys, SelectListItem? emptyListItem) where TGroup : notnull where TType : notnull// where TKey : notnull
{
var selectList = new List<SelectListItem>();
var groups = new Dictionary<TGroup, SelectListGroup>();
//Iterate over the enumerable, and create the initial list of SelectListItems from it
if (enumerable != null) {
selectList = [.. enumerable.Select(x => new SelectListItem()
{
Value = key.Invoke(x)?.ToString() ?? string.Empty,
Text = text.Invoke(x)?.ToString() ?? string.Empty,
//Using ternary operator for the group because we can't call Invoke on a null object, and group?.Invoke(x) is not permitted because the generic type TGroup is not (and can't be) constrained to a struct or class
//Primitive types (int, DateTime, Guid) are all structs but string are an object, and I want to allow primitive types to avoid cluttering calls to ToSelectList with .ToString()
Group = group != null ? GetGroup(group.Invoke(x), groups) : null,
Selected = (currentKeys! != null && currentKeys.Contains(key.Invoke(x)))
})];
}
HandleUnexpectedSelection(selectList, currentKeys, null);
//If needed, add the item to serve as the "no selection yet" indicator to the top of the list to serve as the default selection
if (emptyListItem != null)
selectList.Insert(0, emptyListItem);
return selectList;
}
public static void HandleUnexpectedSelection<TKey>(IList<SelectListItem> selectListItems, IEnumerable<TKey>? currentKeys, Func<TKey, string>? getUnexpectedName)// where TKey : notnull
{
//Remove any previously added items that might be from calling this method twice. Mainly only expected to be needed when compositing multiple select lists together
for (var i = selectListItems.Count - 1; i >= 0; i--) {
if (selectListItems[i].Group?.Name == EphemeralGroupName) selectListItems.RemoveAt(i);
}
//If any of the currentKeys do not have a respective SelectListItem already, add a new item for each of them under a new group called using EphemeralGroupName
if (currentKeys != null) {
var currentSelectListGroup = new SelectListGroup() { Name = EphemeralGroupName };
foreach (var currentKey in currentKeys.Where(x => x != null)) {
var currentKeyString = currentKey!.ToString()!;
if (!IsDefault(currentKey) && selectListItems.All(x => x.Value != currentKeyString)) {
string text;
if (getUnexpectedName != null)
text = getUnexpectedName(currentKey);
else
text = currentKeyString; //currentKey is never expected to be null so ToString() should never return null, using null-forgiving operator
var currentSelectListItem = new SelectListItem(text, currentKeyString, true) { Group = currentSelectListGroup };
if (!selectListItems.Any() || selectListItems[0].Value != string.Empty)
selectListItems.Insert(0, currentSelectListItem);
else
selectListItems.Insert(1, currentSelectListItem);
}
}
}
}
/// <summary>
/// Adds new <see cref="SelectListItem"/>s for the <paramref name="ephemeralIds"/> passed in and sets their group to "Ephemeral"
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <param name="selectListItems">The <see cref="SelectListItem"/>s to modify with the new items</param>
/// <param name="ephemeralIds">The ids to use as the <see cref="SelectListItem.Value"/></param>
/// <param name="getUnexpectedName">Method for obtaining the <see cref="SelectListItem.Text"/>. Just accept a single parameter which is the id of what to look for</param>
public static void HandleEphemeralOptions<TKey>(IList<SelectListItem> selectListItems, IEnumerable<TKey> ephemeralIds, Func<TKey, string>? getUnexpectedName)// where TKey : notnull
{
ArgumentNullException.ThrowIfNull(selectListItems);
ArgumentNullException.ThrowIfNull(ephemeralIds);
//If any of the currentKeys do not have a respective SelectListItem already, add a new item for each of them under a new group called using EphemeralGroupName
var currentSelectListGroup = new SelectListGroup() { Name = EphemeralGroupName };
foreach (var ephemeralId in ephemeralIds.Where(x => x != null)) {
var currentKeyString = ephemeralId!.ToString()!;
if (!IsDefault(ephemeralId) && selectListItems.All(x => x.Value != currentKeyString)) {
string text;
if (getUnexpectedName != null)
text = getUnexpectedName(ephemeralId);
else
text = currentKeyString;
var missingSelectListItem = new SelectListItem(text, currentKeyString) { Group = currentSelectListGroup };
selectListItems.Add(missingSelectListItem);
SetGroup(missingSelectListItem, EphemeralGroupName, selectListItems);
}
}
}
#endregion
#region Enumerable Enums
// The following three methods are only present in case you wish to control how the list if Enum's is built, useful if you need to omit some due for security reasons.
public static List<SelectListItem> ToSelectList<TEnum>(this IEnumerable<TEnum> enumerable, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
return ToSelectList(enumerable, null, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
public static List<SelectListItem> ToSelectList<TEnum>(this IEnumerable<TEnum> enumerable, SelectListItem emptyListItem, bool includeDefaultListItem = true) where TEnum : struct
{
return ToSelectList(enumerable, null, emptyListItem, includeDefaultListItem);
}
public static List<SelectListItem> ToSelectList<TEnum>(this IEnumerable<TEnum> enumerable, TEnum? currentKey, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
return ToSelectList(enumerable, currentKey, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
public static List<SelectListItem> ToSelectList<TEnum>(this IEnumerable<TEnum> enumerable, int currentKey, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
var enumCurrentKey = (TEnum)Enum.ToObject(typeof(TEnum), currentKey);
return ToSelectList(enumerable, enumCurrentKey, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem from a provided collection of Enum. Typically you would do this when you have a source enum, but want to preemptively exclude certain options, or to apply sorting first.
/// </summary>
/// <example>
/// <code>
/// IEnumerable&lt;EnumName&gt; myEnums = Enum.GetValues(typeof(EnumName)).Cast&lt;EnumName&gt;();
/// myEnums.ToSelectList(currentKey, new SelectListItem() {Text = Resources.Views.Shared.PickOne, Value = ""});
/// </code>
/// </example>
public static List<SelectListItem> ToSelectList<TEnum>(this IEnumerable<TEnum> enumerable, TEnum? currentKey, SelectListItem? emptyListItem, bool includeDefaultListItem = true) where TEnum : struct
{
List<SelectListItem> selectList = [];
if (enumerable != null) {
selectList = [.. enumerable.Select(x => new SelectListItem() { Value = x.ToString(), Text = x.GetDisplayName(), Selected = currentKey != null && EqualityComparer<TEnum>.Default.Equals(x, currentKey.Value) })];
}
//In case we want to prevent selecing what is considered the Default value for the given Enum
if (!includeDefaultListItem)
selectList.Remove(selectList.Single(x => x.Value == default(TEnum).ToString()));
if (emptyListItem != null)
selectList.Insert(0, emptyListItem);
return selectList;
}
#endregion
#region Enums
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, with a specific list item selected and optionally an empty list item.
/// </summary>
/// <example>
/// <code>
/// ExtensionMethods.ToSelectList<Colors>(2);
/// // or
/// ExtensionMethods.ToSelectList<Colors>(2, false); if you don't want the empty list item
/// </code>
/// </example>
/// <param name="currentKey">The Guid value of the list item that should be selected by default.</param>
/// <param name="includeEmptyListItem">Whether or not a default list item should be the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(int currentKey, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
var enumCurrentKey = (TEnum)Enum.ToObject(typeof(TEnum), currentKey);
return ToSelectList<TEnum>(enumCurrentKey, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, and optionally an empty list item.
/// </summary>
/// <example>
/// <code>
/// ExtensionMethods.ToSelectList<Colors>();
/// // or
/// ExtensionMethods.ToSelectList<Colors>(false); if you don't want the empty list item
/// </code>
/// </example>
/// <param name="includeEmptyListItem">Whether or not a default list item should be the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
return ToEnumSelectList<TEnum>(null, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
public static List<SelectListItem> ToSelectList<TEnum>(SelectListItem emptyListItem, bool includeDefaultListItem = true) where TEnum : struct
{
return ToEnumSelectList<TEnum>(null, emptyListItem, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, with a specific list item selected and optionally an empty list item.
/// </summary>
/// <example>
/// <code>
/// // Useful when your enum is a nullable viewmodel
/// Colors? viewModel = null;
/// ExtensionMethods.ToSelectList(viewModel)
/// </code>
/// </example>
/// <param name="currentKey">The Guid value of the list item that should be selected by default.</param>
/// <param name="includeEmptyListItem">Whether or not a default list item should be the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(TEnum? currentKey, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
return ToEnumSelectList<TEnum>(currentKey, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, with a specific list item selected and optionally an empty list item.
/// </summary>
/// <example>
/// <code>
/// Colors.Green.ToSelectList();
/// // or
/// Colors.Green.ToSelectList(false); if you don't want the empty list item
/// // or
/// var color = Colors.Green;
/// color.ToSelectList();
/// </code>
/// </example>
/// <param name="currentKey">Value to mark as Selected</param>
/// <param name="includeEmptyListItem">Whether or not a default list item should be the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(this TEnum currentKey, bool includeEmptyListItem = true, bool includeDefaultListItem = true) where TEnum : struct
{
return ToEnumSelectList<TEnum>(currentKey, includeEmptyListItem ? PickOneSelectListItem : null, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, with a specific list item selected and a custom empty list item.
/// </summary>
/// <example>
/// <code>
/// Colors.Green.ToSelectList(new SelectListItem() {Text = "~~ Pick One!!! ~~", Value = string.Empty}).Dump();
/// </code>
/// </example>
/// <param name="currentKey">Value to mark as Selected</param>
/// <param name="emptyListItem">The list item to use as the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(this TEnum? currentKey, SelectListItem emptyListItem, bool includeDefaultListItem) where TEnum : struct
{
//This is the same method as below, but changed to support nullable enums. This exists strictly to support extension method use
return ToEnumSelectList<TEnum>(currentKey, emptyListItem, includeDefaultListItem);
}
/// <summary>
/// Returns a collection of SelectListItem for each possible value of an Enum, with a specific list item selected and a custom empty list item.
/// </summary>
/// <example>
/// <code>
/// Colors.Green.ToSelectList(new SelectListItem() {Text = "~~ Pick One!!! ~~", Value = string.Empty}).Dump();
/// </code>
/// </example>
/// <param name="currentKey">Value to mark as Selected</param>
/// <param name="emptyListItem">The list item to use as the first list item before those from the collection.</param>
public static List<SelectListItem> ToSelectList<TEnum>(this TEnum currentKey, SelectListItem? emptyListItem, bool includeDefaultListItem = true) where TEnum : struct
{
return ToEnumSelectList<TEnum>(currentKey, emptyListItem, includeDefaultListItem);
}
private static List<SelectListItem> ToEnumSelectList<TEnum>(TEnum? currentKey, SelectListItem? emptyListItem, bool includeDefaultListItem = true) where TEnum : struct
{
List<SelectListItem> selectList;
if (typeof(TEnum).GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0) {
selectList = [.. Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.Select(x => new SelectListItem()
{
Value = x.ToString(),
Text = x.GetDisplayName(),
Selected = currentKey.HasValue && (Convert.ToInt32(x) & Convert.ToInt32(currentKey)) == Convert.ToInt32(x)
})];
}
else {
selectList = [.. Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.Select(x => new SelectListItem()
{
Value = x.ToString(),
Text = x.GetDisplayName(),
Selected = currentKey.HasValue && EqualityComparer<TEnum>.Default.Equals(x, currentKey.Value)
})];
}
//In case we want to prevent selecing what is considered the Default value for the given Enum
if (!includeDefaultListItem)
selectList.Remove(selectList.Single(x => x.Value == default(TEnum).ToString()));
if (emptyListItem != null)
selectList.Insert(0, emptyListItem);
return selectList;
}
public static List<SelectListItem> ToEnumSelectList(Type enumType) => ToEnumSelectList(enumType, PickOneSelectListItem);
/// <summary>
/// Meant to be used for an unknown Enum
/// </summary>
public static List<SelectListItem> ToEnumSelectList(Type enumType, SelectListItem? emptyListItem)
{
ArgumentNullException.ThrowIfNull(enumType);
if (!enumType.IsEnum)
throw new ArgumentException("Type must be an Enum", nameof(enumType));
List<SelectListItem> selectList;
if (enumType.GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0) {
selectList = [.. Enum.GetValues(enumType)
.Cast<object>()
.Select(x => new SelectListItem()
{
Value = x.ToString(),
Text = ExtensionMethods.GetDisplayName(x),
})];
}
else {
selectList = [.. Enum.GetValues(enumType)
.Cast<object>()
.Select(x => new SelectListItem()
{
Value = x.ToString(),
Text = ExtensionMethods.GetDisplayName(x),
})];
}
if (emptyListItem != null)
selectList.Insert(0, emptyListItem);
return selectList;
}
#endregion
}
public static string GetDisplayName<TEnum>(this TEnum enumValue) where TEnum : struct
{
string? result;
var iEnumer = Convert.ToInt64(enumValue);
if (!typeof(TEnum).GetTypeInfo().IsDefined(typeof(FlagsAttribute), false) || (iEnumer & (iEnumer - 1)) == 0)
{
var display = enumValue.GetType()
.GetMember(enumValue.ToString()!).FirstOrDefault() //Changed from First() to FirstOrDefault() to handle instances where the value attempted is not a known Enum value for the EnumType (TEnum)
?.GetCustomAttributes(false)
.OfType<System.ComponentModel.DataAnnotations.DisplayAttribute>()
.LastOrDefault();
result = display != null ? display.GetName() : enumValue.ToString()!.SplitPascalCase();
}
else
{
result = GetValues<TEnum>((Enum)(object)enumValue).Select(GetDisplayName).CommaDelimitList();
}
return result ?? enumValue.ToString()!.SplitPascalCase();
}
public static string GetDisplayName<TEnum>(this TEnum? enumValue) where TEnum : struct
{
if (enumValue == null)
return string.Empty;
else
return GetDisplayName(enumValue.Value);
}
public static string GetDisplayName(object value)
{
ArgumentNullException.ThrowIfNull(value, nameof(value));
var type = value.GetType();
var memberInfo = type.GetMember(value.ToString()!);
if (memberInfo.Length > 0) {
if (memberInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false) is DisplayAttribute[] displayAttributes && displayAttributes.Length > 0) {
return displayAttributes[0].Name!;
}
}
return value.ToString()!;
}
private static IEnumerable<TEnum> GetValues<TEnum>(this Enum enumValue) where TEnum : struct
{
if (!typeof(TEnum).GetTypeInfo().IsEnum)
throw new ArgumentException("Generic parameter must be enum");
var valueAsInt = Convert.ToInt64(enumValue);
foreach (var item in Enum.GetValues(typeof(TEnum))) {
var itemAsInt = Convert.ToInt64(item);
if (itemAsInt == (valueAsInt & itemAsInt) && itemAsInt != 0) {
yield return (TEnum)item;
}
}
}
/// <summary>
/// Splits a camel cased string into separate words delimited by a space
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("GeneratedRegex", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Not my regex, don't want to try and split it up into two objects/instances")]
public static string SplitCamelCase(this string str)
{
return System.Text.RegularExpressions.Regex.Replace(System.Text.RegularExpressions.Regex.Replace(str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2");
}
/// <summary>
/// Splits a pascal cased string into separate words delimited by a space
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string SplitPascalCase(this string str)
{
return SplitCamelCase(str);
}
@nickalbrecht

Copy link
Copy Markdown
Author

Revised the logic to not depend on strongly typed keys as String and Guid. Will now take any type that exposes a ToString() method.

@nickalbrecht

Copy link
Copy Markdown
Author

Modified the Enum extension method to correctly handle Enums behaving as Flags (Must have the [Flags] attribute decorating the enum definition).

@nickalbrecht

Copy link
Copy Markdown
Author

Some bug fixes to the code, added the ability to also select Group, started using this with ASP.NET Core, so there might be some changes there, but I don't believe there were any? Added more XML doc comments and also added the supporting methods I had neglected to include last time

@nickalbrecht

Copy link
Copy Markdown
Author

Fixes the logic when dealing with SelectListGroup to not use GroupBy or OrderBy so that it leaves the enumerable's order intact. Refactored out a method for handling which SelectListGroup gets used. Refactored the methods so that the they don't chain so much from one static method to the next to make it easier to follow, and debug if needed. Refactored the methods that use a generic key down to one method instead of two. General cleanups for a few other pieces like comments and a few other small code changed. If anyone encounters an error/bug from using this, let me know :-)

@nickalbrecht

Copy link
Copy Markdown
Author

Updating to match what I'm currently using. It's been long enough that I don't remember what specific fixes some of the changes were for

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment