/// <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> /// Restores the backup to the object. /// </summary> public void RestoreBackup() { Dictionary <string, object> oldPropertyValues = null; using (var stream = new MemoryStream(_propertyValuesBackup)) { try { var serializer = SerializationFactory.GetXmlSerializer(); var properties = serializer.DeserializeMembers(_object.GetType(), stream); oldPropertyValues = properties.ToDictionary(property => property.Name, property => property.Value); } catch (Exception ex) { Log.Error(ex, "Failed to deserialize the data for backup, which is weird. However, for Silverlight, Windows Phone and Windows 8 there is no other option"); } } if (oldPropertyValues == null) { return; } foreach (KeyValuePair <string, object> propertyValue in oldPropertyValues) { if (PropertyDataManager.IsPropertyRegistered(_object.GetType(), propertyValue.Key)) { // Set value so the PropertyChanged event is invoked _object.SetValue(propertyValue.Key, propertyValue.Value); } } _object.IsDirty = (bool)_objectValuesBackup[IsDirty]; }
/// <summary> /// Reads the value from the XML node. /// </summary> /// <param name="reader">The reader.</param> /// <param name="propertyName">Name of the property.</param> /// <exception cref="ArgumentNullException">The <paramref name="reader"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">The <paramref name="propertyName"/> is <c>null</c> or whitespace.</exception> /// <remarks>This method does not check whether the property exists. This is the responsibility of the caller.</remarks> private void ReadValueFromXmlNode(XmlReader reader, string propertyName) { Argument.IsNotNull("reader", reader); Argument.IsNotNullOrWhitespace("propertyName", propertyName); var propertyData = PropertyDataManager.GetPropertyData(GetType(), propertyName); object value = null; switch (reader.NodeType) { case XmlNodeType.Attribute: value = GetObjectFromXmlAttribute(reader, propertyData); break; case XmlNodeType.Element: value = GetObjectFromXmlElement(reader, propertyName); break; default: string error = string.Format("Xml node type '{0}' with local name '{1}' is not supported", reader.NodeType, ObjectToStringHelper.ToString(reader.LocalName)); Log.Error(error); throw new NotSupportedException(error); } if (value != null) { SetValue(propertyData, value, false, false); } }
/// <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> /// Unregisters the property. /// <para /> /// Note that the unregistration of a property applies to all models of the same type. It is not possible to /// unregister a property for a single instance of a type. /// </summary> /// <param name="modelType">Type of the model, required because it cannot be retrieved in a static context.</param> /// <param name="name">The name.</param> protected internal static void UnregisterProperty(Type modelType, string name) { Argument.IsNotNull("modelType", modelType); Argument.IsNotNullOrWhitespace("name", name); PropertyDataManager.UnregisterProperty(modelType, name); }
/// <summary> /// Initializes a specific property for this object. /// </summary> /// <param name="name">Name of the property.</param> /// <param name="type">Type of the property.</param> /// <param name="defaultValue">Default value of the property.</param> /// <param name="propertyChangedEventHandler">The property changed event handler.</param> /// <param name="isSerializable">if set to <c>true</c>, the property is serializable.</param> /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param> /// <param name="includeInBackup">if set to <c>true</c>, the property should be included in the backup when handling IEditableObject.</param> /// <param name="isModelBaseProperty">if set to <c>true</c>, the property is declared by the <see cref="ModelBase"/>.</param> /// <param name="lateRegistration">if set to <c>true</c>, the property is assumed to be registered after the official initialization.</param> /// <param name="isCalculatedProperty">if set to <c>true</c>, the property is a calculated property.</param> /// <exception cref="InvalidPropertyException">The <paramref name="name"/> is <c>null</c> or whitespace.</exception> /// <exception cref="PropertyAlreadyRegisteredException">The property is already registered.</exception> private void InitializeProperty(string name, Type type, object defaultValue, EventHandler <AdvancedPropertyChangedEventArgs> propertyChangedEventHandler, bool isSerializable, bool includeInSerialization, bool includeInBackup, bool isModelBaseProperty, bool lateRegistration, bool isCalculatedProperty) { var objectType = GetType(); if ((defaultValue is null) && !type.IsNullableType()) { throw Log.ErrorAndCreateException(msg => new PropertyNotNullableException(name, objectType), "Property '{0}' is not nullable, please provide a valid (not null) default value", name); } if (!IsPropertyRegistered(name)) { var propertyData = new PropertyData(name, type, defaultValue, propertyChangedEventHandler, isSerializable, includeInSerialization, includeInBackup, isModelBaseProperty, isCalculatedProperty); PropertyDataManager.RegisterProperty(objectType, name, propertyData); } lock (_lock) { if (!_propertyBag.IsPropertyAvailable(name)) { SetValueToPropertyBag(name, defaultValue); } } }
/// <summary> /// Gets the typed value of a specific property. /// </summary> /// <typeparam name="TValue">The type of the value.</typeparam> /// <param name="name">Name of the property.</param> /// <returns>Object value of the property.</returns> /// <exception cref="PropertyNotRegisteredException">The property is not registered.</exception> protected TValue GetValue <TValue>(string name) { Argument.IsNotNullOrEmpty("name", name); var propertyData = PropertyDataManager.GetPropertyData(GetType(), name); return(GetValue <TValue>(propertyData)); }
/// <summary> /// Gets the value of a specific property. /// </summary> /// <param name="name">Name of the property.</param> /// <returns>Object value of the property.</returns> /// <exception cref="PropertyNotRegisteredException">The property is not registered.</exception> protected internal object GetValue(string name) { Argument.IsNotNullOrEmpty("name", name); var propertyData = PropertyDataManager.GetPropertyData(GetType(), name); return(GetValue(propertyData)); }
/// <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 all the properties for this object. /// </summary> private void InitializeProperties() { var type = GetType(); var catelTypeInfo = PropertyDataManager.RegisterProperties(type); foreach (var propertyDataKeyValuePair in catelTypeInfo.GetCatelProperties()) { var propertyData = propertyDataKeyValuePair.Value; InitializeProperty(propertyData); } }
/// <summary> /// Writes the XML elements to the xml writer. /// </summary> /// <param name="writer">The writer.</param> /// <param name="type">The type.</param> private void WriteXmlElements(XmlWriter writer, Type type) { IEnumerable <KeyValuePair <string, object> > propertiesAsElements; lock (_propertyValuesLock) { propertiesAsElements = (from propertyValue in _propertyBag.GetAllProperties() where PropertyDataManager.IsPropertyNameMappedToXmlElement(type, propertyValue.Key) select propertyValue); } foreach (var propertyAsElement in propertiesAsElements) { var propertyInfo = GetPropertyInfo(propertyAsElement.Key); if (propertyInfo == null) { continue; } if (propertyAsElement.Value == null) { continue; } string serializationPropertyName = propertyAsElement.Key; serializationPropertyName = PropertyDataManager.MapPropertyNameToXmlElementName(type, serializationPropertyName); var propertyType = propertyInfo.PropertyType; var propertyTypeToSerialize = propertyAsElement.Value.GetType(); var serializer = SerializationHelper.GetDataContractSerializer(GetType(), propertyTypeToSerialize, serializationPropertyName, propertyAsElement.Value); if (propertyType != propertyTypeToSerialize) { Log.Debug("Property type for property '{0}' is '{1}' but registered as '{2}', adding type info for deserialization", propertyInfo.Name, propertyTypeToSerialize.FullName, propertyType.FullName); serializer.WriteStartObject(writer, propertyAsElement.Value); writer.WriteAttributeString("ctl", "type", null, propertyTypeToSerialize.FullName); serializer.WriteObjectContent(writer, propertyAsElement.Value); serializer.WriteEndObject(writer); } else { serializer.WriteObject(writer, propertyAsElement.Value); } } }
/// <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); }
/// <summary> /// Writes the XML attributes to the xml writer. /// </summary> /// <param name="writer">The writer.</param> /// <param name="type">The type.</param> private void WriteXmlAttributes(XmlWriter writer, Type type) { IEnumerable <KeyValuePair <string, object> > propertiesAsAttributes; lock (_propertyValuesLock) { propertiesAsAttributes = (from propertyValue in _propertyBag.GetAllProperties() where PropertyDataManager.IsPropertyNameMappedToXmlAttribute(type, propertyValue.Key) select propertyValue); } foreach (var propertyAsAttribute in propertiesAsAttributes) { var attributeName = PropertyDataManager.MapPropertyNameToXmlAttributeName(type, propertyAsAttribute.Key); writer.WriteAttributeString(attributeName, propertyAsAttribute.Value.ToString()); } }
/// <summary> /// Initializes all the properties for this object. /// </summary> private void InitializeProperties() { var type = GetType(); var catelTypeInfo = PropertyDataManager.RegisterProperties(type); foreach (var propertyDataKeyValuePair in catelTypeInfo.GetCatelProperties()) { var propertyData = propertyDataKeyValuePair.Value; InitializeProperty(propertyData); } lock (_initializedTypesLock) { // No need to check if already existing _initializedTypes.Add(type); } }
/// <summary> /// Creates a backup of the object property values. /// </summary> private void CreateBackup() { using (var stream = new MemoryStream()) { var propertiesToIgnore = (from propertyData in PropertyDataManager.GetProperties(_object.GetType()) where !propertyData.Value.IncludeInBackup select propertyData.Value.Name).ToArray(); List <PropertyValue> objectsToSerialize; lock (_object._propertyValuesLock) { objectsToSerialize = _object.ConvertDictionaryToListAndExcludeNonSerializableObjects(_object._propertyBag.GetAllProperties(), propertiesToIgnore); } #if NET var serializer = SerializationHelper.GetBinarySerializer(false); serializer.Serialize(stream, objectsToSerialize); #else // Xml backup, create serializer without using the cache since the dictionary is used for every object, and // we need a "this" object specific dictionary. var serializer = SerializationHelper.GetDataContractSerializer(GetType(), objectsToSerialize.GetType(), "backup", objectsToSerialize, false); serializer.WriteObject(stream, objectsToSerialize); _knownTypesForDeserialization = new List <Type>(); foreach (var objectToSerialize in objectsToSerialize) { if (objectToSerialize.Value != null) { _knownTypesForDeserialization.Add(objectToSerialize.Value.GetType()); } } #endif _propertyValuesBackup = stream.ToByteArray(); } _objectValuesBackup = new Dictionary <string, object>(); _objectValuesBackup.Add(IsDirty, _object.IsDirty); }
/// <summary> /// Initializes a specific property for this object. /// </summary> /// <param name="name">Name of the property.</param> /// <param name="type">Type of the property.</param> /// <param name="defaultValue">Default value of the property.</param> /// <param name="setParent">if set to <c>true</c>, the parent of the property will be set.</param> /// <param name="propertyChangedEventHandler">The property changed event handler.</param> /// <param name="isSerializable">if set to <c>true</c>, the property is serializable.</param> /// <param name="includeInSerialization">if set to <c>true</c>, the property should be included in the serialization.</param> /// <param name="includeInBackup">if set to <c>true</c>, the property should be included in the backup when handling IEditableObject.</param> /// <param name="isModelBaseProperty">if set to <c>true</c>, the property is declared by the <see cref="ModelBase"/>.</param> /// <param name="lateRegistration">if set to <c>true</c>, the property is assumed to be registered after the official initialization.</param> /// <param name="isCalculatedProperty">if set to <c>true</c>, the property is a calculated property.</param> /// <exception cref="InvalidPropertyException">The <paramref name="name"/> is <c>null</c> or whitespace.</exception> /// <exception cref="PropertyAlreadyRegisteredException">The property is already registered.</exception> private void InitializeProperty(string name, Type type, object defaultValue, bool setParent, EventHandler <AdvancedPropertyChangedEventArgs> propertyChangedEventHandler, bool isSerializable, bool includeInSerialization, bool includeInBackup, bool isModelBaseProperty, bool lateRegistration, bool isCalculatedProperty) { var objectType = GetType(); if ((defaultValue == null) && !type.IsNullableType()) { throw Log.ErrorAndCreateException(msg => new PropertyNotNullableException(name, objectType), "Property '{0}' is not nullable, please provide a valid (not null) default value", name); } lock (_initializedTypesLock) { if (!_initializedTypes.Contains(objectType) || lateRegistration) { if (!IsPropertyRegistered(name)) { var propertyData = new PropertyData(name, type, defaultValue, setParent, propertyChangedEventHandler, isSerializable, includeInSerialization, includeInBackup, isModelBaseProperty, isCalculatedProperty); PropertyDataManager.RegisterProperty(objectType, name, propertyData); #if !WINDOWS_PHONE && !NETFX_CORE && !PCL && !NET35 // Skip validation for modelbase properties if (propertyData.IsModelBaseProperty) { _propertyValuesIgnoredOrFailedForValidation[type].Add(propertyData.Name); } #endif } } } lock (_propertyValuesLock) { if (!_propertyBag.IsPropertyAvailable(name)) { SetValueFast(name, defaultValue); } } }
/// <summary> /// Restores the backup to the object. /// </summary> public void RestoreBackup() { Dictionary <string, object> oldPropertyValues = null; using (var stream = new MemoryStream(_propertyValuesBackup)) { try { var properties = new List <MemberValue>(); if (_serializer != null) { properties = _serializer.DeserializeMembers(_object.GetType(), stream, null); } oldPropertyValues = properties.ToDictionary(property => property.Name, property => property.Value); } catch (Exception ex) { Log.Error(ex, "Failed to deserialize the data for backup, which is weird"); } } if (oldPropertyValues == null) { return; } foreach (var propertyValue in oldPropertyValues) { if (PropertyDataManager.IsPropertyRegistered(_object.GetType(), propertyValue.Key)) { // Set value so the PropertyChanged event is invoked _object.SetValue(propertyValue.Key, propertyValue.Value); } } _object.IsDirty = (bool)_objectValuesBackup[nameof(ModelBase.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> /// Deserializes all the properties that are serializable on this object using the specified <see cref="ConformanceLevel"/>. /// </summary> /// <param name="data">The data containing the serialized properties.</param> /// <param name="conformanceLevel">The conformance level.</param> /// <returns>List of deserialized properties.</returns> private List <PropertyValue> DeserializeProperties(byte[] data, ConformanceLevel conformanceLevel) { using (var stream = new MemoryStream(data)) { #if DEBUG long initialStreamPos = stream.Position; var debugReader = new StreamReader(stream); string content = debugReader.ReadToEnd(); stream.Position = initialStreamPos; #endif // Make sure to include all properties of the view model, the types must be known var additionalKnownTypes = new List <Type>(); additionalKnownTypes.Add(GetType()); foreach (var property in PropertyDataManager.GetProperties(GetType())) { if (!additionalKnownTypes.Contains(property.Value.Type)) { additionalKnownTypes.Add(property.Value.Type); } } DataContractSerializer serializer = SerializationHelper.GetDataContractSerializer(GetType(), InternalSerializationType, "internal", additionalKnownTypes, false); // Use a custom reader, required to succeed var settings = new XmlReaderSettings(); settings.ConformanceLevel = conformanceLevel; settings.IgnoreComments = true; settings.IgnoreProcessingInstructions = true; settings.IgnoreWhitespace = true; var reader = XmlReader.Create(stream, settings); return((List <PropertyValue>)serializer.ReadObject(reader, false)); } }
/// <summary> /// Initializes a new instance of the <see cref="ModelEqualityComparer" /> class. /// </summary> static ModelEqualityComparer() { PropertyDataManager = PropertyDataManager.Default; }
/// <summary> /// Generates an object from its XML representation. /// </summary> /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param> void IXmlSerializable.ReadXml(XmlReader reader) { if (reader.IsEmptyElement) { return; } var type = GetType(); var settings = new XmlReaderSettings(); settings.ConformanceLevel = ConformanceLevel.Auto; settings.IgnoreComments = true; settings.IgnoreProcessingInstructions = true; settings.IgnoreWhitespace = true; var newReader = XmlReader.Create(reader, settings); //if (!string.Equals(type.Name, newReader.LocalName, StringComparison.OrdinalIgnoreCase)) //{ // if (!newReader.Read()) // { // return; // } //} bool isAtXmlRoot = string.IsNullOrEmpty(newReader.LocalName) || string.Equals(newReader.LocalName, "xml", StringComparison.OrdinalIgnoreCase); if (isAtXmlRoot) { newReader.MoveToContent(); } // Read attributes if (newReader.MoveToFirstAttribute()) { do { if (PropertyDataManager.IsXmlAttributeNameMappedToProperty(type, newReader.LocalName)) { var propertyName = PropertyDataManager.MapXmlAttributeNameToPropertyName(type, reader.LocalName); ReadValueFromXmlNode(newReader, propertyName); } } while (newReader.MoveToNextAttribute()); } // This might be the node itself or a wrapping node (in case of web services), // so check if that is true and step into child element if (string.IsNullOrEmpty(newReader.LocalName) || !PropertyDataManager.IsXmlElementNameMappedToProperty(type, newReader.LocalName)) { newReader.Read(); newReader.MoveToElement(); } while (newReader.NodeType != XmlNodeType.EndElement) { if (string.IsNullOrEmpty(reader.LocalName)) { Log.Debug("reader.LocalName is null or empty, trying to skip current node"); reader.Skip(); if (string.IsNullOrEmpty(reader.LocalName)) { Log.Warning("reader.LocalName is null or empty, cannot read empty xml tag"); continue; } } if (PropertyDataManager.IsXmlElementNameMappedToProperty(type, reader.LocalName)) { var propertyName = PropertyDataManager.MapXmlElementNameToPropertyName(type, reader.LocalName); ReadValueFromXmlNode(newReader, propertyName); } else { newReader.Skip(); } while (newReader.NodeType == XmlNodeType.Whitespace) { newReader.Skip(); } } FinishDeserialization(); }
/// <summary> /// Initializes a new instance of the <see cref="XmlNameMapper<T>"/> class. /// </summary> /// <param name="propertyDataManager">The property data manager.</param> /// <exception cref="ArgumentNullException">The <paramref name="propertyDataManager"/> is <c>null</c>.</exception> internal XmlNameMapper(PropertyDataManager propertyDataManager) { Argument.IsNotNull("propertyDataManager", propertyDataManager); _propertyDataManager = propertyDataManager; }
/// <summary> /// Initializes static members of the <see cref="PropertyDataManager" /> class. /// </summary> static PropertyDataManager() { Default = new PropertyDataManager(); }
/// <summary> /// Gets the <see cref="PropertyData"/> for the specified property. /// </summary> /// <param name="name">The name of the property.</param> /// <returns>The <see cref="PropertyData"/>.</returns> /// <exception cref="PropertyNotRegisteredException">The property is not registered.</exception> protected PropertyData GetPropertyData(string name) { return(PropertyDataManager.GetPropertyData(GetType(), name)); }
/// <summary> /// Returns whether a specific property is registered. /// </summary> /// <param name="type">The type of the object for which to check.</param> /// <param name="name">Name of the property.</param> /// <returns> /// True if the property is registered, otherwise false. /// </returns> protected static bool IsPropertyRegistered(Type type, string name) { return(PropertyDataManager.IsPropertyRegistered(type, name)); }
/// <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; }
/// <summary> /// Validates the property using data annotations. /// </summary> /// <param name="propertyName">Name of the property.</param> /// <returns><c>true</c> if no errors using data annotations are found; otherwise <c>false</c>.</returns> private bool ValidatePropertyUsingAnnotations(string propertyName) { if (!ValidateUsingDataAnnotations) { return(true); } var validationSuspensionContext = _validationSuspensionContext; if (validationSuspensionContext != null) { validationSuspensionContext.Add(propertyName); return(true); } #if !NETFX_CORE && !PCL var type = GetType(); try { if (!PropertiesNotCausingValidation[type].Contains(propertyName)) { object value = null; var handled = false; var propertyDataManager = PropertyDataManager; if (propertyDataManager.IsPropertyRegistered(type, propertyName)) { var catelPropertyData = PropertyDataManager.GetPropertyData(type, propertyName); if (catelPropertyData != null) { var propertyInfo = catelPropertyData.GetPropertyInfo(type); if (propertyInfo == null || !propertyInfo.HasPublicGetter) { PropertiesNotCausingValidation[type].Add(propertyName); return(false); } value = GetValue(catelPropertyData); handled = true; } } if (!handled) { if (!PropertyHelper.IsPublicProperty(this, propertyName)) { Log.Debug("Property '{0}' is not a public property, cannot validate non-public properties in the current platform", propertyName); PropertiesNotCausingValidation[type].Add(propertyName); return(false); } value = PropertyHelper.GetPropertyValue(this, propertyName); } if (!_dataAnnotationsValidationContext.ContainsKey(propertyName)) { _dataAnnotationsValidationContext[propertyName] = new System.ComponentModel.DataAnnotations.ValidationContext(this, null, null) { MemberName = propertyName }; } System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, _dataAnnotationsValidationContext[propertyName]); // If succeeded, clear any previous error if (_dataAnnotationValidationResults.ContainsKey(propertyName)) { _dataAnnotationValidationResults[propertyName] = null; } } } catch (System.ComponentModel.DataAnnotations.ValidationException validationException) { _dataAnnotationValidationResults[propertyName] = validationException.Message; return(false); } catch (Exception ex) { PropertiesNotCausingValidation[type].Add(propertyName); Log.Warning(ex, "Failed to validate property '{0}' via Validator (property does not exists?)", propertyName); } #endif return(true); }
/// <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) { // In forced mode, validate all registered properties for annotations foreach (var propertyData in PropertyDataManager.GetProperties(GetType())) { var propertyValue = GetValue(propertyData.Value); ValidatePropertyUsingAnnotations(propertyData.Key, propertyValue); } } 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 && !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; }