Skip to content

Instantly share code, notes, and snippets.

@vanillajonathan
Created December 11, 2018 12:03
Show Gist options
  • Save vanillajonathan/17317a2d7dfe6ecd809221a4feec2d6a to your computer and use it in GitHub Desktop.
Save vanillajonathan/17317a2d7dfe6ecd809221a4feec2d6a to your computer and use it in GitHub Desktop.
Data annotation validation attribute that compares one property with another.
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace Example
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class CompareToAttribute : ValidationAttribute
{
public static string FormatEqual = "{0} must equal {1}.";
public static string FormatLessThan = "{0} must be less than {1}.";
public static string FormatLessThanOrEqual = "{0} must be less than or equal to {1}.";
public static string FormatGreaterThan = "{0} must be greater than {1}.";
public static string FormatGreaterThanOrEqual = "{0} must be greater than or equal to {1}.";
public static string FormatNotEqual = "{0} must not equal {1}.";
private readonly Comparison _comparison;
public CompareToAttribute(Comparison comparison, string otherProperty) : base("SR.CompareAttribute_MustMatch")
{
OtherProperty = otherProperty ?? throw new ArgumentNullException(nameof(otherProperty));
_comparison = comparison;
}
public string OtherProperty { get; }
public string OtherPropertyDisplayName { get; internal set; }
public override bool RequiresValidationContext => true;
public override string FormatErrorMessage(string name) =>
string.Format(
CultureInfo.CurrentCulture, ErrorMessageString, name, OtherPropertyDisplayName ?? OtherProperty);
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetRuntimeProperty(OtherProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult($"CompareAttribute_UnknownProperty {OtherProperty}");
}
if (otherPropertyInfo.GetIndexParameters().Any())
{
throw new ArgumentException($"Common_PropertyNotFound {validationContext.ObjectType.FullName} {OtherProperty}");
}
object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var comparison = ((IComparable)value).CompareTo(otherPropertyValue);
if (!IsValid(comparison))
{
if (OtherPropertyDisplayName == null)
{
OtherPropertyDisplayName = GetDisplayNameForProperty(otherPropertyInfo);
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
private string GetDisplayNameForProperty(PropertyInfo property)
{
var attributes = CustomAttributeExtensions.GetCustomAttributes(property, true);
var display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
if (display != null)
{
return display.GetName();
}
return OtherProperty;
}
private bool IsValid(int comparison)
{
var format = "";
switch (_comparison)
{
case Comparison.Equal when comparison != 0:
format = FormatEqual;
break;
case Comparison.GreaterThan when comparison != 1:
format = FormatGreaterThan;
break;
case Comparison.GreaterThanOrEqual when comparison == -1:
format = FormatGreaterThanOrEqual;
break;
case Comparison.LessThan when comparison != -1:
format = FormatLessThan;
break;
case Comparison.LessThanOrEqual when comparison == 1:
format = FormatLessThanOrEqual;
break;
case Comparison.NotEqual when comparison == 0:
format = FormatNotEqual;
break;
}
if (format != "")
{
ErrorMessage = format;
return false;
}
return true;
}
}
public enum Comparison
{
Equal,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
NotEqual
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment