Support new Options attributes in the Runtime (#90275)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Thu, 10 Aug 2023 19:49:30 +0000 (12:49 -0700)
committerGitHub <noreply@github.com>
Thu, 10 Aug 2023 19:49:30 +0000 (12:49 -0700)
16 files changed:
src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/DataAnnotationValidateOptions.cs
src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj
src/libraries/Microsoft.Extensions.Options/gen/Parser.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs [deleted file]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs [deleted file]
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs
src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs

index 446f7d2..e8f4b60 100644 (file)
@@ -2,9 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.CompilerServices;
 
 namespace Microsoft.Extensions.Options
 {
@@ -52,19 +56,81 @@ namespace Microsoft.Extensions.Options
             ThrowHelper.ThrowIfNull(options);
 
             var validationResults = new List<ValidationResult>();
-            if (Validator.TryValidateObject(options, new ValidationContext(options), validationResults, validateAllProperties: true))
+            HashSet<object>? visited = null;
+            List<string>? errors = null;
+
+            if (TryValidateOptions(options, options.GetType().Name, validationResults, ref errors, ref visited))
             {
                 return ValidateOptionsResult.Success;
             }
 
-            string typeName = options.GetType().Name;
-            var errors = new List<string>();
-            foreach (ValidationResult result in validationResults)
+            Debug.Assert(errors is not null && errors.Count > 0);
+
+            return ValidateOptionsResult.Fail(errors);
+        }
+
+        [RequiresUnreferencedCode("This method on this type will walk through all properties of the passed in options object, and its type cannot be " +
+            "statically analyzed so its members may be trimmed.")]
+        private static bool TryValidateOptions(object options, string qualifiedName, List<ValidationResult> results, ref List<string>? errors, ref HashSet<object>? visited)
+        {
+            Debug.Assert(options is not null);
+
+            if (visited is not null && visited.Contains(options))
             {
-                errors.Add($"DataAnnotation validation failed for '{typeName}' members: '{string.Join(",", result.MemberNames)}' with the error: '{result.ErrorMessage}'.");
+                return true;
             }
 
-            return ValidateOptionsResult.Fail(errors);
+            results.Clear();
+
+            bool res = Validator.TryValidateObject(options, new ValidationContext(options), results, validateAllProperties: true);
+            if (!res)
+            {
+                errors ??= new List<string>();
+
+                foreach (ValidationResult result in results!)
+                {
+                    errors.Add($"DataAnnotation validation failed for '{qualifiedName}' members: '{string.Join(",", result.MemberNames)}' with the error: '{result.ErrorMessage}'.");
+                }
+            }
+
+            foreach (PropertyInfo propertyInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
+            {
+                if (propertyInfo.GetMethod is null)
+                {
+                    continue;
+                }
+
+                object? value = propertyInfo!.GetValue(options);
+
+                if (value is null)
+                {
+                    continue;
+                }
+
+                if (propertyInfo.GetCustomAttribute<ValidateObjectMembersAttribute>() is not null)
+                {
+                    visited ??= new HashSet<object>(ReferenceEqualityComparer.Instance);
+                    visited.Add(options);
+
+                    results ??= new List<ValidationResult>();
+                    res = TryValidateOptions(value, $"{qualifiedName}.{propertyInfo.Name}", results, ref errors, ref visited) && res;
+                }
+                else if (value is IEnumerable enumerable &&
+                         propertyInfo.GetCustomAttribute<ValidateEnumeratedItemsAttribute>() is not null)
+                {
+                    visited ??= new HashSet<object>(ReferenceEqualityComparer.Instance);
+                    visited.Add(options);
+                    results ??= new List<ValidationResult>();
+
+                    int index = 0;
+                    foreach (object item in enumerable)
+                    {
+                        res = TryValidateOptions(item, $"{qualifiedName}.{propertyInfo.Name}[{index++}]", results, ref errors, ref visited) && res;
+                    }
+                }
+            }
+
+            return res;
         }
     }
 }
index 031a210..0fdd978 100644 (file)
@@ -12,6 +12,7 @@
   </ItemGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+    <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ReferenceEqualityComparer.cs" />
     <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
     <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
     <Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
index c4d1da9..07ea7de 100644 (file)
@@ -276,7 +276,7 @@ namespace Microsoft.Extensions.Options.Generators
                 var memberInfo = GetMemberInfo(member, speculate, location, validatorType);
                 if (memberInfo is not null)
                 {
-                    if (member.DeclaredAccessibility != Accessibility.Public && member.DeclaredAccessibility != Accessibility.Internal)
+                    if (member.DeclaredAccessibility != Accessibility.Public)
                     {
                         Diag(DiagDescriptors.MemberIsInaccessible, member.Locations.First(), member.Name);
                         continue;
@@ -297,6 +297,8 @@ namespace Microsoft.Extensions.Options.Generators
                 case IPropertySymbol prop:
                     memberType = prop.Type;
                     break;
+
+                /* The runtime doesn't support fields validation yet. If we allow that in the future, we need to add the following code back.
                 case IFieldSymbol field:
                     if (field.AssociatedSymbol is not null)
                     {
@@ -306,6 +308,7 @@ namespace Microsoft.Extensions.Options.Generators
 
                     memberType = field.Type;
                     break;
+                */
                 default:
                     // we only care about properties and fields
                     return null;
index 756b169..b243ee5 100644 (file)
@@ -143,7 +143,7 @@ namespace __OptionValidationStaticInstances
             public class SecondModel
             {
                 [Required]
-                public string? P3;
+                public string? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -205,7 +205,7 @@ namespace __OptionValidationStaticInstances
                 public const string? P1;
 
                 [ValidateObjectMembers]
-                public static SecondModel P2 = new();
+                public static SecondModel P2 { get; set; } = new();
 
                 [ValidateEnumeratedItems]
                 public static System.Collections.Generic.IList<SecondModel>? P3 { get; set; }
@@ -217,7 +217,7 @@ namespace __OptionValidationStaticInstances
             public class SecondModel
             {
                 [Required]
-                public string? P3;
+                public string? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -226,7 +226,7 @@ namespace __OptionValidationStaticInstances
             }
         ");
 
-        Assert.Equal(4, d.Count);
+        Assert.Equal(3, d.Count);
         Assert.All(d, x => Assert.Equal(DiagDescriptors.CantValidateStaticOrConstMember.Id, x.Id));
         Assert.All(d, x => Assert.Equal(DiagnosticSeverity.Warning, x.DefaultSeverity));
     }
@@ -259,13 +259,13 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [Required]
-                public string? P1;
+                public string? P1 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P2;
+                public string? P2 { get; set; }
             }
 
             [OptionsValidator]
@@ -290,13 +290,13 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [ValidateObjectMembers(typeof(SecondValidator)]
-                public SecondModel? P1;
+                public SecondModel? P1 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P2;
+                public string? P2 { get; set; }
             }
 
             [OptionsValidator]
@@ -320,16 +320,16 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [Required]
-                public string? P1;
+                public string? P1 { get; set; }
 
                 [ValidateObjectMembers(typeof(SecondValidator)]
-                public SecondModel? P2;
+                public SecondModel? P2 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P3;
+                public string? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -358,13 +358,13 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [ValidateObjectMembers(null!)]
-                public SecondModel? P1;
+                public SecondModel? P1 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P2;
+                public string? P2 { get; set; }
             }
 
             [OptionsValidator]
@@ -389,16 +389,16 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [Required]
-                public string? P1;
+                public string? P1 { get; set; }
 
                 [ValidateObjectMembers(typeof(SecondValidator)]
-                public SecondModel? P2;
+                public SecondModel? P2 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P3;
+                public string? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -426,7 +426,7 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [Required]
-                public string P1;
+                public string P1 { get; set; }
             }
 
             [OptionsValidator]
@@ -461,15 +461,15 @@ namespace __OptionValidationStaticInstances
             {
                 [Required]
                 [ValidateObjectMembers]
-                public T? P1;
+                public T? P1 { get; set; }
 
                 [ValidateObjectMembers]
                 [Required]
-                public T[]? P2;
+                public T[]? P2 { get; set; }
 
                 [ValidateObjectMembers]
                 [Required]
-                public System.Collections.Generics.IList<T> P3 = null!;
+                public System.Collections.Generics.IList<T> P3 { get; set;} = null!;
             }
 
             [OptionsValidator]
@@ -492,19 +492,19 @@ namespace __OptionValidationStaticInstances
             {
                 [Required]
                 [ValidateObjectMembers]
-                public T? P1;
+                public T? P1 { get; set; }
 
                 [ValidateObjectMembers]
                 [Required]
-                public T[]? P2;
+                public T[]? P2 { get; set; }
 
                 [ValidateObjectMembers]
                 [Required]
-                public int[]? P3;
+                public int[]? P3 { get; set; }
 
                 [ValidateObjectMembers]
                 [Required]
-                public System.Collections.Generics.IList<T>? P4;
+                public System.Collections.Generics.IList<T>? P4 { get; set; }
             }
 
             [OptionsValidator]
@@ -528,12 +528,12 @@ namespace __OptionValidationStaticInstances
             {
                 [Required]
                 [ValidateObjectMembers]
-                public SecondModel? P1;
+                public SecondModel? P1 { get; set; }
             }
 
             public class SecondModel
             {
-                public string P2;
+                public string P2 { get; set; };
             }
 
             [OptionsValidator]
@@ -1078,13 +1078,13 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [ValidateEnumeratedItems(typeof(SecondValidator)]
-                public SecondModel[]? P1;
+                public SecondModel[]? P1 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P2;
+                public string? P2 { get; set; }
             }
 
             [OptionsValidator]
@@ -1108,13 +1108,13 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [ValidateEnumeratedItems(null!)]
-                public SecondModel[]? P1;
+                public SecondModel[]? P1 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P2;
+                public string? P2 { get; set; }
             }
 
             [OptionsValidator]
@@ -1139,16 +1139,16 @@ namespace __OptionValidationStaticInstances
             public class FirstModel
             {
                 [Required]
-                public string? P1;
+                public string? P1 { get; set; }
 
                 [ValidateEnumeratedItems(typeof(SecondValidator)]
-                public SecondModel[]? P2;
+                public SecondModel[]? P2 { get; set; }
             }
 
             public class SecondModel
             {
                 [Required]
-                public string? P3;
+                public string? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -1507,15 +1507,15 @@ namespace __OptionValidationStaticInstances
             {
                 [Required]
                 [ValidateEnumeratedItems]
-                public T[]? P1;
+                public T[]? P1 { get; set; }
 
                 [ValidateEnumeratedItems]
                 [Required]
-                public T[]? P2;
+                public T[]? P2 { get; set; }
 
                 [ValidateEnumeratedItems]
                 [Required]
-                public System.Collections.Generic.IList<T> P3 = null!;
+                public System.Collections.Generic.IList<T> P3 { get; set; } = null!;
             }
 
             [OptionsValidator]
@@ -1538,15 +1538,15 @@ namespace __OptionValidationStaticInstances
             {
                 [ValidateEnumeratedItems]
                 [Required]
-                public T[]? P1;
+                public T[]? P1 { get; set; }
 
                 [ValidateEnumeratedItems]
                 [Required]
-                public int[]? P2;
+                public int[]? P2 { get; set; }
 
                 [ValidateEnumeratedItems]
                 [Required]
-                public System.Collections.Generic.IList<T>? P3;
+                public System.Collections.Generic.IList<T>? P3 { get; set; }
             }
 
             [OptionsValidator]
@@ -1569,7 +1569,7 @@ namespace __OptionValidationStaticInstances
             {
                 [Required]
                 [ValidateEnumeratedItems]
-                public int P1;
+                public int P1 { get; set; }
             }
 
             [OptionsValidator]
index 22ab23a..d76cbd4 100644 (file)
     <ProjectReference Include="$(LibrariesProjectRoot)System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj" SkipUseReferenceAssembly="true" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options.DataAnnotations\src\Microsoft.Extensions.Options.DataAnnotations.csproj" />
+  </ItemGroup>
+
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
     <Reference Include="System.ComponentModel.DataAnnotations" />
   </ItemGroup>
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs
new file mode 100644 (file)
index 0000000..1ac4618
--- /dev/null
@@ -0,0 +1,252 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.Gen.OptionsValidation.Unit.Test
+{
+    public class OptionsRuntimeTests
+    {
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestValidationSuccessResults()
+        {
+            MyOptions options = new()
+            {
+                Name = "T",
+                Phone = "P",
+                Age = 30,
+                Nested = new()
+                {
+                    Tall = 10,
+                    Id = "1",
+                    Children = new()
+                    {
+                        new ChildOptions() { Name = "C1" },
+                        new ChildOptions() { Name = "C2" }
+                    },
+                    NestedList = new()
+                    {
+                        new NestedOptions() { Tall = 5, Id = "1" },
+                        new NestedOptions() { Tall = 6, Id = "2" },
+                        new NestedOptions() { Tall = 7, Id = "3" }
+                    }
+                }
+            };
+
+            MySourceGenOptionsValidator sourceGenOptionsValidator = new();
+            DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
+
+            ValidateOptionsResult result = sourceGenOptionsValidator.Validate("MyOptions", options);
+            Assert.True(result.Succeeded);
+
+            result = dataAnnotationValidateOptions.Validate("MyOptions", options);
+            Assert.True(result.Succeeded);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestBasicDataAnnotationFailures()
+        {
+            MyOptions options = new();
+
+            MySourceGenOptionsValidator sourceGenOptionsValidator = new();
+            DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
+
+            ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
+            Assert.True(result1.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "Age: The field MyOptions.Age must be between 0 and 100.",
+                            "Name: The MyOptions.Name field is required.",
+                            "Phone: The MyOptions.Phone field is required."
+                        },
+                        result1.Failures);
+
+            ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
+            Assert.True(result2.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "DataAnnotation validation failed for 'MyOptions' members: 'Age' with the error: 'The field Age must be between 0 and 100.'.",
+                            "DataAnnotation validation failed for 'MyOptions' members: 'Name' with the error: 'The Name field is required.'.",
+                            "DataAnnotation validation failed for 'MyOptions' members: 'Phone' with the error: 'The Phone field is required.'."
+                        },
+                        result2.Failures);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestValidationWithNestedTypes()
+        {
+            MyOptions options = new()
+            {
+                Name = "T",
+                Phone = "P",
+                Age = 30,
+                Nested = new()
+                {
+                    Tall = 20,
+                }
+            };
+
+            MySourceGenOptionsValidator sourceGenOptionsValidator = new();
+            DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
+
+            ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
+            Assert.True(result1.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "Tall: The field MyOptions.Nested.Tall must be between 0 and 10.",
+                            "Id: The MyOptions.Nested.Id field is required.",
+                        },
+                        result1.Failures);
+
+            ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
+            Assert.True(result2.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "DataAnnotation validation failed for 'MyOptions.Nested' members: 'Tall' with the error: 'The field Tall must be between 0 and 10.'.",
+                            "DataAnnotation validation failed for 'MyOptions.Nested' members: 'Id' with the error: 'The Id field is required.'.",
+                        },
+                        result2.Failures);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestValidationWithEnumeration()
+        {
+            MyOptions options = new()
+            {
+                Name = "T",
+                Phone = "P",
+                Age = 30,
+                Nested = new()
+                {
+                    Tall = 10,
+                    Id = "1",
+                    Children = new()
+                    {
+                        new ChildOptions(),
+                        new ChildOptions(),
+                        new ChildOptions()
+                    }
+                }
+            };
+
+            MySourceGenOptionsValidator sourceGenOptionsValidator = new();
+            DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
+
+            ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
+            Assert.True(result1.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "Name: The MyOptions.Nested.Children[0].Name field is required.",
+                            "Name: The MyOptions.Nested.Children[1].Name field is required.",
+                            "Name: The MyOptions.Nested.Children[2].Name field is required.",
+                        },
+                        result1.Failures);
+
+            ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
+            Assert.True(result2.Failed);
+            Assert.Equal(new List<string>
+                        {
+                            "DataAnnotation validation failed for 'MyOptions.Nested.Children[0]' members: 'Name' with the error: 'The Name field is required.'.",
+                            "DataAnnotation validation failed for 'MyOptions.Nested.Children[1]' members: 'Name' with the error: 'The Name field is required.'.",
+                            "DataAnnotation validation failed for 'MyOptions.Nested.Children[2]' members: 'Name' with the error: 'The Name field is required.'.",
+                        },
+                        result2.Failures);
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestValidationWithCyclicReferences()
+        {
+            NestedOptions nestedOptions = new()
+            {
+                Tall = 10,
+                Id = "2",
+            };
+
+            MyOptions options = new()
+            {
+                Name = "T",
+                Phone = "P",
+                Age = 30,
+                Nested = nestedOptions,
+            };
+
+            nestedOptions.NestedList = new()
+            {
+                new NestedOptions() { Tall = 5, Id = "1" },
+                nestedOptions, // Circular reference
+                new NestedOptions() { Tall = 7, Id = "3" },
+                nestedOptions  // Circular reference
+            };
+
+            MySourceGenOptionsValidator sourceGenOptionsValidator = new();
+            DataAnnotationValidateOptions<MyOptions> dataAnnotationValidateOptions = new("MyOptions");
+
+            ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options);
+            Assert.True(result1.Succeeded);
+
+            ValidateOptionsResult result2 = dataAnnotationValidateOptions.Validate("MyOptions", options);
+            Assert.True(result1.Succeeded);
+        }
+    }
+
+    public class MyOptions
+    {
+        [Range(0, 100)]
+        public int Age { get; set; } = 200;
+
+        [Required]
+        public string? Name { get; set; }
+
+        [Required]
+        public string? Phone { get; set; }
+
+        [ValidateObjectMembers]
+        public NestedOptions Nested { get; set; }
+    }
+
+    public class NestedOptions
+    {
+        [Range(0, 10)]
+        public double Tall { get; set; }
+
+        [Required]
+        public string? Id { get; set; }
+
+        [ValidateEnumeratedItems]
+        public List<ChildOptions>? Children { get; set; }
+
+#pragma warning disable SYSLIB1211 // Source gen does static analysis for circular reference. We need to disable it for this test.
+        [ValidateEnumeratedItems]
+        public List<NestedOptions> NestedList { get; set; } // To check cycling reference
+#pragma warning restore SYSLIB1211
+    }
+
+    public class ChildOptions
+    {
+        [Required]
+        public string? Name { get; set; }
+    }
+
+    public struct MyOptionsStruct
+    {
+        [Range(0, 100)]
+        public int Age { get; set; }
+
+        [Required]
+        public string? Name { get; set; }
+
+        [ValidateObjectMembers]
+        public NestedOptions Nested { get; set; }
+    }
+
+    [OptionsValidator]
+    public partial class MySourceGenOptionsValidator : IValidateOptions<MyOptions>
+    {
+    }
+}
\ No newline at end of file
index f848443..a467ed2 100644 (file)
@@ -1,4 +1,4 @@
-
+
     // <auto-generated/>
     #nullable enable
     #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
@@ -391,110 +391,6 @@ namespace Enumeration
         }
     }
 }
-namespace Fields
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    internal sealed partial class __ThirdModelValidator__
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P5";
-            context.DisplayName = baseName + "P5";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            return builder.Build();
-        }
-    }
-}
-namespace Fields
-{
-    partial struct FirstValidator
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P1";
-            context.DisplayName = baseName + "P1";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            if (options.P2 is not null)
-            {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
-            }
-
-            builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
-
-            return builder.Build();
-        }
-    }
-}
-namespace Fields
-{
-    partial struct SecondValidator
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P4";
-            context.DisplayName = baseName + "P4";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            return builder.Build();
-        }
-    }
-}
 namespace FileScopedNamespace
 {
     partial struct FirstValidator
@@ -658,7 +554,7 @@ namespace MultiModelValidator
 
             if (options.P2 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
             }
 
             return builder.Build();
@@ -793,14 +689,14 @@ namespace Nested
 
                     if (options.P2 is not null)
                     {
-                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
                     }
 
                     builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
 
                     if (options.P4 is not null)
                     {
-                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P4", options.P4));
                     }
 
                     return builder.Build();
@@ -1015,12 +911,12 @@ namespace RecordTypes
 
             if (options.P2 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P2", options.P2));
             }
 
             if (options.P3 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P3", options.P3));
             }
 
             builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
@@ -2065,16 +1961,14 @@ namespace __OptionValidationStaticInstances
 
         internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
 
-        internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
-
-        internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
+        internal static readonly global::MultiModelValidator.MultiValidator V3 = new global::MultiModelValidator.MultiValidator();
 
-        internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
+        internal static readonly global::Nested.Container2.Container3.SecondValidator V4 = new global::Nested.Container2.Container3.SecondValidator();
 
-        internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
+        internal static readonly global::Nested.Container4.Container5.ThirdValidator V5 = new global::Nested.Container4.Container5.ThirdValidator();
 
-        internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
+        internal static readonly global::RecordTypes.SecondValidator V6 = new global::RecordTypes.SecondValidator();
 
-        internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
+        internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
     }
 }
index 9055e11..9c68710 100644 (file)
@@ -1,4 +1,4 @@
-
+
     // <auto-generated/>
     #nullable enable
     #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
@@ -391,110 +391,6 @@ namespace Enumeration
         }
     }
 }
-namespace Fields
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    internal sealed partial class __ThirdModelValidator__
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P5";
-            context.DisplayName = baseName + "P5";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            return builder.Build();
-        }
-    }
-}
-namespace Fields
-{
-    partial struct FirstValidator
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.FirstModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P1";
-            context.DisplayName = baseName + "P1";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            if (options.P2 is not null)
-            {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
-            }
-
-            builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
-
-            return builder.Build();
-        }
-    }
-}
-namespace Fields
-{
-    partial struct SecondValidator
-    {
-        /// <summary>
-        /// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
-        /// </summary>
-        /// <param name="name">The name of the options instance being validated.</param>
-        /// <param name="options">The options instance.</param>
-        /// <returns>Validation result.</returns>
-        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.SecondModel options)
-        {
-            var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + ".";
-            var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();
-            var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
-            var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(2);
-
-            context.MemberName = "P4";
-            context.DisplayName = baseName + "P4";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes))
-            {
-                builder.AddResults(validationResults);
-            }
-
-            return builder.Build();
-        }
-    }
-}
 namespace FileScopedNamespace
 {
     partial struct FirstValidator
@@ -658,7 +554,7 @@ namespace MultiModelValidator
 
             if (options.P2 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2));
             }
 
             return builder.Build();
@@ -793,14 +689,14 @@ namespace Nested
 
                     if (options.P2 is not null)
                     {
-                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2));
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2));
                     }
 
                     builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3));
 
                     if (options.P4 is not null)
                     {
-                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4));
+                        builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P4", options.P4));
                     }
 
                     return builder.Build();
@@ -1015,12 +911,12 @@ namespace RecordTypes
 
             if (options.P2 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P2", options.P2));
             }
 
             if (options.P3 is not null)
             {
-                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3));
+                builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P3", options.P3));
             }
 
             builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4));
@@ -2057,16 +1953,14 @@ namespace __OptionValidationStaticInstances
 
         internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator();
 
-        internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator();
-
-        internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator();
+        internal static readonly global::MultiModelValidator.MultiValidator V3 = new global::MultiModelValidator.MultiValidator();
 
-        internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator();
+        internal static readonly global::Nested.Container2.Container3.SecondValidator V4 = new global::Nested.Container2.Container3.SecondValidator();
 
-        internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator();
+        internal static readonly global::Nested.Container4.Container5.ThirdValidator V5 = new global::Nested.Container4.Container5.ThirdValidator();
 
-        internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator();
+        internal static readonly global::RecordTypes.SecondValidator V6 = new global::RecordTypes.SecondValidator();
 
-        internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator();
+        internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
     }
 }
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs
deleted file mode 100644 (file)
index 3656d4f..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Fields;
-using Microsoft.Extensions.Options;
-using Xunit;
-
-namespace Microsoft.Gen.OptionsValidation.Test;
-
-public class FieldTests
-{
-    [Fact]
-    public void Invalid()
-    {
-        var thirdModel = new ThirdModel
-        {
-            P5 = "1234",
-        };
-
-        var secondModel = new SecondModel
-        {
-            P4 = "1234",
-        };
-
-        var firstModel = new FirstModel
-        {
-            P1 = "1234",
-            P2 = secondModel,
-            P3 = thirdModel,
-        };
-
-        var validator = default(FirstValidator);
-        var vr = validator.Validate("Fields", firstModel);
-
-        Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5");
-    }
-
-    [Fact]
-    public void Valid()
-    {
-        var thirdModel = new ThirdModel
-        {
-            P5 = "12345",
-            P6 = 1
-        };
-
-        var secondModel = new SecondModel
-        {
-            P4 = "12345",
-        };
-
-        var firstModel = new FirstModel
-        {
-            P1 = "12345",
-            P2 = secondModel,
-            P3 = thirdModel,
-        };
-
-        var validator = default(FirstValidator);
-        Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Fields", firstModel));
-    }
-}
index 549e5fb..2c44b9a 100644 (file)
@@ -16,46 +16,46 @@ namespace Enumeration
     public class FirstModel
     {
         [ValidateEnumeratedItems]
-        public IList<SecondModel>? P1;
+        public IList<SecondModel>? P1 { get; set; }
 
         [ValidateEnumeratedItems(typeof(SecondValidator))]
-        public IList<SecondModel>? P2;
+        public IList<SecondModel>? P2 { get; set; }
 
         [ValidateEnumeratedItems]
-        public IList<SecondModel?>? P3;
+        public IList<SecondModel?>? P3 { get; set; }
 
         [ValidateEnumeratedItems]
-        public IList<ThirdModel>? P4;
+        public IList<ThirdModel>? P4 { get; set; }
 
         [ValidateEnumeratedItems]
-        public IList<ThirdModel?>? P5;
+        public IList<ThirdModel?>? P5 { get; set; }
 
         [ValidateEnumeratedItems]
         [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
-        public IList<Nullable<ThirdModel>>? P51;
+        public IList<Nullable<ThirdModel>>? P51 { get; set; }
 
         [ValidateEnumeratedItems]
-        public SynteticEnumerable? P6;
+        public SynteticEnumerable? P6 { get; set; }
 
         [ValidateEnumeratedItems]
-        public SynteticEnumerable P7;
+        public SynteticEnumerable P7 { get; set; }
 
         [ValidateEnumeratedItems]
         [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable<T>")]
-        public Nullable<SynteticEnumerable> P8;
+        public Nullable<SynteticEnumerable> P8 { get; set; }
     }
 
     public class SecondModel
     {
         [Required]
         [MinLength(5)]
-        public string P6 = string.Empty;
+        public string P6 { get; set; } = string.Empty;
     }
 
     public struct ThirdModel
     {
         [Range(0, 10)]
-        public int Value;
+        public int Value { get; set; }
     }
 
     public struct SynteticEnumerable : IEnumerable<SecondModel>
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs
deleted file mode 100644 (file)
index 0a6ab14..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel.DataAnnotations;
-using Microsoft.Extensions.Options;
-
-namespace Fields
-{
-#pragma warning disable SA1649
-#pragma warning disable SA1402
-#pragma warning disable S1186
-#pragma warning disable CA1822
-
-    public class FirstModel
-    {
-        [Required]
-        [MinLength(5)]
-        public string P1 = string.Empty;
-
-        [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))]
-        public SecondModel? P2;
-
-        [Microsoft.Extensions.Options.ValidateObjectMembers]
-        public ThirdModel P3;
-    }
-
-    public class SecondModel
-    {
-        [Required]
-        [MinLength(5)]
-        public string P4 = string.Empty;
-    }
-
-    public struct ThirdModel
-    {
-        [Required]
-        [MinLength(5)]
-        public string P5 = string.Empty;
-
-        public int P6 = default;
-
-        public ThirdModel(object _)
-        {
-        }
-    }
-
-    [OptionsValidator]
-    public partial struct FirstValidator : IValidateOptions<FirstModel>
-    {
-        public void Validate()
-        {
-        }
-
-        public void Validate(int _)
-        {
-        }
-
-        public void Validate(string? _)
-        {
-        }
-
-        public void Validate(string? _0, object _1)
-        {
-        }
-    }
-
-    [OptionsValidator]
-    public partial struct SecondValidator : IValidateOptions<SecondModel>
-    {
-    }
-}
index 31993d0..2401630 100644 (file)
@@ -154,7 +154,7 @@ namespace TestClasses.OptionsValidation
         public string? DerivedVal { get; set; }
 
         [Required]
-        internal virtual int? VirtualValWithAttr { get; set; }
+        public virtual int? VirtualValWithAttr { get; set; }
 
         public virtual int? VirtualValWithoutAttr { get; set; }
 
@@ -164,7 +164,7 @@ namespace TestClasses.OptionsValidation
 
     public class LeafModel : DerivedModel
     {
-        internal override int? VirtualValWithAttr { get; set; }
+        public override int? VirtualValWithAttr { get; set; }
 
         [Required]
         public override int? VirtualValWithoutAttr { get; set; }
index 2ca6c78..695ee65 100644 (file)
@@ -13,17 +13,17 @@ namespace MultiModelValidator
     {
         [Required]
         [MinLength(5)]
-        public string P1 = string.Empty;
+        public string P1 { get; set; } = string.Empty;
 
         [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(MultiValidator))]
-        public SecondModel? P2;
+        public SecondModel? P2 { get; set; }
     }
 
     public class SecondModel
     {
         [Required]
         [MinLength(5)]
-        public string P3 = string.Empty;
+        public string P3 { get; set; } = string.Empty;
     }
 
     [OptionsValidator]
index db295a4..c57ebd2 100644 (file)
@@ -36,7 +36,7 @@ namespace RepeatedTypes
     {
         [Required]
         [MinLength(5)]
-        public string? P5;
+        public string? P5 { get; set; }
     }
 
     [OptionsValidator]
index 8fe5a9a..1ca4e93 100644 (file)
@@ -13,7 +13,7 @@ namespace SelfValidation
     public class FirstModel : IValidatableObject
     {
         [Required]
-        public string P1 = string.Empty;
+        public string P1 { get; set; } = string.Empty;
 
         public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
         {