I am attempting to create a neat, reusable checkbox list for enums with the [flags] attribute in Razor Pages with .NET Core 7.
I do not know which worflow to use - Partial, TagHelpers, ViewComponent or any others (or a combination), neither how I might apply these tools (having no experience creating any of them) to create a clean, efficient and reusable tool/helper.
The code below works, but it is not particularly reusable - for example, if I wanted to change the html so the label element became a parent of the checkbox input, I will need to change this in every instance of the 'cut and pasted' cshtml code.
In addition, the call to the helper function MyHtmlHelpers.EnumToCheckboxList<Urgency>(nameof(TransportReferral), nameof(TransportReferral.Urgency), TransportReferral?.Urgency) seems verbose and inefficient when compared to TagHelpers. Instead, it would be ideal to be able to access all these arguments with a single reference - in a similar way the TagHelpers do with the asp-for attribute, but I do not know how this might be achieved.
public static partial class MyHtmlHelpers
{
public static IEnumerable<CheckboxListItem> EnumToCheckboxList<TEnum>(string? modelName, string propertyName, TEnum? enumValue) where TEnum : struct, Enum
{
string name = string.IsNullOrEmpty(modelName)
? propertyName
: modelName + '.' + propertyName;
string idPrefix = name.Replace('.', '_');
return Enum.GetValues<TEnum>().Select(e =>
{
var eStr = e.ToString();
var eInt = Convert.ToInt32(e).ToString();
// ignoring DisplayAttribute.Name
return new CheckboxListItem
{
Display = typeof(TEnum).GetMember(eStr)[0]
.GetCustomAttributes<DescriptionAttribute>(false)
.FirstOrDefault()?
.Description ?? SplitCamelCase(eStr),
IsChecked = enumValue.HasValue && enumValue.Value.HasFlag(e),
Value = eInt,
Name = name,
Id = idPrefix + '_' + eInt,
};
}).ToList();
}
public static string SplitCamelCase(string input)
{
return lowerUpper().Replace(input, "$1 $2");
}
[GeneratedRegex("([a-z])([A-Z])", RegexOptions.CultureInvariant)]
private static partial Regex lowerUpper();
}
public class CheckboxListItem
{
public string Display { get; set; }
public string Value { get; set; }
public string Name { get; set; }
public string Id { get; set; }
public bool IsChecked { get; set; }
}
consumed in a cshtml page like so:
@foreach (var e in MyHtmlHelpers.EnumToCheckboxList<Urgency>(nameof(TransportReferral), nameof(TransportReferral.Urgency), Model.TransportReferral?.Urgency))
{
<div class="form-check form-check-inline">
<input type="checkbox"
name="@e.Name"
id="@e.Id"
checked="@e.IsChecked"
value="@e.Value">
<label class="form-check-label" for="@e.Id">
@e.Display
</label>
</div>
}
So in summary, is there a way to refactor the above code, taking advantage of Razor pages tools, to make the cshtml markup more reusable and also allow the full name TransportReferral.Urgency and its value to be passed cleanly to the tool with a single argument, similarly to (or in the same way) the asp-for attribute does for taghelpers?