Created
January 14, 2020 17:18
-
-
Save emctague/385cf9470cc2be9ff401d215cd5381ca to your computer and use it in GitHub Desktop.
When - Conditional EntityFramework Validation using Attributes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// When | |
// | |
// Allows for vertain validation rules to only apply under certain conditions, using only an Attribute. | |
// e.g., if a certain string should match the regex `^.+@.+$` when another property, "WantsEmail", is equal to `true`: | |
// | |
// [When("WantsEmail", true, typeof(DoesRegexMatch), "^.+@.+$", Error = "Wants email, but none provided!"] | |
// public string AProperty { get; set; } | |
// | |
using System; | |
using System.ComponentModel.DataAnnotations; | |
using System.Text.RegularExpressions; | |
namespace When | |
{ | |
/// <summary> | |
/// Provides conditional Entity Framework verification, in which a particular | |
/// condition on the current attribute is only necessary when a different attribute | |
/// matches a particular value. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] | |
public sealed class When : ValidationAttribute | |
{ | |
string PropertyName { get; set; } | |
object ExpectedValue { get; set; } | |
Type VerifierType { get; set; } | |
public string Error { get; set; } | |
bool WhenNotEqual { get; set; } | |
object[] Parameters; | |
/// <summary> | |
/// Create a conditional verification. | |
/// </summary> | |
/// <param name="property">The name of a property of the current model that should be compared to <c>value</c>. Prefix with an exclamation mark (<c>!</c>) to check for inequality instead of equality.</param> | |
/// <param name="value">The value that <c>property</c> should be equal to for verification to be necessary.</param> | |
/// <param name="verifierType">A type that provides verification, such as <c>IsNot</c> or <c>DoesRegexMatch</c>. This must inherit from <c>WhenConditional</c>.</param> | |
/// <param name="parameters">Any parameters the verifier requires on its <c>Check</c> method.</param> | |
/// <example> | |
/// [When("WantsPromotionalEmails", true, typeof(DoesRegexMatch), "(.+)@(.+)", Error = "Invalid email given, but needed to send promotional emails!")] | |
/// </example> | |
public When(string property, object value, Type verifierType, params object[] parameters) | |
{ | |
if (!verifierType.IsAssignableFrom(typeof(WhenConditional))) | |
{ | |
throw new ArgumentException("verifierType does not implement WhenConditional."); | |
} | |
PropertyName = property; | |
ExpectedValue = value; | |
VerifierType = verifierType; | |
Parameters = parameters ?? new object[] { }; | |
WhenNotEqual = false; | |
if (PropertyName[0] == '!') | |
{ | |
PropertyName = PropertyName.Substring(1); | |
WhenNotEqual = !WhenNotEqual; | |
} | |
} | |
protected override ValidationResult IsValid(object value, ValidationContext validationContext) | |
{ | |
bool equalResult = validationContext.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(value) == this.ExpectedValue; | |
if (WhenNotEqual != equalResult) | |
{ | |
// Add the attribute's value as the final parameter. | |
var method = VerifierType.GetMethod("Check", System.Reflection.BindingFlags.Public); | |
Array.Resize<object>(ref Parameters, Parameters.Length + 1); | |
Parameters[Parameters.Length - 1] = value; | |
if ((bool)method.Invoke(null, Parameters)) return ValidationResult.Success; | |
else return new ValidationResult(Error); | |
} | |
else return ValidationResult.Success; | |
} | |
} | |
/// <summary> | |
/// A generic interface that all valid conditionals for <c>When</c> inherit from. | |
/// Each <c>When</c> conditional has a method <c>Check</c>, which accepts some | |
/// specific number of objects as parameters and returns true or false. | |
/// </summary> | |
public interface WhenConditional | |
{ | |
} | |
/// <summary> | |
/// Verifies the inverse of the given check type parameter <c>T</c>. | |
/// </summary> | |
/// <typeparam name="T">Another checker type (<c>WhenConditional</c>) to test for.</typeparam> | |
public sealed class IsNot<T> : WhenConditional where T: WhenConditional | |
{ | |
public bool Check(params object[] list) | |
{ | |
return !(bool) typeof(T).GetMethod("Check", System.Reflection.BindingFlags.Public).Invoke(null, list); | |
} | |
} | |
/// <summary> | |
/// Verifies that the property (which must be a string) matches the given | |
/// regular expression. | |
/// </summary> | |
public sealed class DoesRegexMatch : WhenConditional | |
{ | |
public bool Check(string Pattern, object input) | |
{ | |
if (input is string) | |
{ | |
return new Regex(Pattern).IsMatch((string)input); | |
} else | |
{ | |
return false; | |
} | |
} | |
} | |
/// <summary> | |
/// Verifies that the property is not null. | |
/// </summary> | |
public sealed class IsNull : WhenConditional | |
{ | |
public bool Check(object input) | |
{ | |
return input != null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment