/// <summary> /// Clears the <see cref="IsDirty"/> on all childs. /// </summary> /// <param name="obj">The object.</param> /// <param name="handledReferences">The already handled references, required to prevent circular stackoverflows.</param> private static void ClearIsDirtyOnAllChilds(object obj, HashSet <IModel> handledReferences) { var objAsModelBase = obj as ModelBase; var objAsIEnumerable = obj as IEnumerable; if (objAsModelBase != null) { if (handledReferences.Contains(objAsModelBase)) { return; } objAsModelBase.IsDirty = false; handledReferences.Add(objAsModelBase); var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(obj.GetType()); foreach (var property in catelTypeInfo.GetCatelProperties()) { object value = objAsModelBase.GetValue(property.Value); ClearIsDirtyOnAllChilds(value, handledReferences); } } else if (objAsIEnumerable != null) { foreach (var childItem in objAsIEnumerable) { if (childItem is ModelBase) { ClearIsDirtyOnAllChilds(childItem, handledReferences); } } } }
/// <summary> /// Finishes the initialization after construction or deserialization. /// </summary> private void FinishInitializationAfterConstructionOrDeserialization() { var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(GetType()); foreach (var propertyData in catelTypeInfo.GetCatelProperties()) { if (propertyData.Value.SetParent) { lock (_propertyValuesLock) { var propertyValue = GetValueFast(propertyData.Key); var propertyValueAsModelBase = propertyValue as ModelBase; var propertyValueAsIEnumerable = propertyValue as IEnumerable; if (propertyValueAsModelBase != null) { propertyValueAsModelBase.SetParent(this); } else if (propertyValueAsIEnumerable != null) { foreach (var obj in propertyValueAsIEnumerable) { var objAsModelBase = obj as ModelBase; if (objAsModelBase != null) { objAsModelBase.SetParent(this); } } } } } } //SubscribeAllObjectsToNotifyChangedEvents(); }
/// <summary> /// Invokes the property changed for all registered properties. /// </summary> /// <remarks> /// Using this method does not set the <see cref="IsDirty"/> property to <c>true</c>, nor will /// it cause the object to validate itself automatically, even when the <see cref="AutomaticallyValidateOnPropertyChanged"/> /// is set to <c>true</c>. /// </remarks> internal void RaisePropertyChangedForAllRegisteredProperties() { var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(GetType()); foreach (var propertyData in catelTypeInfo.GetCatelProperties()) { if (!IsModelBaseProperty(propertyData.Key)) { RaisePropertyChanged(this, new PropertyChangedEventArgs(propertyData.Key), false, true); } } }
/// <summary> /// Initializes the XML property mappings. /// </summary> /// <param name="type">The type for which to initialize the xml mappings.</param> private void InitializeXmlPropertyMappings(Type type) { if (_xmlNameToPropertyNameMappings.ContainsKey(type)) { return; } lock (_xmlMappingsLock) { _xmlNameToPropertyNameMappings.Add(type, new Dictionary <string, string>()); _xmlPropertyNameToXmlNameMappings.Add(type, new Dictionary <string, string>()); var catelTypeInfo = _propertyDataManager.GetCatelTypeInfo(type); foreach (var propertyData in catelTypeInfo.GetCatelProperties()) { var cachedPropertyInfo = propertyData.Value.GetPropertyInfo(type); if (cachedPropertyInfo == null) { // Dynamic property, not mapped (always fixed) continue; } var propertyInfo = cachedPropertyInfo.PropertyInfo; // 1st, check if XmlIgnore is used if (AttributeHelper.IsDecoratedWithAttribute <XmlIgnoreAttribute>(propertyInfo)) { continue; } // 2nd, check if XmlAttribute is used XmlAttributeAttribute xmlAttributeAttribute = null; AttributeHelper.TryGetAttribute(propertyInfo, out xmlAttributeAttribute); if (InitializeXmlAttributeAttribute(type, xmlAttributeAttribute, propertyData.Key)) { continue; } // 3rd, check if XmlElement is used XmlElementAttribute xmlElementAttribute = null; AttributeHelper.TryGetAttribute(propertyInfo, out xmlElementAttribute); if (InitializeXmlElementAttribute(type, xmlElementAttribute, propertyData.Key)) { continue; } } } }
/// <summary> /// Creates a backup of the object property values. /// </summary> private void CreateBackup() { using (var stream = new MemoryStream()) { var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(_object.GetType()); var propertiesToIgnore = (from propertyData in catelTypeInfo.GetCatelProperties() where !propertyData.Value.IncludeInBackup select propertyData.Value.Name).ToArray(); _serializer?.SerializeMembers(_object, stream, null, propertiesToIgnore); _propertyValuesBackup = stream.ToByteArray(); } _objectValuesBackup = new Dictionary <string, object>(); _objectValuesBackup.Add(nameof(ModelBase.IsDirty), _object.IsDirty); }
private void InitializeModelValidation() { var type = GetType(); AutomaticallyValidateOnPropertyChanged = true; ValidateUsingDataAnnotations = DefaultValidateUsingDataAnnotationsValue; lock (PropertiesNotCausingValidation) { if (!PropertiesNotCausingValidation.ContainsKey(type)) { var hashSet = new HashSet <string>(); // Ignore modelbase properties hashSet.Add(nameof(AlwaysInvokeNotifyChanged)); hashSet.Add(nameof(AutomaticallyValidateOnPropertyChanged)); hashSet.Add(nameof(HideValidationResults)); hashSet.Add(nameof(HasWarnings)); hashSet.Add(nameof(HasErrors)); hashSet.Add(nameof(IsValidating)); hashSet.Add("IsValidated"); var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(type); foreach (var property in catelTypeInfo.GetCatelProperties()) { if (property.Value.IsModelBaseProperty) { hashSet.Add(property.Key); } } PropertiesNotCausingValidation.Add(type, hashSet); } } }
/// <summary> /// Validates the current object for field and business rule errors. /// </summary> /// <param name="force">If set to <c>true</c>, a validation is forced (even if the object knows it is already validated).</param> /// <param name="validateDataAnnotations">If set to <c>true</c>, the data annotations will be checked. This value is only used if <paramref name="force"/> is set to <c>true</c>.</param> /// <remarks> /// To check whether this object contains any errors, use the ValidationContext property. /// </remarks> internal void Validate(bool force, bool validateDataAnnotations) { if (IsValidating) { return; } if (IsValidationSuspended) { 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); if (force && validateDataAnnotations) { var type = GetType(); var ignoredOrFailedPropertyValidations = PropertiesNotCausingValidation[type]; // In forced mode, validate all registered properties for annotations var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(type); foreach (var propertyData in catelTypeInfo.GetCatelProperties()) { if (propertyData.Value.IsModelBaseProperty) { continue; } var propertyInfo = propertyData.Value.GetPropertyInfo(type); if (propertyInfo == null || !propertyInfo.HasPublicGetter) { // Note: non-public getter, do not validate ignoredOrFailedPropertyValidations.Add(propertyData.Key); continue; } ValidatePropertyUsingAnnotations(propertyData.Key); } #if !NETFX_CORE && !PCL // Validate non-catel properties as well for attribute validation foreach (var propertyInfo in catelTypeInfo.GetNonCatelProperties()) { if (_firstAnnotationValidation) { if (propertyInfo.Value.IsDecoratedWithAttribute <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 { ValidatePropertyUsingAnnotations(propertyInfo.Key); } 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 (_validationContext) { 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 !NETFX_CORE && !PCL // 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 var hasNotifiedBusinessWarningsChanged = false; var 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(this, new PropertyChangedEventArgs(nameof(HasWarnings)), false, false); RaisePropertyChanged(this, new PropertyChangedEventArgs(HasWarningsMessageProperty), false, false); } if (_validationContext.HasErrors != hasErrors) { RaisePropertyChanged(this, new PropertyChangedEventArgs(nameof(HasErrors)), false, false); RaisePropertyChanged(this, new PropertyChangedEventArgs(HasErrorsMessageProperty), false, false); } #endregion IsValidating = false; }
/// <summary> /// Validates the current object for field and business rule errors. /// </summary> /// <param name="force">If set to <c>true</c>, a validation is forced (even if the object knows it is already validated).</param> /// <param name="validateDataAnnotations">If set to <c>true</c>, the data annotations will be checked. This value is only used if <paramref name="force"/> is set to <c>true</c>.</param> /// <remarks> /// To check whether this object contains any errors, use the <see cref="HasErrors"/> property. /// </remarks> internal void Validate(bool force, bool validateDataAnnotations) { if (SuspendValidation) { return; } if (IsValidating) { return; } IsValidating = true; var validationContext = (ValidationContext)ValidationContext; var changes = new List <ValidationContextChange>(); bool hasErrors = HasErrors; bool hasWarnings = HasWarnings; var validator = Validator; if (validator != null) { validator.BeforeValidation(this, validationContext.GetFieldValidations(), validationContext.GetBusinessRuleValidations()); } OnValidating(); CatchUpWithSuspendedAnnotationsValidation(); if (force && validateDataAnnotations) { var type = GetType(); // In forced mode, validate all registered properties for annotations var catelTypeInfo = PropertyDataManager.GetCatelTypeInfo(type); foreach (var propertyData in catelTypeInfo.GetCatelProperties()) { var propertyValue = GetValue(propertyData.Value); ValidatePropertyUsingAnnotations(propertyData.Key, propertyValue); } #if !WINDOWS_PHONE && !NETFX_CORE && !PCL && !NET35 // Validate non-catel properties as well for attribute validation foreach (var propertyInfo in catelTypeInfo.GetNonCatelProperties()) { var ignoredOrFailedPropertyValidations = _propertyValuesIgnoredOrFailedForValidation[type]; if (_firstAnnotationValidation) { if (AttributeHelper.IsDecoratedWithAttribute <ExcludeFromValidationAttribute>(propertyInfo.Value)) { ignoredOrFailedPropertyValidations.Add(propertyInfo.Key); } } // TODO: Should we check for annotations attributes? if (ignoredOrFailedPropertyValidations.Contains(propertyInfo.Key)) { continue; } try { var propertyValue = propertyInfo.Value.GetValue(this, null); ValidatePropertyUsingAnnotations(propertyInfo.Key, propertyValue); } 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(); 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); // In-between validations, it might be possible that users used the SetFieldValidationResult if (_internalValidationContext != null) { fieldValidationResults.AddRange(_internalValidationContext.GetFieldValidations()); } OnValidatedFields(); if (validator != null) { validator.AfterValidateFields(this, fieldValidationResults); } #endregion #region Business rules if (validator != null) { validator.BeforeValidateBusinessRules(this, validationContext.GetBusinessRuleValidations()); } OnValidatingBusinessRules(); if (validator != null) { validator.ValidateBusinessRules(this, businessRuleValidationResults); } ValidateBusinessRules(businessRuleValidationResults); // In-between validations, it might be possible that users used the SetBusinessRuleValidationResult if (_internalValidationContext != null) { businessRuleValidationResults.AddRange(_internalValidationContext.GetBusinessRuleValidations()); } OnValidatedBusinessRules(); if (validator != null) { validator.AfterValidateBusinessRules(this, businessRuleValidationResults); } #endregion if (validator != null) { validator.Validate(this, validationContext); } IsValidated = true; // Clear internal validation _internalValidationContext = new ValidationContext(); changes = validationContext.SynchronizeWithContext(new ValidationContext(fieldValidationResults, businessRuleValidationResults)); } } OnValidated(); 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 (HasWarnings != hasWarnings) { RaisePropertyChanged("HasWarnings"); } if (HasErrors != hasErrors) { RaisePropertyChanged("HasErrors"); } #endregion IsValidating = false; }