using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Resources;
using System.Globalization;
using System.Linq;
using System.Reflection;
namespace System.ComponentModel.DataAnnotations {
///
/// Helper class to validate objects, properties and other values using their associated
/// custom attributes.
///
public static class Validator {
private static ValidationAttributeStore _store = ValidationAttributeStore.Instance;
///
/// Tests whether the given property value is valid.
///
///
/// This method will test each associated with the property
/// identified by . If is non-null,
/// this method will add a to it for each validation failure.
///
/// If there is a found on the property, it will be evaluated before all other
/// validation attributes. If the required validator fails then validation will abort, adding that single
/// failure into the when applicable, returning a value of false.
///
///
/// If is null and there isn't a failure,
/// then all validators will be evaluated.
///
///
/// The value to test.
/// Describes the property member to validate and provides services and context for the validators.
/// Optional collection to receive s for the failures.
/// true if the value is valid, false if any validation errors are encountered.
///
/// When the of is not a valid property.
///
public static bool TryValidateProperty(object value, ValidationContext validationContext, ICollection validationResults) {
// Throw if value cannot be assigned to this property. That is not a validation exception.
Type propertyType = _store.GetPropertyType(validationContext);
string propertyName = validationContext.MemberName;
EnsureValidPropertyType(propertyName, propertyType, value);
bool result = true;
bool breakOnFirstError = (validationResults == null);
IEnumerable attributes = _store.GetPropertyValidationAttributes(validationContext);
foreach (ValidationError err in GetValidationErrors(value, validationContext, attributes, breakOnFirstError)) {
result = false;
if (validationResults != null) {
validationResults.Add(err.ValidationResult);
}
}
return result;
}
///
/// Tests whether the given object instance is valid.
///
///
/// This method evaluates all s attached to the object instance's type. It also
/// checks to ensure all properties marked with are set. It does not validate the
/// property values of the object.
///
/// If is null, then execution will abort upon the first validation
/// failure. If is non-null, then all validation attributes will be
/// evaluated.
///
///
/// The object instance to test. It cannot be null.
/// Describes the object to validate and provides services and context for the validators.
/// Optional collection to receive s for the failures.
/// true if the object is valid, false if any validation errors are encountered.
/// When is null.
/// When doesn't match the
/// on .
public static bool TryValidateObject(object instance, ValidationContext validationContext, ICollection validationResults) {
return TryValidateObject(instance, validationContext, validationResults, false /*validateAllProperties*/);
}
///
/// Tests whether the given object instance is valid.
///
///
/// This method evaluates all s attached to the object instance's type. It also
/// checks to ensure all properties marked with are set. If
/// is true, this method will also evaluate the s for all the immediate properties
/// of this object. This process is not recursive.
///
/// If is null, then execution will abort upon the first validation
/// failure. If is non-null, then all validation attributes will be
/// evaluated.
///
///
/// For any given property, if it has a that fails validation, no other validators
/// will be evaluated for that property.
///
///
/// The object instance to test. It cannot be null.
/// Describes the object to validate and provides services and context for the validators.
/// Optional collection to receive s for the failures.
/// If true, also evaluates all properties of the object (this process is not
/// recursive over properties of the properties).
/// true if the object is valid, false if any validation errors are encountered.
/// When is null.
/// When doesn't match the
/// on .
public static bool TryValidateObject(object instance, ValidationContext validationContext, ICollection validationResults, bool validateAllProperties) {
if (instance == null) {
throw new ArgumentNullException("instance");
}
if (validationContext != null && instance != validationContext.ObjectInstance) {
throw new ArgumentException(Resources.DataAnnotationsResources.Validator_InstanceMustMatchValidationContextInstance, "instance");
}
bool result = true;
bool breakOnFirstError = (validationResults == null);
foreach (ValidationError err in GetObjectValidationErrors(instance, validationContext, validateAllProperties, breakOnFirstError)) {
result = false;
if (validationResults != null) {
validationResults.Add(err.ValidationResult);
}
}
return result;
}
///
/// Tests whether the given value is valid against a specified list of s.
///
///
/// This method will test each s specified . If
/// is non-null, this method will add a
/// to it for each validation failure.
///
/// If there is a within the , it will
/// be evaluated before all other validation attributes. If the required validator fails then validation will
/// abort, adding that single failure into the when applicable, returning a
/// value of false.
///
///
/// If is null and there isn't a failure,
/// then all validators will be evaluated.
///
///
/// The value to test. It cannot be null.
/// Describes the object being validated and provides services and context for the validators.
/// Optional collection to receive s for the failures.
/// The list of s to validate this against.
/// true if the object is valid, false if any validation errors are encountered.
public static bool TryValidateValue(object value, ValidationContext validationContext, ICollection validationResults, IEnumerable validationAttributes) {
bool result = true;
bool breakOnFirstError = validationResults == null;
foreach (ValidationError err in GetValidationErrors(value, validationContext, validationAttributes, breakOnFirstError)) {
result = false;
if (validationResults != null) {
validationResults.Add(err.ValidationResult);
}
}
return result;
}
///
/// Throws a if the given property is not valid.
///
/// The value to test.
/// Describes the object being validated and provides services and context for the validators. It cannot be null.
/// When is null.
/// When is invalid for this property.
public static void ValidateProperty(object value, ValidationContext validationContext) {
// Throw if value cannot be assigned to this property. That is not a validation exception.
Type propertyType = _store.GetPropertyType(validationContext);
EnsureValidPropertyType(validationContext.MemberName, propertyType, value);
IEnumerable attributes = _store.GetPropertyValidationAttributes(validationContext);
ValidationError err = GetValidationErrors(value, validationContext, attributes, false).FirstOrDefault();
if (err != null) {
err.ThrowValidationException();
}
}
///
/// Throws a if the given is not valid.
///
///
/// This method evaluates all s attached to the object's type.
///
/// The object instance to test. It cannot be null.
/// Describes the object being validated and provides services and context for the validators. It cannot be null.
/// When is null.
/// When is null.
/// When doesn't match the
/// on .
/// When is found to be invalid.
public static void ValidateObject(object instance, ValidationContext validationContext) {
ValidateObject(instance, validationContext, false /*validateAllProperties*/);
}
///
/// Throws a if the given object instance is not valid.
///
///
/// This method evaluates all s attached to the object's type.
/// If is true it also validates all the object's properties.
///
/// The object instance to test. It cannot be null.
/// Describes the object being validated and provides services and context for the validators. It cannot be null.
/// If true, also validates all the 's properties.
/// When is null.
/// When is null.
/// When doesn't match the
/// on .
/// When is found to be invalid.
public static void ValidateObject(object instance, ValidationContext validationContext, bool validateAllProperties) {
if (instance == null) {
throw new ArgumentNullException("instance");
}
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
if (instance != validationContext.ObjectInstance) {
throw new ArgumentException(Resources.DataAnnotationsResources.Validator_InstanceMustMatchValidationContextInstance, "instance");
}
ValidationError err = GetObjectValidationErrors(instance, validationContext, validateAllProperties, false).FirstOrDefault();
if (err != null) {
err.ThrowValidationException();
}
}
///
/// Throw a if the given value is not valid for the s.
///
///
/// This method evaluates the s supplied until a validation error occurs,
/// at which time a is thrown.
///
/// A within the will always be evaluated first.
///
///
/// The value to test. It cannot be null.
/// Describes the object being tested.
/// The list of s to validate against this instance.
/// When is null.
/// When is found to be invalid.
public static void ValidateValue(object value, ValidationContext validationContext, IEnumerable validationAttributes) {
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
ValidationError err = GetValidationErrors(value, validationContext, validationAttributes, false).FirstOrDefault();
if (err != null) {
err.ThrowValidationException();
}
}
///
/// Creates a new to use to validate the type or a member of
/// the given object instance.
///
/// The object instance to use for the context.
/// An parent validation context that supplies an
/// and .
/// A new for the provided.
/// When is null.
internal static ValidationContext CreateValidationContext(object instance, ValidationContext validationContext) {
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
// Create a new context using the existing ValidationContext that acts as an IServiceProvider and contains our existing items.
ValidationContext context = new ValidationContext(instance, validationContext, validationContext.Items);
return context;
}
///
/// Determine whether the given value can legally be assigned into the specified type.
///
/// The destination for the value.
/// The value to test to see if it can be assigned as the Type indicated by .
/// true if the assignment is legal.
/// When is null.
private static bool CanBeAssigned(Type destinationType, object value) {
if (destinationType == null) {
throw new ArgumentNullException("destinationType");
}
if (value == null) {
// Null can be assigned only to reference types or Nullable or Nullable<>
return !destinationType.IsValueType ||
(destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>));
}
// Not null -- be sure it can be cast to the right type
return destinationType.IsAssignableFrom(value.GetType());
}
///
/// Determines whether the given value can legally be assigned to the given property.
///
/// The name of the property.
/// The type of the property.
/// The value. Null is permitted only if the property will accept it.
/// is thrown if is the wrong type for this property.
private static void EnsureValidPropertyType(string propertyName, Type propertyType, object value) {
if (!CanBeAssigned(propertyType, value)) {
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, DataAnnotationsResources.Validator_Property_Value_Wrong_Type, propertyName, propertyType), "value");
}
}
///
/// Internal iterator to enumerate all validation errors for the given object instance.
///
/// Object instance to test.
/// Describes the object type.
/// if true also validates all properties.
/// Whether to break on the first error or validate everything.
/// A collection of validation errors that result from validating the with
/// the given .
/// When is null.
/// When is null.
/// When doesn't match the
/// on .
private static IEnumerable GetObjectValidationErrors(object instance, ValidationContext validationContext, bool validateAllProperties, bool breakOnFirstError) {
if (instance == null) {
throw new ArgumentNullException("instance");
}
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
// Step 1: Validate the object properties' validation attributes
List errors = new List();
errors.AddRange(GetObjectPropertyValidationErrors(instance, validationContext, validateAllProperties, breakOnFirstError));
// We only proceed to Step 2 if there are no errors
if (errors.Any()) {
return errors;
}
// Step 2: Validate the object's validation attributes
IEnumerable attributes = _store.GetTypeValidationAttributes(validationContext);
errors.AddRange(GetValidationErrors(instance, validationContext, attributes, breakOnFirstError));
#if !SILVERLIGHT
// We only proceed to Step 3 if there are no errors
if (errors.Any()) {
return errors;
}
// Step 3: Test for IValidatableObject implementation
IValidatableObject validatable = instance as IValidatableObject;
if (validatable != null) {
IEnumerable results = validatable.Validate(validationContext);
foreach (ValidationResult result in results.Where(r => r != ValidationResult.Success)) {
errors.Add(new ValidationError(null, instance, result));
}
}
#endif
return errors;
}
///
/// Internal iterator to enumerate all the validation errors for all properties of the given object instance.
///
/// Object instance to test.
/// Describes the object type.
/// If true, evaluates all the properties, otherwise just checks that
/// ones marked with are not null.
/// Whether to break on the first error or validate everything.
/// A list of instances.
private static IEnumerable GetObjectPropertyValidationErrors(object instance, ValidationContext validationContext, bool validateAllProperties, bool breakOnFirstError) {
ICollection> properties = GetPropertyValues(instance, validationContext);
List errors = new List();
foreach (KeyValuePair property in properties) {
// get list of all validation attributes for this property
IEnumerable attributes = _store.GetPropertyValidationAttributes(property.Key);
if (validateAllProperties) {
// validate all validation attributes on this property
errors.AddRange(GetValidationErrors(property.Value, property.Key, attributes, breakOnFirstError));
} else {
// only validate the Required attributes
RequiredAttribute reqAttr = attributes.FirstOrDefault(a => a is RequiredAttribute) as RequiredAttribute;
if (reqAttr != null) {
// Note: we let the [Required] attribute do its own null testing,
// since the user may have subclassed it and have a deeper meaning to what 'required' means
ValidationResult validationResult = reqAttr.GetValidationResult(property.Value, property.Key);
if (validationResult != ValidationResult.Success) {
errors.Add(new ValidationError(reqAttr, property.Value, validationResult));
}
}
}
if (breakOnFirstError && errors.Any()) {
break;
}
}
return errors;
}
///
/// Retrieves the property values for the given instance.
///
/// Instance from which to fetch the properties.
/// Describes the entity being validated.
/// A set of key value pairs, where the key is a validation context for the property and the value is its current
/// value.
/// Ignores indexed properties.
private static ICollection> GetPropertyValues(object instance, ValidationContext validationContext) {
#if SILVERLIGHT
IEnumerable properties = from property in instance.GetType().GetProperties()
where !property.GetIndexParameters().Any()
select property;
List> items = new List>(properties.Count());
foreach (PropertyInfo property in properties) {
ValidationContext context = CreateValidationContext(instance, validationContext);
context.MemberName = property.Name;
if (_store.GetPropertyValidationAttributes(context).Any()) {
items.Add(new KeyValuePair(context, property.GetValue(instance, null)));
}
}
#else
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(instance);
List> items = new List>(properties.Count);
foreach (PropertyDescriptor property in properties) {
ValidationContext context = CreateValidationContext(instance, validationContext);
context.MemberName = property.Name;
if (_store.GetPropertyValidationAttributes(context).Any()) {
items.Add(new KeyValuePair(context, property.GetValue(instance)));
}
}
#endif
return items;
}
///
/// Internal iterator to enumerate all validation errors for an value.
///
///
/// If a is found, it will be evaluated first, and if that fails,
/// validation will abort, regardless of the parameter value.
///
/// The value to pass to the validation attributes.
/// Describes the type/member being evaluated.
/// The validation attributes to evaluate.
/// Whether or not to break on the first validation failure. A
/// failure will always abort with that sole failure.
/// The collection of validation errors.
/// When is null.
private static IEnumerable GetValidationErrors(object value, ValidationContext validationContext, IEnumerable attributes, bool breakOnFirstError) {
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
List errors = new List();
ValidationError validationError;
// Get the required validator if there is one and test it first, aborting on failure
RequiredAttribute required = attributes.FirstOrDefault(a => a is RequiredAttribute) as RequiredAttribute;
if (required != null) {
if (!TryValidate(value, validationContext, required, out validationError)) {
errors.Add(validationError);
return errors;
}
}
// Iterate through the rest of the validators, skipping the required validator
foreach (ValidationAttribute attr in attributes) {
if (attr != required) {
if (!TryValidate(value, validationContext, attr, out validationError)) {
errors.Add(validationError);
if (breakOnFirstError) {
break;
}
}
}
}
return errors;
}
///
/// Tests whether a value is valid against a single using the .
///
/// The value to be tested for validity.
/// Describes the property member to validate.
/// The validation attribute to test.
/// The validation error that occurs during validation. Will be null when the return value is true.
/// true if the value is valid.
/// When is null.
private static bool TryValidate(object value, ValidationContext validationContext, ValidationAttribute attribute, out ValidationError validationError) {
if (validationContext == null) {
throw new ArgumentNullException("validationContext");
}
ValidationResult validationResult = attribute.GetValidationResult(value, validationContext);
if (validationResult != ValidationResult.Success) {
validationError = new ValidationError(attribute, value, validationResult);
return false;
}
validationError = null;
return true;
}
///
/// Private helper class to encapsulate a ValidationAttribute with the failed value and the user-visible
/// target name against which it was validated.
///
private class ValidationError {
internal ValidationError(ValidationAttribute validationAttribute, object value, ValidationResult validationResult) {
this.ValidationAttribute = validationAttribute;
this.ValidationResult = validationResult;
this.Value = value;
}
internal object Value { get; set; }
internal ValidationAttribute ValidationAttribute { get; set; }
internal ValidationResult ValidationResult { get; set; }
internal void ThrowValidationException() {
throw new ValidationException(this.ValidationResult, this.ValidationAttribute, this.Value);
}
}
}
}