├── .gitignore ├── DataAnnotationsValidator ├── .editorconfig ├── DataAnnotationsValidator.Tests │ ├── Child.cs │ ├── ClassWithDictionary.cs │ ├── ClassWithNullableEnumeration.cs │ ├── DataAnnotationsValidator.Tests.csproj │ ├── DataAnnotationsValidatorTests.cs │ ├── GrandChild.cs │ ├── Parent.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SaveValidationContextAttribute.cs │ └── TestPageModel.cs ├── DataAnnotationsValidator.sln ├── DataAnnotationsValidator │ ├── DataAnnotationsValidator.cs │ ├── DataAnnotationsValidator.csproj │ ├── IDataAnnotationsValidator.cs │ ├── ObjectExtensions.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── SkipRecursiveValidation.cs └── DataAnnotationsValidatorRecursive.snk ├── License.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.user 3 | *.suo 4 | bin 5 | obj 6 | *resharper* 7 | *.userprefs 8 | testrunner* 9 | TestResult.xml 10 | DataAnnotationsValidator/packages* 11 | DataAnnotationsValidator/.vs* -------------------------------------------------------------------------------- /DataAnnotationsValidator/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | indent_style = space 3 | indent_size = 4 4 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/Child.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DataAnnotationsValidator.Tests 5 | { 6 | public class Child : IValidatableObject 7 | { 8 | [Required(ErrorMessage = "Child Parent is required")] 9 | public Parent Parent { get; set; } 10 | 11 | [Required(ErrorMessage = "Child PropertyA is required")] 12 | [Range(0, 10, ErrorMessage = "Child PropertyA not within range")] 13 | public int? PropertyA { get; set; } 14 | 15 | [Required(ErrorMessage = "Child PropertyB is required")] 16 | [Range(0, 10, ErrorMessage = "Child PropertyB not within range")] 17 | public int? PropertyB { get; set; } 18 | 19 | public IEnumerable GrandChildren { get; set; } 20 | 21 | [SaveValidationContext] 22 | public bool HasNoRealValidation { get; set; } 23 | 24 | public IEnumerable Validate(ValidationContext validationContext) 25 | { 26 | if (PropertyA.HasValue && PropertyB.HasValue && (PropertyA + PropertyB > 10)) 27 | yield return new ValidationResult("Child PropertyA and PropertyB cannot add up to more than 10"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/ClassWithDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DataAnnotationsValidator.Tests 4 | { 5 | public class ClassWithDictionary 6 | { 7 | public List> Objects { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/ClassWithNullableEnumeration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DataAnnotationsValidator.Tests 4 | { 5 | public class ClassWithNullableEnumeration 6 | { 7 | public List Objects { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/DataAnnotationsValidator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8.0.30703 4 | {019E325C-9923-4643-8834-1A301F148093} 5 | net9.0 6 | ..\ 7 | DataAnnotationsValidator.Tests 8 | DataAnnotationsValidator.Tests 9 | Copyright © 2011 10 | bin\$(Configuration)\ 11 | 12 | 13 | full 14 | 15 | 16 | pdbonly 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/DataAnnotationsValidatorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace DataAnnotationsValidator.Tests 7 | { 8 | [TestFixture] 9 | public class DataAnnotationsValidatorTests 10 | { 11 | private IDataAnnotationsValidator _validator; 12 | 13 | [SetUp] 14 | public void Setup() 15 | { 16 | SaveValidationContextAttribute.SavedContexts.Clear(); 17 | _validator = new DataAnnotationsValidator(); 18 | } 19 | 20 | [Test] 21 | public void TryValidateObject_on_valid_parent_returns_no_errors() 22 | { 23 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 24 | var validationResults = new List(); 25 | 26 | var result = _validator.TryValidateObject(parent, validationResults); 27 | 28 | Assert.IsTrue(result); 29 | Assert.AreEqual(0, validationResults.Count); 30 | } 31 | 32 | [Test] 33 | public void TryValidateObject_when_missing_required_properties_returns_errors() 34 | { 35 | var parent = new Parent { PropertyA = null, PropertyB = null }; 36 | var validationResults = new List(); 37 | 38 | var result = _validator.TryValidateObject(parent, validationResults); 39 | 40 | Assert.IsFalse(result); 41 | Assert.AreEqual(2, validationResults.Count); 42 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "Parent PropertyA is required")); 43 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "Parent PropertyB is required")); 44 | } 45 | 46 | [Test] 47 | public void TryValidateObject_calls_IValidatableObject_method() 48 | { 49 | var parent = new Parent { PropertyA = 5, PropertyB = 6 }; 50 | var validationResults = new List(); 51 | 52 | var result = _validator.TryValidateObject(parent, validationResults); 53 | 54 | Assert.IsFalse(result); 55 | Assert.AreEqual(1, validationResults.Count); 56 | Assert.AreEqual("Parent PropertyA and PropertyB cannot add up to more than 10", validationResults[0].ErrorMessage); 57 | } 58 | 59 | [Test] 60 | public void TryValidateObjectRecursive_returns_errors_when_child_class_has_invalid_properties() 61 | { 62 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 63 | parent.Child = new Child { Parent = parent, PropertyA = null, PropertyB = 5 }; 64 | var validationResults = new List(); 65 | 66 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 67 | 68 | Assert.IsFalse(result); 69 | Assert.AreEqual(1, validationResults.Count); 70 | Assert.AreEqual("Child PropertyA is required", validationResults[0].ErrorMessage); 71 | } 72 | 73 | [Test] 74 | public void TryValidateObjectRecursive_ignored_errors_when_child_class_has_SkipRecursiveValidationProperty() 75 | { 76 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 77 | parent.Child = new Child { Parent = parent, PropertyA = 1, PropertyB = 1 }; 78 | parent.SkippedChild = new Child { PropertyA = null, PropertyB = 1 }; 79 | var validationResults = new List(); 80 | 81 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 82 | 83 | Assert.IsTrue(result); 84 | } 85 | 86 | [Test] 87 | public void TryValidateObjectRecursive_calls_IValidatableObject_method_on_child_class() 88 | { 89 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 90 | parent.Child = new Child { Parent = parent, PropertyA = 5, PropertyB = 6 }; 91 | var validationResults = new List(); 92 | 93 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 94 | 95 | Assert.IsFalse(result); 96 | Assert.AreEqual(1, validationResults.Count); 97 | Assert.AreEqual("Child PropertyA and PropertyB cannot add up to more than 10", validationResults[0].ErrorMessage); 98 | } 99 | 100 | [Test] 101 | public void TryValidateObjectRecursive_returns_errors_when_grandchild_class_has_invalid_properties() 102 | { 103 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 104 | parent.Child = new Child { Parent = parent, PropertyA = 1, PropertyB = 1 }; 105 | parent.Child.GrandChildren = new[] { new GrandChild { PropertyA = 11, PropertyB = 11 } }; 106 | var validationResults = new List(); 107 | 108 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 109 | 110 | Assert.IsFalse(result); 111 | Assert.AreEqual(2, validationResults.Count); 112 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "GrandChild PropertyA not within range")); 113 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "GrandChild PropertyB not within range")); 114 | } 115 | 116 | [Test] 117 | public void TryValidateObjectRecursive_passes_validation_context_items_to_all_validation_calls() 118 | { 119 | var parent = new Parent(); 120 | parent.Child = new Child(); 121 | parent.Child.GrandChildren = new[] { new GrandChild() }; 122 | var validationResults = new List(); 123 | 124 | var contextItems = new Dictionary { { "key", 12345 } }; 125 | 126 | _validator.TryValidateObjectRecursive(parent, validationResults, contextItems); 127 | 128 | Assert.AreEqual(3, SaveValidationContextAttribute.SavedContexts.Count, "Test expects 3 validated properties in the object graph to have a SaveValidationContextAttribute"); 129 | Assert.That(SaveValidationContextAttribute.SavedContexts.Select(c => c.Items).All(items => items["key"] == contextItems["key"])); 130 | } 131 | 132 | [Test] 133 | public void TryValidateObject_calls_grandchild_IValidatableObject_method() 134 | { 135 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 136 | parent.Child = new Child { Parent = parent, PropertyA = 1, PropertyB = 1 }; 137 | parent.Child.GrandChildren = new[] { new GrandChild { PropertyA = 5, PropertyB = 6 } }; 138 | var validationResults = new List(); 139 | 140 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 141 | 142 | Assert.IsFalse(result); 143 | Assert.AreEqual(1, validationResults.Count); 144 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "GrandChild PropertyA and PropertyB cannot add up to more than 10")); 145 | } 146 | 147 | [Test] 148 | public void TryValidateObject_includes_errors_from_all_objects() 149 | { 150 | var parent = new Parent { PropertyA = 5, PropertyB = 6 }; 151 | parent.Child = new Child { Parent = parent, PropertyA = 5, PropertyB = 6 }; 152 | parent.Child.GrandChildren = new[] { new GrandChild { PropertyA = 5, PropertyB = 6 } }; 153 | var validationResults = new List(); 154 | 155 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 156 | 157 | Assert.IsFalse(result); 158 | Assert.AreEqual(3, validationResults.Count); 159 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "Parent PropertyA and PropertyB cannot add up to more than 10")); 160 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "Child PropertyA and PropertyB cannot add up to more than 10")); 161 | Assert.AreEqual(1, validationResults.ToList().Count(x => x.ErrorMessage == "GrandChild PropertyA and PropertyB cannot add up to more than 10")); 162 | } 163 | 164 | [Test] 165 | public void TryValidateObject_modifies_membernames_for_nested_properties() 166 | { 167 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 168 | parent.Child = new Child { Parent = parent, PropertyA = null, PropertyB = 5 }; 169 | var validationResults = new List(); 170 | 171 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 172 | 173 | Assert.IsFalse(result); 174 | Assert.AreEqual(1, validationResults.Count); 175 | Assert.AreEqual("Child PropertyA is required", validationResults[0].ErrorMessage); 176 | Assert.AreEqual("Child.PropertyA", validationResults[0].MemberNames.First()); 177 | } 178 | 179 | [Test] 180 | public void TryValidateObject_object_with_dictionary_does_not_fail() 181 | { 182 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 183 | var classWithDictionary = new ClassWithDictionary 184 | { 185 | Objects = new List> 186 | { 187 | new Dictionary 188 | { 189 | { "key", 190 | new Child 191 | { 192 | Parent = parent, 193 | PropertyA = 1, 194 | PropertyB = 2 195 | } 196 | } 197 | } 198 | } 199 | }; 200 | var validationResults = new List(); 201 | 202 | var result = _validator.TryValidateObjectRecursive(classWithDictionary, validationResults); 203 | 204 | Assert.IsTrue(result); 205 | Assert.IsEmpty(validationResults); 206 | } 207 | 208 | [Test] 209 | public void TryValidateObject_object_with_null_enumeration_values_does_not_fail() 210 | { 211 | var parent = new Parent { PropertyA = 1, PropertyB = 1 }; 212 | var classWithNullableEnumeration = new ClassWithNullableEnumeration 213 | { 214 | Objects = new List 215 | { 216 | null, 217 | new Child 218 | { 219 | Parent = parent, 220 | PropertyA = 1, 221 | PropertyB = 2 222 | } 223 | } 224 | }; 225 | var validationResults = new List(); 226 | 227 | var result = _validator.TryValidateObjectRecursive(classWithNullableEnumeration, validationResults); 228 | 229 | Assert.IsTrue(result); 230 | Assert.IsEmpty(validationResults); 231 | } 232 | 233 | [Test] 234 | public void TryValidateObjectRecursive_can_validate_page_model() 235 | { 236 | var parent = new TestPageModel(); 237 | var validationResults = new List(); 238 | 239 | var result = _validator.TryValidateObjectRecursive(parent, validationResults); 240 | 241 | Assert.IsTrue(result); 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/GrandChild.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DataAnnotationsValidator.Tests 5 | { 6 | public class GrandChild : IValidatableObject 7 | { 8 | [Required] 9 | [Range(0, 10, ErrorMessage = "GrandChild PropertyA not within range")] 10 | public int? PropertyA { get; set; } 11 | 12 | [Required] 13 | [Range(0, 10, ErrorMessage = "GrandChild PropertyB not within range")] 14 | public int? PropertyB { get; set; } 15 | 16 | [SaveValidationContext] 17 | public bool HasNoRealValidation { get; set; } 18 | 19 | public IEnumerable Validate(ValidationContext validationContext) 20 | { 21 | if (PropertyA.HasValue && PropertyB.HasValue && (PropertyA + PropertyB > 10)) 22 | yield return new ValidationResult("GrandChild PropertyA and PropertyB cannot add up to more than 10"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/Parent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DataAnnotationsValidator.Tests 5 | { 6 | public class Parent : IValidatableObject 7 | { 8 | [Required(ErrorMessage = "Parent PropertyA is required")] 9 | [Range(0, 10, ErrorMessage = "Parent PropertyA not within range")] 10 | public int? PropertyA { get; set; } 11 | 12 | [Required(ErrorMessage = "Parent PropertyB is required")] 13 | [Range(0, 10, ErrorMessage = "Parent PropertyB not within range")] 14 | public int? PropertyB { get; set; } 15 | 16 | public Child Child { get; set; } 17 | 18 | [SkipRecursiveValidation] 19 | public Child SkippedChild { get; set; } 20 | 21 | [SaveValidationContext] 22 | public bool HasNoRealValidation { get; set; } 23 | 24 | IEnumerable IValidatableObject.Validate(ValidationContext validationContext) 25 | { 26 | if (PropertyA.HasValue && PropertyB.HasValue && (PropertyA + PropertyB > 10)) 27 | yield return new ValidationResult("Parent PropertyA and PropertyB cannot add up to more than 10"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // Setting ComVisible to false makes the types in this assembly not visible 4 | // to COM components. If you need to access a type in this assembly from 5 | // COM, set the ComVisible attribute to true on that type. 6 | [assembly: ComVisible(false)] 7 | 8 | // The following GUID is for the ID of the typelib if this project is exposed to COM 9 | [assembly: Guid("220ab1f3-e5a1-47c1-8b8d-33f94f8165dd")] 10 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/SaveValidationContextAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DataAnnotationsValidator.Tests 5 | { 6 | public class SaveValidationContextAttribute: ValidationAttribute 7 | { 8 | public static IList SavedContexts = new List(); 9 | 10 | protected override ValidationResult IsValid(object value, ValidationContext validationContext) 11 | { 12 | SavedContexts.Add(validationContext); 13 | return ValidationResult.Success; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.Tests/TestPageModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace DataAnnotationsValidator.Tests; 4 | 5 | public class TestPageModel : PageModel; 6 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.14.36127.28 d17.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAnnotationsValidator.Tests", "DataAnnotationsValidator.Tests\DataAnnotationsValidator.Tests.csproj", "{019E325C-9923-4643-8834-1A301F148093}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAnnotationsValidator", "DataAnnotationsValidator\DataAnnotationsValidator.csproj", "{218B0C2C-19AD-4C7B-B583-840FF755A76D}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{01E8FDFE-435E-4F24-AD27-D4DAA621523B}" 11 | ProjectSection(SolutionItems) = preProject 12 | DataAnnotationsValidatorRecursive.snk = DataAnnotationsValidatorRecursive.snk 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {019E325C-9923-4643-8834-1A301F148093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {019E325C-9923-4643-8834-1A301F148093}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {019E325C-9923-4643-8834-1A301F148093}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {019E325C-9923-4643-8834-1A301F148093}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {218B0C2C-19AD-4C7B-B583-840FF755A76D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {218B0C2C-19AD-4C7B-B583-840FF755A76D}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {218B0C2C-19AD-4C7B-B583-840FF755A76D}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {218B0C2C-19AD-4C7B-B583-840FF755A76D}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/DataAnnotationsValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace DataAnnotationsValidator 8 | { 9 | public class DataAnnotationsValidator : IDataAnnotationsValidator 10 | { 11 | public bool TryValidateObject(object obj, ICollection results, IDictionary validationContextItems = null) 12 | { 13 | return Validator.TryValidateObject(obj, new ValidationContext(obj, null, validationContextItems), results, true); 14 | } 15 | 16 | public bool TryValidateObjectRecursive(T obj, List results, IDictionary validationContextItems = null) 17 | { 18 | return TryValidateObjectRecursive(obj, results, new HashSet(), validationContextItems); 19 | } 20 | 21 | private bool TryValidateObjectRecursive(T obj, List results, ISet validatedObjects, IDictionary validationContextItems = null) 22 | { 23 | //short-circuit to avoid infinit loops on cyclical object graphs 24 | if (validatedObjects.Contains(obj)) 25 | { 26 | return true; 27 | } 28 | 29 | validatedObjects.Add(obj); 30 | bool result = TryValidateObject(obj, results, validationContextItems); 31 | 32 | var properties = obj.GetType().GetProperties().Where(prop => prop.CanRead 33 | && !prop.GetCustomAttributes(typeof(SkipRecursiveValidation), false).Any() 34 | && prop.GetIndexParameters().Length == 0).ToList(); 35 | 36 | foreach (var property in properties) 37 | { 38 | if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) continue; 39 | 40 | var value = obj.GetPropertyValue(property.Name); 41 | 42 | if (value == null) continue; 43 | 44 | var asEnumerable = value as IEnumerable; 45 | if (asEnumerable != null) 46 | { 47 | foreach (var enumObj in asEnumerable) 48 | { 49 | if ( enumObj != null) { 50 | var nestedResults = new List(); 51 | if (!TryValidateObjectRecursive(enumObj, nestedResults, validatedObjects, validationContextItems)) 52 | { 53 | result = false; 54 | foreach (var validationResult in nestedResults) 55 | { 56 | PropertyInfo property1 = property; 57 | results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x))); 58 | } 59 | }; 60 | } 61 | } 62 | } 63 | else 64 | { 65 | var nestedResults = new List(); 66 | if (!TryValidateObjectRecursive(value, nestedResults, validatedObjects, validationContextItems)) 67 | { 68 | result = false; 69 | foreach (var validationResult in nestedResults) 70 | { 71 | PropertyInfo property1 = property; 72 | results.Add(new ValidationResult(validationResult.ErrorMessage, validationResult.MemberNames.Select(x => property1.Name + '.' + x))); 73 | } 74 | }; 75 | } 76 | } 77 | 78 | return result; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/DataAnnotationsValidator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8.0.30703 4 | {218B0C2C-19AD-4C7B-B583-840FF755A76D} 5 | netstandard2.0 6 | true 7 | DataAnnotationsValidator 8 | 2.3.0.0 9 | Mike Reust 10 | en-US 11 | License.md 12 | README.md 13 | Mike Reust 14 | https://github.com/reustmd/DataAnnotationsValidatorRecursive 15 | DataAnnotation validation validator 16 | DataAnnotationsValidator 17 | The helper will recursively traverse your object graph and invoke validation against DataAnnotations. This originated from following Stackoverflow answer: http://stackoverflow.com/a/8090614/605586 18 | TryValidateObjectRecursive 19 | TryValidateObjectRecursive 20 | Allows scanning object graph and validates every object using .NET standatd System.ComponentModel.DataAnnotations.Validator 21 | Copyright © 2018 22 | 2.3.0 23 | 2.3.0.0 24 | 2.3.0.0 25 | bin\$(Configuration)\ 26 | true 27 | Targeting .NET Standard 2.0; fixing recursively validating PageModel which throws System.Reflection.AmbiguousMatchException for ActionDescriptor property; 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | full 36 | 37 | 38 | pdbonly 39 | 40 | 41 | true 42 | 43 | 44 | ..\DataAnnotationsValidatorRecursive.snk 45 | 46 | 47 | 48 | DataAnnotationsValidatorRecursive.snk 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/IDataAnnotationsValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DataAnnotationsValidator 5 | { 6 | public interface IDataAnnotationsValidator 7 | { 8 | bool TryValidateObject(object obj, ICollection results, IDictionary validationContextItems = null); 9 | bool TryValidateObjectRecursive(T obj, List results, IDictionary validationContextItems = null); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace DataAnnotationsValidator 4 | { 5 | public static class ObjectExtensions 6 | { 7 | public static object GetPropertyValue(this object o, string propertyName) 8 | { 9 | object objValue = string.Empty; 10 | 11 | // First return only the property declared on the object's type. 12 | var propertyInfo = o.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); 13 | 14 | if (propertyInfo == null) 15 | { 16 | // fallback: try to get the property from the full hierarchy (may throw if ambiguous) 17 | propertyInfo = o.GetType().GetProperty(propertyName); 18 | } 19 | 20 | if (propertyInfo != null) 21 | objValue = propertyInfo.GetValue(o, null); 22 | 23 | return objValue; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // Setting ComVisible to false makes the types in this assembly not visible 4 | // to COM components. If you need to access a type in this assembly from 5 | // COM, set the ComVisible attribute to true on that type. 6 | [assembly: ComVisible(false)] 7 | 8 | // The following GUID is for the ID of the typelib if this project is exposed to COM 9 | [assembly: Guid("9e99ddf6-daa3-455c-b9af-126b2d740af6")] 10 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidator/SkipRecursiveValidation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DataAnnotationsValidator 4 | { 5 | public class SkipRecursiveValidation : Attribute 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /DataAnnotationsValidator/DataAnnotationsValidatorRecursive.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reustmd/DataAnnotationsValidatorRecursive/71ed27cdd24569aee7d9ac8b770589c7f4d606bd/DataAnnotationsValidator/DataAnnotationsValidatorRecursive.snk -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mike Reust 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DataAnnotationsValidatorRecursive 2 | 3 | The helper will recursively traverse your object graph and invoke validation against DataAnnotations. 4 | This originated from following Stackoverflow answer: http://stackoverflow.com/a/8090614/605586 5 | 6 | ## Installation 7 | 8 | Available as NuGet-Package `DataAnnotationsValidator`: 9 | 10 | Install-Package DataAnnotationsValidator 11 | 12 | ## Usage 13 | 14 | See file `DataAnnotationsValidator/DataAnnotationsValidator.Tests/DataAnnotationsValidatorTests.cs` 15 | 16 | Short example: 17 | 18 | var validator = new DataAnnotationsValidator.DataAnnotationsValidator(); 19 | var validationResults = new List(); 20 | 21 | validator.TryValidateObjectRecursive(modelToValidate, validationResults); 22 | --------------------------------------------------------------------------------