Using ASP.NET Core 2.2 Razor Pages, I\'m exploring binding radio buttons and dropdownlists to the page model.
Plenty of people are asking about client-side validation to
Came up with my own elegant solution since I found nothing out there.
With the helper class below, I'll declare my model with this
[BindProperty]
public InputList Gender { get; set; } = new InputList(new[] { "Man", "Woman" });
[BindProperty]
public InputList Country { get; set; } = new InputList(new NameValueCollection()
{
{ "", "--Select--" },
{ "CA", "Canada" },
{ "US", "USA" },
{ "MX", "Mexico" }
});
Insert radio buttons and a dropdown list on my page
@foreach (var item in Model.Gender.ListItems)
{
}
@Html.DropDownListFor(x => x.Country.Value, Model.Country.ListItems)
And voilà! Validation works both on the client-side and server-side, ensuring posted value is valid.
Of course, can move "Man" and "Woman" into constants, and can move the list of countries into a separate class that generates it once for the whole application.
Here's the InputList helper class.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace EmergenceGuardian.WebsiteTools.Web
{
///
/// Represents a list of items to display as radio buttons or drop down list that can be bound to a web page and validated.
///
[InputListValidation]
public class InputList
{
///
/// Initializes a new instance of InputList with specified list of items that will be used for both the value and text.
///
/// A list of string values reprenting valid values.
/// Whether this field is required.
public InputList(IEnumerable values, bool required = true)
{
Required = required;
foreach (var item in values)
{
ListItems.Add(new SelectListItem(item, item));
}
}
///
/// Initializes a new instance of InputList with specified list of SelectListItem objects.
///
/// A list of SelectListItem objects representing display text and valid values.
/// Whether this field is required.
public InputList(IEnumerable values, bool required = true)
{
Required = required;
ListItems.AddRange(values);
}
///
/// Initializes a new instance of InputList with a NameValueCollection allowing quick collection initializer.
///
/// The NameValueCollection containing display texts and valid values.
/// Whether this field is required.
public InputList(NameValueCollection values, bool required = true)
{
Required = required;
foreach (var key in values.AllKeys)
{
ListItems.Add(new SelectListItem(values[key], key));
}
}
///
/// Gets or sets whether this field is required.
///
public bool Required { get; set; }
///
/// Gets or sets the list of display text and valid values, used for display and validation.
///
public List ListItems { get; set; } = new List();
///
/// Gets or sets the user input value. This value can be bound to the UI and validated by InputListValidation.
///
public string Value { get; set; }
}
///
/// Validates an InputList class to ensure Value is contained in ListItems.
///
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
sealed public class InputListValidationAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "Selected value is invalid.";
private const string DefaultRequiredErrorMessage = "The {0} field is required.";
public InputListValidationAttribute()
{
}
///
/// Validates whether InputList.Value contains a valid value.
///
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var input = value as InputList;
if (input != null)
{
if (string.IsNullOrEmpty(input.Value))
{
if (input.Required)
{
return new ValidationResult(string.Format(ErrorMessage ?? DefaultRequiredErrorMessage, validationContext.MemberName));
}
}
else if (input.ListItems?.Any(x => x.Value == input.Value) == false)
{
return new ValidationResult(ErrorMessage ?? DefaultErrorMessage);
}
}
return ValidationResult.Success;
}
}
}