public void Validate(bool force, bool validateDataAnnotations)
{
lock (_validationLock)
{
// Note: in v5 we should check the actual properties we missed
var validationSuspensionContext = _validationSuspensionContext;
if (validationSuspensionContext != null)
{
return;
}
}
if (SuspendValidation)
{
return;
}
if (IsValidating)
{
return;
}
IsValidating = true;
var existingValidationContext = _validationContext;
if (existingValidationContext == null)
{
existingValidationContext = new ValidationContext();
}
var hasErrors = existingValidationContext.HasErrors;
var hasWarnings = existingValidationContext.HasWarnings;
var validationContext = new ValidationContext();
var changes = new List<ValidationContextChange>();
var validator = GetValidator();
if (validator != null)
{
validator.BeforeValidation(this, existingValidationContext.GetFieldValidations(), existingValidationContext.GetBusinessRuleValidations());
}
OnValidating(validationContext);
CatchUpWithSuspendedAnnotationsValidation();
if (force && validateDataAnnotations)
{
var type = GetType();
var ignoredOrFailedPropertyValidations = _propertyValuesIgnoredOrFailedForValidation[type];
// In forced mode, validate all registered properties for annotations
var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(type);
foreach (var propertyData in catelTypeInfo.GetCatelProperties())
{
var propertyInfo = propertyData.Value.GetPropertyInfo(type);
if (propertyInfo == null || !propertyInfo.HasPublicGetter)
{
// Note: non-public getter, do not validate
ignoredOrFailedPropertyValidations.Add(propertyData.Key);
continue;
}
var propertyValue = GetValue(propertyData.Value);
ValidatePropertyUsingAnnotations(propertyData.Key, propertyValue, propertyData.Value);
}
#if !WINDOWS_PHONE && !NETFX_CORE && !PCL && !NET35
// Validate non-catel properties as well for attribute validation
foreach (var propertyInfo in catelTypeInfo.GetNonCatelProperties())
{
if (_firstAnnotationValidation)
{
if (propertyInfo.Value.IsDecoratedWithAttribute(typeof(ExcludeFromValidationAttribute)))
{
ignoredOrFailedPropertyValidations.Add(propertyInfo.Key);
}
}
if (!propertyInfo.Value.HasPublicGetter)
{
// Note: non-public getter, do not validate
ignoredOrFailedPropertyValidations.Add(propertyInfo.Key);
continue;
}
// TODO: Should we check for annotations attributes?
if (ignoredOrFailedPropertyValidations.Contains(propertyInfo.Key))
{
continue;
}
try
{
var propertyValue = propertyInfo.Value.PropertyInfo.GetValue(this, null);
ValidatePropertyUsingAnnotations(propertyInfo.Key, propertyValue, null);
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to validate property '{0}.{1}', adding it to the ignore list", type.Name, propertyInfo.Key);
ignoredOrFailedPropertyValidations.Add(propertyInfo.Key);
}
}
_firstAnnotationValidation = false;
#endif
}
if (!IsValidated || force)
{
lock (_validationLock)
{
var fieldValidationResults = new List<IFieldValidationResult>();
var businessRuleValidationResults = new List<IBusinessRuleValidationResult>();
#region Fields
if (validator != null)
{
validator.BeforeValidateFields(this, validationContext.GetFieldValidations());
}
OnValidatingFields(validationContext);
if (validator != null)
{
validator.ValidateFields(this, fieldValidationResults);
}
#if !WINDOWS_PHONE && !NETFX_CORE && !PCL && !NET35
// Support annotation validation
fieldValidationResults.AddRange(from fieldAnnotationValidation in _dataAnnotationValidationResults
where !string.IsNullOrEmpty(fieldAnnotationValidation.Value)
select (IFieldValidationResult)FieldValidationResult.CreateError(fieldAnnotationValidation.Key, fieldAnnotationValidation.Value));
#endif
ValidateFields(fieldValidationResults);
OnValidatedFields(validationContext);
if (validator != null)
{
validator.AfterValidateFields(this, fieldValidationResults);
}
// As the last step, sync the field validation results with the context
foreach (var fieldValidationResult in fieldValidationResults)
{
validationContext.AddFieldValidationResult(fieldValidationResult);
}
#endregion
#region Business rules
if (validator != null)
{
validator.BeforeValidateBusinessRules(this, validationContext.GetBusinessRuleValidations());
}
OnValidatingBusinessRules(validationContext);
if (validator != null)
{
validator.ValidateBusinessRules(this, businessRuleValidationResults);
}
ValidateBusinessRules(businessRuleValidationResults);
OnValidatedBusinessRules(validationContext);
if (validator != null)
{
validator.AfterValidateBusinessRules(this, businessRuleValidationResults);
}
// As the last step, sync the field validation results with the context
foreach (var businessRuleValidationResult in businessRuleValidationResults)
{
validationContext.AddBusinessRuleValidationResult(businessRuleValidationResult);
}
#endregion
if (validator != null)
{
validator.Validate(this, validationContext);
}
IsValidated = true;
// Manual sync to get the changes
changes = existingValidationContext.SynchronizeWithContext(validationContext);
}
}
OnValidated(validationContext);
if (validator != null)
{
validator.AfterValidation(this, validationContext.GetFieldValidations(), validationContext.GetBusinessRuleValidations());
}
#region Notify changes
bool hasNotifiedBusinessWarningsChanged = false;
bool hasNotifiedBusinessErrorsChanged = false;
foreach (var change in changes)
{
var changeAsFieldValidationResult = change.ValidationResult as IFieldValidationResult;
var changeAsBusinessRuleValidationResult = change.ValidationResult as IBusinessRuleValidationResult;
if (changeAsFieldValidationResult != null)
{
switch (change.ValidationResult.ValidationResultType)
{
case ValidationResultType.Warning:
NotifyWarningsChanged(changeAsFieldValidationResult.PropertyName, false);
break;
case ValidationResultType.Error:
NotifyErrorsChanged(changeAsFieldValidationResult.PropertyName, false);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
else if (changeAsBusinessRuleValidationResult != null)
{
switch (change.ValidationResult.ValidationResultType)
{
case ValidationResultType.Warning:
if (!hasNotifiedBusinessWarningsChanged)
{
hasNotifiedBusinessWarningsChanged = true;
NotifyWarningsChanged(string.Empty, false);
}
break;
case ValidationResultType.Error:
if (!hasNotifiedBusinessErrorsChanged)
{
hasNotifiedBusinessErrorsChanged = true;
NotifyErrorsChanged(string.Empty, false);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
if (_validationContext.HasWarnings != hasWarnings)
{
RaisePropertyChanged(HasWarningsMessageProperty);
}
if (_validationContext.HasErrors != hasErrors)
{
RaisePropertyChanged(HasErrorsMessageProperty);
}
#endregion
IsValidating = false;
}