/Trunk/Source/Core/Core/ComponentModel/InputValidation/DataErrorNotifier.cs
C# | 453 lines | 291 code | 53 blank | 109 comment | 44 complexity | 5157c8ff40baf2aa8da9de125724b777 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, LGPL-2.1, LGPL-2.0
- #region File and License Information
- /*
- <File>
- <License Type="BSD">
- Copyright © 2009 - 2012, Outcoder. All rights reserved.
-
- This file is part of Calcium (http://calciumsdk.net).
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the <organization> nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- </License>
- <Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com"/>
- <CreationDate>2010-09-29 16:32:45Z</CreationDate>
- </File>
- */
- #endregion
-
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Threading.Tasks;
-
- using Outcoder.Reflection;
-
- namespace Outcoder.ComponentModel.InputValidation
- {
- public class DataErrorNotifier : INotifyDataErrorInfo
- {
- readonly IValidateData validator;
-
- readonly object errorsLock = new object();
- Dictionary<string, List<DataValidationError>> errorsField;
-
- readonly object propertyDictionaryLock = new object();
- readonly IDictionary<string, Func<object>> propertyDictionary
- = new Dictionary<string, Func<object>>();
-
- readonly INotifyPropertyChanged owner;
-
- /// <summary>
- /// Initializes a new instance
- /// of the <see cref="DataErrorNotifier"/> class.
- /// </summary>
- /// <param name="owner">The instance for which validation
- /// is being provided.</param>
- /// <param name="validator">The validator.</param>
- public DataErrorNotifier(INotifyPropertyChanged owner, IValidateData validator)
- {
- this.validator = ArgumentValidator.AssertNotNull(validator, "validator");
- this.owner = ArgumentValidator.AssertNotNull(owner, "owner");
-
- owner.PropertyChanged += HandleOwnerPropertyChanged;
-
- ReadValidationAttributes();
- }
-
- void ReadValidationAttributes()
- {
- #if NETFX_CORE
- var properties = owner.GetType().GetTypeInfo().DeclaredProperties;
- #else
- PropertyInfo[] properties = owner.GetType().GetProperties();
- #endif
- foreach (PropertyInfo propertyInfo in properties)
- {
- var attributes = propertyInfo.GetCustomAttributes(
- typeof(ValidateAttribute), true);
-
- if (!attributes.Any())
- {
- continue;
- }
-
- if (!propertyInfo.CanRead)
- {
- throw new InvalidOperationException(string.Format(
- "Property {0} must have a getter to be validated.",
- propertyInfo.Name));
- }
-
- /* Prevents access to internal closure warning. */
- PropertyInfo info = propertyInfo;
-
- AddValidationProperty(
- propertyInfo.Name, () => info.GetValue(owner, null));
- }
- }
-
- async void HandleOwnerPropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- if (e == null || e.PropertyName == null)
- {
- return;
- }
-
- await BeginGetPropertyErrorsFromValidator(e.PropertyName);
- }
-
- async Task<ValidationCompleteEventArgs> BeginGetPropertyErrorsFromValidator(string propertyName)
- {
- Func<object> propertyFunc;
- lock (propertyDictionaryLock)
- {
- if (!propertyDictionary.TryGetValue(propertyName, out propertyFunc))
- {
- /* No property registered with that name. */
- return new ValidationCompleteEventArgs(propertyName);
- }
- }
-
- var result = await validator.ValidateAsync(propertyName, propertyFunc());
- ProcessValidationComplete(result);
-
- return result;
- }
-
- void ProcessValidationComplete(ValidationCompleteEventArgs e)
- {
- try
- {
- if (e.Exception == null)
- {
- SetPropertyErrors(e.PropertyName, e.Errors);
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine("Unable to set property error." + ex);
- }
- }
-
- /// <summary>
- /// Adds the property to the list of known class properties,
- /// which is used, for example, when performing validation
- /// of the whole class instance.
- /// </summary>
- /// <param name="name">The name of the property.</param>
- /// <param name="property">The <c>Func</c> to
- /// retrieve the property.</param>
- public void AddValidationProperty(string name, Func<object> property)
- {
- lock (propertyDictionaryLock)
- {
- propertyDictionary[name] = property;
- }
- }
-
- public async Task ValidateAllAsync()
- {
- if (propertyDictionary == null)
- {
- return;
- }
-
- bool raiseEvent = false;
-
- foreach (KeyValuePair<string, Func<object>> pair in propertyDictionary)
- {
- string propertyName = pair.Key;
- var validateResult = await validator.ValidateAsync(propertyName, pair.Value());
- ProcessValidationComplete(validateResult);
- }
- }
-
- /// <summary>
- /// Gets the validation errors for all properties.
- /// </summary>
- /// <value>The errors.</value>
- Dictionary<string, List<DataValidationError>> Errors
- {
- get
- {
- /* Lazy instantiation. */
- if (errorsField == null)
- {
- lock (errorsLock)
- {
- if (errorsField == null)
- {
- errorsField = new Dictionary<string, List<DataValidationError>>();
- }
- }
- }
-
- return errorsField;
- }
- }
-
- #region INotifyDataErrorInfo implementation
-
- public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
-
- /// <summary>
- /// Gets the validation errors for a specified property
- /// or for the entire object.
- /// </summary>
- /// <param name="propertyName">The name of the property
- /// to retrieve validation errors for,
- /// or null or <see cref="F:System.String.Empty"/>
- /// to retrieve errors for the entire object.</param>
- /// <returns>
- /// The validation errors for the property or object.
- /// </returns>
- public IEnumerable GetErrors(string propertyName)
- {
- return GetDataValidationErrors(propertyName);
- }
-
- /// <summary>
- /// Gets a value that indicates whether the object has validation errors.
- /// </summary>
- /// <value></value>
- /// <returns><c>true</c> if the object currently has validation errors;
- /// otherwise, <c>false</c>.</returns>
- public bool HasErrors
- {
- get
- {
- lock (errorsLock)
- {
- return errorsField != null && Errors.Count > 0;
- }
- }
- }
- #endregion
-
- /// <summary>
- /// Gets the validation errors for a specified property
- /// or for the entire object.
- /// </summary>
- /// <param name="propertyName">The name of the property
- /// to retrieve validation errors for,
- /// or null or <see cref="F:System.String.Empty"/>
- /// to retrieve errors for the entire object.</param>
- /// <returns>
- /// The validation errors for the property or object.
- /// </returns>
- public IEnumerable<DataValidationError> GetDataValidationErrors(
- string propertyName)
- {
- if (string.IsNullOrEmpty(propertyName))
- {
- lock (errorsLock)
- {
- if (errorsField == null)
- {
- return new List<DataValidationError>();
- }
-
- var result = from list in errorsField.Values
- from errorInfo in list
- select errorInfo;
- return result;
- }
- }
-
- lock (errorsLock)
- {
- List<DataValidationError> propertyErrors;
- if (errorsField == null
- || !errorsField.TryGetValue(propertyName, out propertyErrors))
- {
- return new List<DataValidationError>();
- }
-
- return propertyErrors.ToList();
- }
- }
-
- /// <summary>
- /// Raises the <see cref="ErrorsChanged"/> event.
- /// </summary>
- /// <param name="property">The property
- /// for which the list of errors changed.</param>
- protected virtual void OnErrorsChanged(string property)
- {
- UISynchronizationContext.Instance.InvokeIfRequired(
- delegate
- {
- ErrorsChanged?.Invoke(this,
- new DataErrorsChangedEventArgs(property));
- });
- }
-
- /// <summary>
- /// Raises the ErrorsChanged event.
- /// </summary>
- public void RaiseErrorsChanged()
- {
- OnErrorsChanged(string.Empty);
- }
-
- /// <summary>
- /// Removes the property error from the properties list
- /// of validation errors.
- /// </summary>
- /// <param name="propertyName">Name of the property.</param>
- /// <param name="errorCode">The error code.</param>
- public void RemovePropertyError(string propertyName, int errorCode)
- {
- bool raiseEvent = false;
-
- lock (errorsLock)
- {
- if (errorsField == null)
- {
- return;
- }
-
- List<DataValidationError> list = errorsField[propertyName];
- DataValidationError dataValidationError
- = list.SingleOrDefault(e => e.Id == errorCode);
-
- if (dataValidationError != null)
- {
- list.Remove(dataValidationError);
- raiseEvent = true;
- }
- }
-
- if (raiseEvent)
- {
- OnErrorsChanged(propertyName);
- }
- }
-
- /// <summary>
- /// Adds a property error for the specified propertyName.
- /// This may produce a prompt in the UI to correct the error before proceeding.
- /// </summary>
- /// <param name="propertyName">Name of the property.</param>
- /// <param name="dataValidationError">The data validation error.</param>
- public void AddPropertyError(
- string propertyName, DataValidationError dataValidationError)
- {
- SetPropertyErrors(propertyName,
- new List<DataValidationError> { dataValidationError });
- }
-
- public void AddValidationProperty(Expression<Func<object>> expression)
- {
- PropertyInfo propertyInfo = PropertyUtility.GetPropertyInfo(expression);
- string name = propertyInfo.Name;
- #if NETFX_CORE
- MethodInfo getMethodInfo = propertyInfo.GetMethod;
- Func<object> getter = (Func<object>)getMethodInfo.CreateDelegate(
- typeof(Func<object>),
- this);
- #else
- Func<object> getter = (Func<object>)Delegate.CreateDelegate(
- typeof(Func<object>),
- this,
- propertyInfo.GetGetMethod());
- #endif
- AddValidationProperty(name, getter);
- }
-
- /// <summary>
- /// Sets the known validation errors for a property.
- /// This may produce a prompt in the UI to correct the error before proceeding.
- /// </summary>
- /// <param name="propertyName">Name of the property.</param>
- /// <param name="dataErrors">The data errors.</param>
- public void SetPropertyErrors(
- string propertyName, IEnumerable<DataValidationError> dataErrors)
- {
- ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");
-
- List<DataValidationError> list;
- bool raiseEvent = false;
- lock (errorsLock)
- {
- bool created = false;
-
- int paramErrorCount = dataErrors == null ? 0 : dataErrors.Count();
- if ((errorsField == null || errorsField.Count < 1)
- && paramErrorCount < 1)
- {
- return;
- }
-
- if (errorsField == null)
- {
- errorsField = new Dictionary<string, List<DataValidationError>>();
- created = true;
- }
-
- bool listFound = false;
- if (created ||
- !(listFound = errorsField.TryGetValue(propertyName, out list)))
- {
- list = new List<DataValidationError>();
- }
-
- if (paramErrorCount < 1)
- {
- if (listFound)
- {
- errorsField.Remove(propertyName);
- raiseEvent = true;
- }
- }
- else
- {
- var tempList = new List<DataValidationError>();
-
- foreach (var dataError in dataErrors)
- {
- if (created || list.SingleOrDefault(
- e => e.Id == dataError.Id) == null)
- {
- tempList.Add(dataError);
- raiseEvent = true;
- }
- }
-
- list.AddRange(tempList);
- errorsField[propertyName] = list;
- }
- }
-
- if (raiseEvent)
- {
- OnErrorsChanged(propertyName);
- }
- }
- }
- }
-