Validate that One of Two Properties is Provided
Sometimes, you want to make sure that one of two properties is provided by a user. It's not natively supported in Asp.Net Core so here's a custom attribute to help with that.
/// <summary>
/// Validation attribute that ensures at least one of two specified properties has a value.
/// Both properties can have values, but at least one must be provided.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireOneOfTwoAttribute : ValidationAttribute
{
public string FirstProperty { get; }
public string SecondProperty { get; }
public string FirstDisplayName { get; set; }
public string SecondDisplayName { get; set; }
public RequireOneOfTwoAttribute(string firstProperty, string secondProperty)
{
FirstProperty = firstProperty ?? throw new ArgumentNullException(nameof(firstProperty));
SecondProperty = secondProperty ?? throw new ArgumentNullException(nameof(secondProperty));
// Set default display names
FirstDisplayName = firstProperty;
SecondDisplayName = secondProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
return new ValidationResult($"Either {FirstDisplayName} or {SecondDisplayName} must be provided.");
}
var firstProp = validationContext.ObjectType.GetProperty(FirstProperty);
var secondProp = validationContext.ObjectType.GetProperty(SecondProperty);
if (firstProp == null)
{
throw new ArgumentException($"Property '{FirstProperty}' not found on type '{validationContext.ObjectType.Name}'");
}
if (secondProp == null)
{
throw new ArgumentException($"Property '{SecondProperty}' not found on type '{validationContext.ObjectType.Name}'");
}
var firstValue = firstProp.GetValue(value);
var secondValue = secondProp.GetValue(value);
// Check if both values are null or empty
bool firstIsEmpty = IsNullOrEmpty(firstValue);
bool secondIsEmpty = IsNullOrEmpty(secondValue);
if (firstIsEmpty && secondIsEmpty)
{
return new ValidationResult(
$"Either {FirstDisplayName} or {SecondDisplayName} must be provided.",
new[] { FirstProperty, SecondProperty });
}
return ValidationResult.Success;
}
private static bool IsNullOrEmpty(object value)
{
return value switch
{
null => true,
string str => string.IsNullOrWhiteSpace(str),
_ => false
};
}
}
Here's an example of how you can use this attribute. Imagine you have a signup model and you require a user to provide either an email address or a phone number. It's okay if they provide both, but they must provide at least one of the two properties.
[RequireOneOfTwo(nameof(Email), nameof(PhoneNumber),
FirstDisplayName = "Email Address",
SecondDisplayName = "Phone Number")]
public class SignupModel
{
public string Email { get; set; } = string.Empty;
public string PhoneNumber { get; set; } = string.Empty;
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
FirstDisplayName
and SecondDisplayName
are optional properties to improve readability.