Created
January 23, 2012 12:52
-
-
Save feanz/1662991 to your computer and use it in GitHub Desktop.
A fluent validation system to do domain model validation
This file contains hidden or 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
using System; | |
using System.Collections.Generic; | |
using System.Collections.ObjectModel; | |
using System.ComponentModel.DataAnnotations; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
using Utilities.Extensions; | |
using Utilities.Patterns; | |
namespace Utilities.Validation | |
{ | |
/// <summary> | |
/// This class implements a fluent-style validator. | |
/// </summary> | |
public class FluentValidator : IFluentInterface | |
{ | |
private readonly int _initialErrorCount; | |
private bool _checkCondition; | |
private string _propertyName; | |
private object _target; | |
public FluentValidator() | |
{ | |
Errors = new Collection<ValidationResult>(); | |
_initialErrorCount = 0; | |
} | |
/// <summary> | |
/// Creates a new instance of this class. | |
/// </summary> | |
/// <param name="errors"> Validation results. </param> | |
public FluentValidator(ICollection<ValidationResult> errors) | |
{ | |
Errors = errors ?? new Collection<ValidationResult>(); | |
_initialErrorCount = Errors.Count; | |
} | |
/// <summary> | |
/// A combine error of all the validation errors | |
/// </summary> | |
public string ErrorMessage | |
{ | |
get | |
{ | |
var result = string.Empty; | |
foreach (var error in Errors.Select(x => x.ErrorMessage)) | |
{ | |
result += error; | |
result += Environment.NewLine; | |
} | |
return result; | |
} | |
} | |
/// <summary> | |
/// Get/set the validation results. | |
/// </summary> | |
public ICollection<ValidationResult> Errors { get; private set; } | |
/// <summary> | |
/// True if there are errors. | |
/// </summary> | |
public bool HasErrors | |
{ | |
get { return Errors.Count > _initialErrorCount; } | |
} | |
/// <summary> | |
/// Set the validation target object. | |
/// </summary> | |
/// <param name="target"> The target object. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Validate(object target) | |
{ | |
// Reset the check condition flag. | |
_checkCondition = true; | |
_propertyName = string.Empty; | |
_target = target; | |
return this; | |
} | |
/// <summary> | |
/// Set the validation expression. | |
/// </summary> | |
/// <param name="exp"> Validation expression. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Validate(Expression<Func<object>> exp) | |
{ | |
_target = GetPropertyNameAndValue(exp, ref _propertyName); | |
_checkCondition = true; | |
return this; | |
} | |
/// <summary> | |
/// Sets the property name and the target object. | |
/// </summary> | |
/// <param name="propName"> Property name. </param> | |
/// <param name="target"> Target object. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Validate(string propName, object target) | |
{ | |
// Reset the check condition flag. | |
_checkCondition = true; | |
_propertyName = propName; | |
_target = target; | |
return this; | |
} | |
/// <summary> | |
/// Determines if a value contains a specified string. | |
/// </summary> | |
/// <param name="val"> Specified string. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Contains(string val, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (((string)_target).IsNullOrEmpty()) | |
return errorMessage.IsNullOrEmpty() ? IsValid(false, "does not contain : " + val) | |
: IsValid(false, errorMessage); | |
var valToCheck = (string)_target; | |
return ErrorMessage.IsNullOrEmpty() ? IsValid(valToCheck.Contains(val), "must contain : " + val) | |
: IsValid(valToCheck.Contains(val), errorMessage); | |
} | |
/// <summary> | |
/// Returns the current instance of this class. | |
/// </summary> | |
/// <returns> </returns> | |
public FluentValidator End() | |
{ | |
return this; | |
} | |
/// <summary> | |
/// Sets a check condition. | |
/// </summary> | |
/// <param name="isOkToCheckNext"> Validate condition. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator If(bool isOkToCheckNext) | |
{ | |
_checkCondition = isOkToCheckNext; | |
return this; | |
} | |
/// <summary> | |
/// Determines if the value is in a list of values. | |
/// </summary> | |
/// <typeparam name="T"> Type of values in list. </typeparam> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <param name="vals"> List of values. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator In<T>(string errorMessage = "", params object[] vals) | |
{ | |
if (!_checkCondition) return this; | |
if (vals.IsNull() || vals.Length == 0) | |
return this; | |
var checkVal = _target.As<T>(); | |
var isValid = vals.Select(val => val.As<T>()).Contains(checkVal); | |
return errorMessage.IsNullOrEmpty() ? IsValid(isValid, "is not a valid value") : IsValid(isValid, errorMessage); | |
} | |
/// <summary> | |
/// Validates against a specified value. | |
/// </summary> | |
/// <param name="val"> Value to validate against. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Is(object val, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (_target.IsNull() && val.IsNull()) | |
return this; | |
if (_target.IsNull() && val != null) | |
return errorMessage.IsNullOrEmpty() ? IsValid(false, "must equal : " + val) : IsValid(false, errorMessage); | |
return errorMessage.IsNullOrEmpty() ? IsValid(val.Equals(_target), "must equal : " + val) : IsValid(val.Equals(_target), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a day is after a specified date. | |
/// </summary> | |
/// <param name="date"> Specified date. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsAfter(DateTime date, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var checkVal = (DateTime)_target; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(checkVal.Date.CompareTo(date.Date) > 0, | |
"must be after date : " + date.ToShortDateString()) | |
: IsValid(checkVal.Date.CompareTo(date.Date) > 0, errorMessage); | |
} | |
/// <summary> | |
/// Determines if a day is after today. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsAfterToday(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (errorMessage.IsNullOrEmpty()) | |
IsAfter(DateTime.Today); | |
else | |
IsAfter(DateTime.Today, errorMessage); | |
_checkCondition = false; | |
return this; | |
} | |
/// <summary> | |
/// Determines if a day is before a specified date. | |
/// </summary> | |
/// <param name="date"> Specified date. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsBefore(DateTime date, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var checkVal = (DateTime)_target; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(checkVal.Date.CompareTo(date.Date) < 0, | |
"must be before date : " + date.ToShortDateString()) | |
: IsValid(checkVal.Date.CompareTo(date.Date) < 0, errorMessage); | |
} | |
/// <summary> | |
/// Determines if a day is before today. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsBeforeToday(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (errorMessage.IsNullOrEmpty()) | |
IsBefore(DateTime.Today); | |
else | |
IsBefore(DateTime.Today, errorMessage); | |
_checkCondition = false; | |
return this; | |
} | |
/// <summary> | |
/// Determines if a value is between a minimum and a maximum. | |
/// </summary> | |
/// <param name="min"> Minimum value. </param> | |
/// <param name="max"> Maximum value. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsBetween(int min, int max, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var isNumeric = _target.IsNumeric(); | |
if (isNumeric) | |
{ | |
var val = _target.As<decimal>(); | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(min <= val && val <= max, "must be between : " + min + ", " + max) | |
: IsValid(min <= val && val <= max, errorMessage); | |
} | |
// can only be string. | |
var strVal = _target as string; | |
if (min > 0 && string.IsNullOrEmpty(strVal)) | |
return errorMessage.IsNullOrEmpty() ? IsValid(false, "length must be between : " + min + ", " + max) : | |
IsValid(false, errorMessage); | |
return errorMessage.IsNullOrEmpty() ? IsValid(strVal.IsNotNull() && (min <= strVal.Length && strVal.Length <= max), | |
"length must be between : " + min + ", " + max) : | |
IsValid(strVal.IsNotNull() && (min <= strVal.Length && strVal.Length <= max), | |
errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is false. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsFalse(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var isBool = _target.Is<bool>(); | |
if(!isBool) | |
return IsValid(false, "must be bool(true/false)"); | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.As<bool>() == false, "must be false") | |
: IsValid(_target.As<bool>() == false, errorMessage); | |
} | |
/// <summary> | |
/// Determines if the validation is different than the specified value. | |
/// </summary> | |
/// <param name="val"> Specified value. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsNot(object val, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (_target.IsNull() && val.IsNull()) | |
return this; | |
if (_target.IsNull() && val != null) | |
return this; | |
return errorMessage.IsNullOrEmpty() ? IsValid(!val.Equals(_target), "must not equal : " + val) : | |
IsValid(!val.Equals(_target), errorMessage); | |
} | |
/// <summary> | |
/// Determines if the value is not null. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsNotNull(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() ? IsValid(_target.IsNotNull(), "must be not null") | |
: IsValid(_target.IsNotNull(), errorMessage); | |
} | |
/// <summary> | |
/// Determines if the value is null. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsNull(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.IsNull(), "must be null") | |
: IsValid(_target.IsNull(), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is true. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsTrue(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var isBool = _target.Is<bool>(); | |
if (!isBool) | |
IsValid(false, "must be bool(true/false)"); | |
return errorMessage.IsNullOrEmpty() ? IsValid(_target.As<bool>(), "must be true") : | |
IsValid(_target.As<bool>(),errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is a valid e-mail. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsValidEmail(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.ToString().IsEmail(), "must be a valid email.") | |
: IsValid(_target.ToString().IsEmail(), errorMessage); | |
} | |
/// <summary> | |
/// Determine if value is a valid MobileNumber | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsValidMobileNumber(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.ToString().IsMobileNumber(), "must be a valid zip.") | |
: IsValid(_target.ToString().IsMobileNumber(), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is a valid zip code. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsValidPostcode(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.ToString().IsPostcode(), "must be a valid zip.") | |
: IsValid(_target.ToString().IsPostcode(), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is a valid URL. | |
/// </summary> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator IsValidUrl(string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(_target.ToString().IsURL(), "must be a valid url.") | |
: IsValid(_target.ToString().IsURL(), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a regular expression produces a match. | |
/// </summary> | |
/// <param name="regex"> Regular expression. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Matches(string regex,string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(Regex.IsMatch((string) _target, regex), "does not match pattern : " + regex) | |
: IsValid(Regex.IsMatch((string) _target, regex), errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is equal or below a specified maximum. | |
/// </summary> | |
/// <param name="max"> Specified maximum. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Max(int max, string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var isNumeric = _target.IsNumeric(); | |
if (!isNumeric) return IsValid(false, "must be a numeric value"); | |
var val = _target.As<decimal>(); | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(val <= max, "must have maximum value of : " + max) | |
: IsValid(val <= max, errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value is equal or above a specified minimum. | |
/// </summary> | |
/// <param name="min"> Specified minimum. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator Min(int min,string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
var isNumeric = _target.IsNumeric(); | |
if (!isNumeric) return IsValid(false, "must be numeric value"); | |
var val = _target.As<decimal>(); | |
return errorMessage.IsNullOrEmpty() ? IsValid(val >= min, "must have minimum value of : " + min): | |
IsValid(val >= min, errorMessage); | |
} | |
/// <summary> | |
/// Determines if property conforms with a custom action | |
/// </summary> | |
/// <param name="predicate">expression to execute on parameter</param> | |
/// <param name="errorMessage">Error message to display if expression return false</param> | |
/// <returns>The current instance of this class.</returns> | |
public FluentValidator Must(Func<object, bool> predicate, string errorMessage) | |
{ | |
if (predicate.IsNull()) throw new ArgumentNullException("predicate"); | |
var result = predicate(_target); | |
return errorMessage.IsNullOrEmpty() | |
? IsValid(result, "problem with Property") | |
: IsValid(result, errorMessage); | |
} | |
/// <summary> | |
/// Determines if a value does not contain a specified string. | |
/// </summary> | |
/// <param name="val"> Specified string. </param> | |
/// <param name="errorMessage">An optional error message </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator NotContain(string val,string errorMessage = "") | |
{ | |
if (!_checkCondition) return this; | |
if (string.IsNullOrEmpty((string)_target)) | |
return this; | |
var valToCheck = _target.As<string>(); | |
return errorMessage.IsNullOrEmpty() ? IsValid(!valToCheck.Contains(val), "should not contain : " + val): | |
IsValid(!valToCheck.Contains(val), errorMessage); | |
} | |
/// <summary> | |
/// Determines if the value is not in a list of values. | |
/// </summary> | |
/// <typeparam name="T"> Type of values in list. </typeparam> | |
/// <param name="errorMessage"> </param> | |
/// <param name="vals"> List of values. </param> | |
/// <returns> The current instance of this class. </returns> | |
public FluentValidator NotIn<T>(string errorMessage = "",params object[] vals) | |
{ | |
if (!_checkCondition) return this; | |
if (vals.IsNull() || vals.Length == 0) | |
return this; | |
var checkVal = _target.As<T>(); | |
var isValid = !vals.Select(val => val.As<T>()).Contains(checkVal); | |
return errorMessage.IsNullOrEmpty() ? IsValid(isValid, "is not a valid value"): | |
IsValid(isValid, errorMessage); | |
} | |
/// <summary> | |
/// Get the property name from the expression. e.g. GetPropertyName(Person)( p => p.FirstName); | |
/// </summary> | |
/// <param name="exp"> </param> | |
/// <param name="propName"> </param> | |
/// <returns> </returns> | |
private static object GetPropertyNameAndValue(Expression<Func<object>> exp, ref string propName) | |
{ | |
PropertyInfo propInfo = null; | |
if (exp.Body is MemberExpression) | |
{ | |
propInfo = ((MemberExpression)exp.Body).Member as PropertyInfo; | |
} | |
else if (exp.Body is UnaryExpression) | |
{ | |
var op = ((UnaryExpression)exp.Body).Operand; | |
propInfo = ((MemberExpression)op).Member as PropertyInfo; | |
} | |
var val = exp.Compile().DynamicInvoke(); | |
if (propInfo.IsNotNull()) propName = propInfo.Name.TitleCase(); | |
return val; | |
} | |
private FluentValidator IsValid(bool isValid, string error) | |
{ | |
if (!isValid) | |
{ | |
var prefix = string.IsNullOrEmpty(_propertyName) ? "Property " : _propertyName + " "; | |
Errors.Add(new ValidationResult(prefix + error)); | |
} | |
return this; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment