From b62c8ff4a7044c8e7008a4d9f83b1d6560d9c86a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:00:00 -0700 Subject: [PATCH] [release/8.0-staging] Support IValidatableObject for nested model types in options source gen (#94062) Co-authored-by: Tarek Mahmoud Sayed --- .../gen/Parser.cs | 2 +- .../src/Microsoft.Extensions.Options.csproj | 2 + .../Baselines/NetCoreApp/Validators.g.cs | 50 +++++++++++++++++++ .../Baselines/NetFX/Validators.g.cs | 48 ++++++++++++++++++ .../Generated/SelfValidationTests.cs | 10 +++- .../TestClasses/SelfValidation.cs | 20 ++++++++ 6 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs index 47cb71c3411cd..ab2c19de81923 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs @@ -687,7 +687,7 @@ private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSy var model = new ValidatedModel( GetFQN(mt), mt.Name, - false, + ModelSelfValidates(mt), membersToValidate); var validatorTypeName = "__" + mt.Name + "Validator__"; diff --git a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj index c7ea3e00049e3..abbc5d2329c5c 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj +++ b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj @@ -4,6 +4,8 @@ $(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true true + true + 1 Provides a strongly typed way of specifying and accessing settings using dependency injection. 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 c487888c9f16b..956cae26e90f6 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 @@ -1154,6 +1154,41 @@ partial class FirstValidator } } namespace SelfValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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")] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "The created ValidationContext object is used in a way that never call reflection")] + public static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.SecondModel options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + 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(1); + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + (builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace SelfValidation { partial struct FirstValidator { @@ -1181,6 +1216,21 @@ partial struct FirstValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + if (options.P2 is not null) + { + (builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2)); + } + (builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); 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 7e998cea22cdd..faae7d62d9c41 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 @@ -1098,6 +1098,39 @@ partial class FirstValidator } } namespace SelfValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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::SelfValidation.SecondModel options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + 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(1); + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "SecondModel.P3" : $"{name}.P3"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + (builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace SelfValidation { partial struct FirstValidator { @@ -1123,6 +1156,21 @@ partial struct FirstValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + if (options.P2 is not null) + { + (builder ??= new()).AddResult(global::SelfValidation.__SecondModelValidator__.Validate(string.IsNullOrEmpty(name) ? "FirstModel.P2" : $"{name}.P2", options.P2)); + } + (builder ??= new()).AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs index 0a511333f0357..36d15fe74a10e 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs @@ -15,12 +15,16 @@ public void Invalid() var firstModel = new FirstModel { P1 = "1234", + P2 = new SecondModel + { + P3 = "5678" + } }; var validator = default(FirstValidator); var vr = validator.Validate("SelfValidation", firstModel); - Utils.VerifyValidateOptionsResult(vr, 1, "P1"); + Utils.VerifyValidateOptionsResult(vr, 2, "P3", "P1"); } [Fact] @@ -29,6 +33,10 @@ public void Valid() var firstModel = new FirstModel { P1 = "12345", + P2 = new SecondModel + { + P3 = "67890" + } }; var validator = default(FirstValidator); 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 1ca4e93142a37..893c3ffc90ad9 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs @@ -15,6 +15,10 @@ public class FirstModel : IValidatableObject [Required] public string P1 { get; set; } = string.Empty; + [Required] + [ValidateObjectMembers] + public SecondModel P2 { get; set; } + public IEnumerable Validate(ValidationContext validationContext) { if (P1.Length < 5) @@ -26,6 +30,22 @@ public IEnumerable Validate(ValidationContext validationContex } } + public class SecondModel : IValidatableObject + { + [Required] + public string P3 { get; set; } = string.Empty; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (P3.Length < 5) + { + return new[] { new ValidationResult("P3 is not long enough") }; + } + + return Array.Empty(); + } + } + [OptionsValidator] public partial struct FirstValidator : IValidateOptions {