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)); 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) { 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))); } } 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); } } } }