PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/ReactiveUI/Validation.cs

https://github.com/bsiegel/ReactiveUI
C# | 285 lines | 205 code | 43 blank | 37 comment | 35 complexity | f559c2353a0f9221d08bfc5e6f834771 MD5 | raw file
Possible License(s): Apache-2.0, CC-BY-SA-3.0, LGPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reactive.Linq;
  5. using System.ComponentModel;
  6. using System.ComponentModel.DataAnnotations;
  7. using System.Reactive.Subjects;
  8. using System.Reflection;
  9. using System.Runtime.Serialization;
  10. namespace ReactiveUI
  11. {
  12. /// <summary>
  13. ///
  14. /// </summary>
  15. [DataContract]
  16. public class ReactiveValidatedObject : ReactiveObject, IDataErrorInfo
  17. {
  18. /// <summary>
  19. ///
  20. /// </summary>
  21. public ReactiveValidatedObject()
  22. {
  23. this.Changing.Subscribe(x => {
  24. if (x.Sender != this) {
  25. return;
  26. }
  27. if (_validationCache.ContainsKey(x.PropertyName)) {
  28. _validationCache.Remove(x.PropertyName);
  29. }
  30. });
  31. _validatedPropertyCount = new Lazy<int>(() => {
  32. lock(allValidatedProperties) {
  33. return allValidatedProperties.Get(this.GetType()).Count;
  34. }
  35. });
  36. }
  37. [IgnoreDataMember]
  38. public string Error {
  39. get { return null; }
  40. }
  41. [IgnoreDataMember]
  42. public string this[string columnName] {
  43. get {
  44. string ret;
  45. if (_validationCache.TryGetValue(columnName, out ret)) {
  46. return ret;
  47. }
  48. this.Log().Debug("Checking {0:X}.{1}...", this.GetHashCode(), columnName);
  49. ret = getPropertyValidationError(columnName);
  50. this.Log().Debug("Validation result: {0}", ret);
  51. _validationCache[columnName] = ret;
  52. _ValidationObservable.OnNext(new ObservedChange<object, bool>() {
  53. Sender = this, PropertyName = columnName, Value = (ret != null)
  54. });
  55. return ret;
  56. }
  57. }
  58. public bool IsObjectValid()
  59. {
  60. if (_validationCache.Count == _validatedPropertyCount.Value) {
  61. //return _validationCache.Values.All(x => x == null);
  62. foreach(var v in _validationCache.Values) {
  63. if (v != null) {
  64. return false;
  65. }
  66. }
  67. return true;
  68. }
  69. IEnumerable<string> allProps;
  70. lock (allValidatedProperties) {
  71. allProps = allValidatedProperties.Get(GetType()).Keys;
  72. };
  73. //return allProps.All(x => this[x] == null);
  74. foreach(var v in allProps) {
  75. if (this[v] != null) {
  76. return false;
  77. }
  78. }
  79. return true;
  80. }
  81. protected void InvalidateValidationCache()
  82. {
  83. _validationCache.Clear();
  84. }
  85. [IgnoreDataMember]
  86. readonly Subject<IObservedChange<object, bool>> _ValidationObservable = new Subject<IObservedChange<object, bool>>();
  87. [IgnoreDataMember]
  88. public IObservable<IObservedChange<object, bool>> ValidationObservable {
  89. get { return _ValidationObservable; }
  90. }
  91. [IgnoreDataMember]
  92. readonly Lazy<int> _validatedPropertyCount;
  93. [IgnoreDataMember]
  94. readonly Dictionary<string, string> _validationCache = new Dictionary<string, string>();
  95. static readonly MemoizingMRUCache<Type, Dictionary<string, PropertyExtraInfo>> allValidatedProperties =
  96. new MemoizingMRUCache<Type, Dictionary<string, PropertyExtraInfo>>((x, _) =>
  97. PropertyExtraInfo.CreateFromType(x).ToDictionary(k => k.PropertyName, v => v),
  98. 5);
  99. string getPropertyValidationError(string propName)
  100. {
  101. PropertyExtraInfo pei;
  102. lock (allValidatedProperties) {
  103. if (!allValidatedProperties.Get(this.GetType()).TryGetValue(propName, out pei)) {
  104. return null;
  105. }
  106. }
  107. foreach(var v in pei.ValidationAttributes) {
  108. try {
  109. var ctx = new ValidationContext(this, null, null) {MemberName = propName};
  110. var getter = Reflection.GetValueFetcherForProperty(pei.Type, propName);
  111. v.Validate(getter(this), ctx);
  112. } catch(Exception ex) {
  113. this.Log().Info("{0:X}.{1} failed validation: {2}",
  114. this.GetHashCode(), propName, ex.Message);
  115. return ex.Message;
  116. }
  117. }
  118. return null;
  119. }
  120. }
  121. internal class PropertyExtraInfo : IComparable
  122. {
  123. string _typeFullName;
  124. Type _Type;
  125. public Type Type {
  126. get { return _Type; }
  127. set { _Type = value; _typeFullName = value.FullName; }
  128. }
  129. public string PropertyName { get; set; }
  130. public ValidationAttribute[] ValidationAttributes { get; set; }
  131. public static PropertyExtraInfo CreateFromTypeAndName(Type type, string propertyName, bool nullOnEmptyValidationAttrs = false)
  132. {
  133. object[] attrs;
  134. var pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
  135. if (pi == null) {
  136. throw new ArgumentException("Property not found on type");
  137. }
  138. attrs = pi.GetCustomAttributes(typeof (ValidationAttribute), true) ?? new ValidationAttribute[0];
  139. if (nullOnEmptyValidationAttrs && attrs.Length == 0) {
  140. return null;
  141. }
  142. return new PropertyExtraInfo() {
  143. Type = type,
  144. PropertyName = propertyName,
  145. ValidationAttributes = attrs.Cast<ValidationAttribute>().ToArray(),
  146. };
  147. }
  148. public static PropertyExtraInfo[] CreateFromType(Type type)
  149. {
  150. return type.GetProperties()
  151. .Select(x => CreateFromTypeAndName(type, x.Name, true))
  152. .Where(x => x != null)
  153. .ToArray();
  154. }
  155. public int CompareTo(object obj)
  156. {
  157. var rhs = obj as PropertyExtraInfo;
  158. if (rhs == null) {
  159. throw new ArgumentException();
  160. }
  161. int ret = 0;
  162. if ((ret = this._typeFullName.CompareTo(rhs._typeFullName)) != 0) {
  163. return ret;
  164. }
  165. return this.PropertyName.CompareTo(rhs.PropertyName);
  166. }
  167. }
  168. /// <summary>
  169. ///
  170. /// </summary>
  171. public abstract class ValidationBase : ValidationAttribute
  172. {
  173. public bool AllowNull = false;
  174. public bool AllowBlanks = true;
  175. /// <summary>
  176. ///
  177. /// </summary>
  178. /// <param name="value"></param>
  179. /// <param name="validationContext"></param>
  180. /// <returns></returns>
  181. protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  182. {
  183. var ret = base.IsValid(value, validationContext);
  184. if (ret == null || ret.ErrorMessage == null)
  185. return null;
  186. return getStandardMessage(validationContext);
  187. }
  188. /// <summary>
  189. ///
  190. /// </summary>
  191. /// <param name="value"></param>
  192. /// <returns></returns>
  193. protected bool isValidViaNullOrBlank(object value)
  194. {
  195. if (value == null && !AllowNull)
  196. return false;
  197. string s = value as string;
  198. return !(s != null && !AllowBlanks && String.IsNullOrWhiteSpace(s));
  199. }
  200. /// <summary>
  201. ///
  202. /// </summary>
  203. /// <param name="value"></param>
  204. /// <param name="ctx"></param>
  205. /// <returns></returns>
  206. protected ValidationResult isValidViaNullOrBlank(object value, ValidationContext ctx)
  207. {
  208. if (isValidViaNullOrBlank(value))
  209. return null;
  210. return new ValidationResult(String.Format("{0} is blank",
  211. ctx.DisplayName ?? "The value"));
  212. }
  213. /// <summary>
  214. ///
  215. /// </summary>
  216. /// <param name="ctx"></param>
  217. /// <returns></returns>
  218. protected virtual ValidationResult getStandardMessage(ValidationContext ctx)
  219. {
  220. return new ValidationResult(ErrorMessage ??
  221. String.Format("{0} is incorrect", ctx.DisplayName ?? "The value"));
  222. }
  223. }
  224. /// <summary>
  225. ///
  226. /// </summary>
  227. public class ValidatesViaMethodAttribute : ValidationBase
  228. {
  229. public string Name;
  230. protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  231. {
  232. var is_blank = isValidViaNullOrBlank(value, validationContext);
  233. if (is_blank != null)
  234. return is_blank;
  235. string func = Name ?? String.Format("Is{0}Valid", validationContext.MemberName);
  236. var mi = validationContext.ObjectType.GetMethod(func, BindingFlags.Public | BindingFlags.Instance);
  237. bool result = (bool)mi.Invoke(validationContext.ObjectInstance, new[] { value });
  238. return result ? null : getStandardMessage(validationContext);
  239. }
  240. }
  241. }
  242. // vim: tw=120 ts=4 sw=4 et :