/
ValidateObjectAttribute.cs
139 lines (118 loc) · 4.36 KB
/
ValidateObjectAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HermaFx;
using HermaFx.Reflection;
namespace HermaFx.DataAnnotations
{
public class ValidateObjectAttribute : ValidationAttribute
{
private static readonly string ITEMS_KEY = typeof(ValidateObjectAttribute).Name + "::Seen";
private static HashSet<object> TryGetSeenHashFrom(ValidationContext context)
{
if (context.Items != null && !context.Items.ContainsKey(ITEMS_KEY))
{
context.Items[ITEMS_KEY] = new HashSet<object>();
}
return context.Items[ITEMS_KEY] as HashSet<object>;
}
private IEnumerable<ValidationResult> ValidateClass(object value, ValidationContext context)
{
var results = new List<ValidationResult>();
var attributes = value.GetType().GetCustomAttributes<ValidationAttribute>(true);
Validator.TryValidateValue(value, context, results, attributes);
return results;
}
private IEnumerable<ValidationResult> ValidateProperties(object value, ValidationContext context)
{
if (value == null) return Enumerable.Empty<ValidationResult>();
var type = value.GetType();
var results = new List<ValidationResult>();
// Now go through the properties and find any that are complex enough that they might
// have their own validation requirements. Recurse into each value that we find.
foreach (var property in type.GetProperties())
{
// Ignore properties with no getter.
if (!property.CanRead)
continue;
// Ignore indexed properties as there is no way to know how to enumerate them on
// their own. Classes should implement IEnumberable if they want these accessed
// anyway.
if (property.GetIndexParameters().Length > 0)
continue;
// Get the value assigned to the property and recurse into it
var value2 = property.GetValue(value, null);
var reqattr = property.GetCustomAttribute<RequiredAttribute>();
var newctx = new ValidationContext(value2 ?? "", context.Items)
{
DisplayName = context.DisplayName.IfNotNull(x => string.Join(":", x, property.Name)),
MemberName = context.MemberName.IfNotNull(x => string.Join(".", x, property.Name))
};
// Let's first evaluate RequiredAttribute..
if (reqattr != null)
{
var attr = property.GetCustomAttribute<RequiredAttribute>();
var res = attr.GetValidationResult(value2, newctx);
if (res != ValidationResult.Success) results.Add(res);
}
if (value2 != null)
{
Validator.TryValidateObject(value2, newctx, results);
}
}
return results;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
context = context ?? new ValidationContext(value);
var seen = TryGetSeenHashFrom(context);
//_Log.DebugFormat("Trying to validate {0}", value.ToString());
if (value == null || seen.IfNotNull(x => x.Contains(value)))
{
return ValidationResult.Success;
}
var results = new List<ValidationResult>();
if (value is string || value.GetType().IsValueType)
{
//throw new InvalidOperationException("ValidObject cannot be applied over string or value type properties.");
return ValidationResult.Success;
}
if (value is IEnumerable)
{
var idx = 0;
var valueAsEnumerable = value as IEnumerable;
foreach (var item in valueAsEnumerable)
{
var idx2 = idx++;
var newctx = new ValidationContext(item, null, context.Items)
{
DisplayName = context.DisplayName.IfNotNull(x => string.Format("{0}[{1}]", x, idx2)),
MemberName = context.MemberName.IfNotNull(x => string.Format("{0}[{1}]", x, idx2))
};
results.AddRange(ValidateClass(item, newctx));
results.AddRange(ValidateProperties(item, newctx));
}
}
else
{
var newctx = new ValidationContext(value, null, context.Items)
{
DisplayName = context.DisplayName,
MemberName = context.MemberName
};
results.AddRange(ValidateClass(value, newctx));
results.AddRange(ValidateProperties(value, newctx));
}
if (results.Count != 0)
{
//_Log.DebugFormat("Validation failed for: {0} | {1} | {2}", validationContext.ObjectType, validationContext.DisplayName, validationContext.MemberName);
return AggregateValidationResult.CreateFor(context, results);
}
return ValidationResult.Success;
}
}
}