From bc43b7ece88885d4f0246c73313c5e19864e15db Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 10 Aug 2023 12:49:30 -0700 Subject: [PATCH] Support new Options attributes in the Runtime (#90275) --- .../src/DataAnnotationValidateOptions.cs | 78 ++++++- ...osoft.Extensions.Options.DataAnnotations.csproj | 1 + .../Microsoft.Extensions.Options/gen/Parser.cs | 5 +- .../tests/SourceGeneration.Unit.Tests/Main.cs | 80 +++---- ...ions.Options.SourceGeneration.Unit.Tests.csproj | 4 + .../OptionsRuntimeTests.cs | 252 +++++++++++++++++++++ .../Baselines/NetCoreApp/Validators.g.cs | 128 +---------- .../Baselines/NetFX/Validators.g.cs | 128 +---------- .../SourceGenerationTests/Generated/FieldTests.cs | 62 ----- .../TestClasses/Enumeration.cs | 22 +- .../SourceGenerationTests/TestClasses/Fields.cs | 71 ------ .../TestClasses/FileScopedNamespace.cs | 2 +- .../SourceGenerationTests/TestClasses/Models.cs | 4 +- .../TestClasses/MultiModelValidator.cs | 6 +- .../TestClasses/RepeatedTypes.cs | 2 +- .../TestClasses/SelfValidation.cs | 2 +- 16 files changed, 414 insertions(+), 433 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs delete mode 100644 src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs delete mode 100644 src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs diff --git a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/DataAnnotationValidateOptions.cs b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/DataAnnotationValidateOptions.cs index 446f7d2..e8f4b60 100644 --- a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/DataAnnotationValidateOptions.cs +++ b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/DataAnnotationValidateOptions.cs @@ -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(); - if (Validator.TryValidateObject(options, new ValidationContext(options), validationResults, validateAllProperties: true)) + HashSet? visited = null; + List? 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(); - 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 results, ref List? errors, ref HashSet? 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(); + + 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() is not null) + { + visited ??= new HashSet(ReferenceEqualityComparer.Instance); + visited.Add(options); + + results ??= new List(); + res = TryValidateOptions(value, $"{qualifiedName}.{propertyInfo.Name}", results, ref errors, ref visited) && res; + } + else if (value is IEnumerable enumerable && + propertyInfo.GetCustomAttribute() is not null) + { + visited ??= new HashSet(ReferenceEqualityComparer.Instance); + visited.Add(options); + results ??= new List(); + + int index = 0; + foreach (object item in enumerable) + { + res = TryValidateOptions(item, $"{qualifiedName}.{propertyInfo.Name}[{index++}]", results, ref errors, ref visited) && res; + } + } + } + + return res; } } } diff --git a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj index 031a210..0fdd978 100644 --- a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj +++ b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj @@ -12,6 +12,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs index c4d1da9..07ea7de 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs @@ -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; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs index 756b169..b243ee5 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs @@ -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? 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 P3 = null!; + public System.Collections.Generics.IList 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? P4; + public System.Collections.Generics.IList? 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 P3 = null!; + public System.Collections.Generic.IList 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? P3; + public System.Collections.Generic.IList? P3 { get; set; } } [OptionsValidator] @@ -1569,7 +1569,7 @@ namespace __OptionValidationStaticInstances { [Required] [ValidateEnumeratedItems] - public int P1; + public int P1 { get; set; } } [OptionsValidator] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj index 22ab23a..d76cbd4 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj @@ -20,6 +20,10 @@ + + + + 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 index 0000000..1ac4618 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs @@ -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 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 dataAnnotationValidateOptions = new("MyOptions"); + + ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options); + Assert.True(result1.Failed); + Assert.Equal(new List + { + "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 + { + "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 dataAnnotationValidateOptions = new("MyOptions"); + + ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options); + Assert.True(result1.Failed); + Assert.Equal(new List + { + "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 + { + "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 dataAnnotationValidateOptions = new("MyOptions"); + + ValidateOptionsResult result1 = sourceGenOptionsValidator.Validate("MyOptions", options); + Assert.True(result1.Failed); + Assert.Equal(new List + { + "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 + { + "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 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? 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 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 + { + } +} \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs index f848443..a467ed2 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs @@ -1,4 +1,4 @@ - + // #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__ - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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 - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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 - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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(); } } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs index 9055e11..9c68710 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs @@ -1,4 +1,4 @@ - + // #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__ - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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 - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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 - { - /// - /// Validates a specific named options instance (or all when is ). - /// - /// The name of the options instance being validated. - /// The options instance. - /// Validation result. - [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(); - var validationAttributes = new global::System.Collections.Generic.List(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 index 3656d4f..0000000 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs +++ /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)); - } -} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs index 549e5fb..2c44b9a 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs @@ -16,46 +16,46 @@ namespace Enumeration public class FirstModel { [ValidateEnumeratedItems] - public IList? P1; + public IList? P1 { get; set; } [ValidateEnumeratedItems(typeof(SecondValidator))] - public IList? P2; + public IList? P2 { get; set; } [ValidateEnumeratedItems] - public IList? P3; + public IList? P3 { get; set; } [ValidateEnumeratedItems] - public IList? P4; + public IList? P4 { get; set; } [ValidateEnumeratedItems] - public IList? P5; + public IList? P5 { get; set; } [ValidateEnumeratedItems] [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable")] - public IList>? P51; + public IList>? 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")] - public Nullable P8; + public Nullable 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 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 index 0a6ab14..0000000 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs +++ /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 - { - public void Validate() - { - } - - public void Validate(int _) - { - } - - public void Validate(string? _) - { - } - - public void Validate(string? _0, object _1) - { - } - } - - [OptionsValidator] - public partial struct SecondValidator : IValidateOptions - { - } -} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs index cf76ea34..59e5f62 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs @@ -12,7 +12,7 @@ public class FirstModel { [Required] [MinLength(5)] - public string P1 = string.Empty; + public string P1 { get; set; } = string.Empty; } [OptionsValidator] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs index 31993d0..2401630 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs @@ -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; } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs index 2ca6c78..695ee65 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs @@ -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] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs index db295a4..c57ebd2 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs @@ -36,7 +36,7 @@ namespace RepeatedTypes { [Required] [MinLength(5)] - public string? P5; + public string? P5 { get; set; } } [OptionsValidator] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs index 8fe5a9a..1ca4e93 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs @@ -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 Validate(ValidationContext validationContext) { -- 2.7.4