[release/8.0] Fix Options Source Gen Trimming Issues (#93193)
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Thu, 12 Oct 2023 00:02:34 +0000 (17:02 -0700)
committerGitHub <noreply@github.com>
Thu, 12 Oct 2023 00:02:34 +0000 (17:02 -0700)
* Fix Options Source Gen Trimming Issues

* Make Emitted Attribute Order Deterministic in Options Source Generator (#93260)

* Make Emitted Attribute Order Deterministic in Options Source Generator

* Use ordinal comparison when ordering the list

---------

Co-authored-by: Tarek Mahmoud Sayed <tarekms@microsoft.com>
41 files changed:
docs/project/list-of-diagnostics.md
src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs
src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs
src/libraries/Microsoft.Extensions.Options/gen/Generator.cs
src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj
src/libraries/Microsoft.Extensions.Options/gen/OptionsSourceGenContext.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/gen/Parser.cs
src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs
src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs
src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs
src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/DataAnnotationAttributesWithParams.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs [new file with mode: 0644]
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
src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx
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/Resources/Strings.resx
src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/ConfigureTests.cs
src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/Microsoft.Extensions.Options.TrimmingTests.proj

index 4b46d49..aed0f89 100644 (file)
@@ -251,7 +251,7 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
 |  __`SYSLIB1214`__ | Options validation generator: Can't validate constants, static fields or properties. |
 |  __`SYSLIB1215`__ | Options validation generator: Validation attribute on the member is inaccessible from the validator type. |
 |  __`SYSLIB1216`__ | C# language version not supported by the options validation source generator. |
-|  __`SYSLIB1217`__ | *_`SYSLIB1201`-`SYSLIB1219` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
+|  __`SYSLIB1217`__ | The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types. |
 |  __`SYSLIB1218`__ | *_`SYSLIB1201`-`SYSLIB1219` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
 |  __`SYSLIB1219`__ | *_`SYSLIB1201`-`SYSLIB1219` reserved for Microsoft.Extensions.Options.SourceGeneration.* |
 |  __`SYSLIB1220`__ | JsonSourceGenerator encountered a [JsonConverterAttribute] with an invalid type argument. |
index 141fc6b..49562a0 100644 (file)
@@ -112,5 +112,12 @@ namespace Microsoft.Extensions.Options.Generators
             messageFormat: SR.OptionsUnsupportedLanguageVersionMessage,
             category: Category,
             defaultSeverity: DiagnosticSeverity.Error);
+
+        public static DiagnosticDescriptor IncompatibleWithTypeForValidationAttribute { get; } = Make(
+            id: "SYSLIB1217",
+            title: SR.TypeCannotBeUsedWithTheValidationAttributeTitle,
+            messageFormat: SR.TypeCannotBeUsedWithTheValidationAttributeMessage,
+            category: Category,
+            defaultSeverity: DiagnosticSeverity.Warning);
     }
 }
index 9e0cb65..41609ad 100644 (file)
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
+using System.Text;
 using System.Threading;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
@@ -18,36 +19,39 @@ namespace Microsoft.Extensions.Options.Generators
     internal sealed class Emitter : EmitterBase
     {
         private const string StaticFieldHolderClassesNamespace = "__OptionValidationStaticInstances";
+        internal const string StaticGeneratedValidationAttributesClassesNamespace = "__OptionValidationGeneratedAttributes";
+        internal const string StaticAttributeClassNamePrefix = "__SourceGen_";
+        internal const string StaticGeneratedMaxLengthAttributeClassesName = "__SourceGen_MaxLengthAttribute";
         private const string StaticListType = "global::System.Collections.Generic.List";
         private const string StaticValidationResultType = "global::System.ComponentModel.DataAnnotations.ValidationResult";
         private const string StaticValidationAttributeType = "global::System.ComponentModel.DataAnnotations.ValidationAttribute";
-
+        private const string StaticValidationContextType = "global::System.ComponentModel.DataAnnotations.ValidationContext";
         private string _staticValidationAttributeHolderClassName = "__Attributes";
         private string _staticValidatorHolderClassName = "__Validators";
         private string _staticValidationAttributeHolderClassFQN;
         private string _staticValidatorHolderClassFQN;
-        private string _modifier;
         private string _TryGetValueNullableAnnotation;
+        private readonly SymbolHolder _symbolHolder;
+        private readonly OptionsSourceGenContext _optionsSourceGenContext;
+
 
         private sealed record StaticFieldInfo(string FieldTypeFQN, int FieldOrder, string FieldName, IList<string> InstantiationLines);
 
-        public Emitter(Compilation compilation, bool emitPreamble = true) : base(emitPreamble)
+        public Emitter(Compilation compilation, SymbolHolder symbolHolder, OptionsSourceGenContext optionsSourceGenContext, bool emitPreamble = true) : base(emitPreamble)
         {
-            if (((CSharpCompilation)compilation).LanguageVersion >= Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp11)
-            {
-                _modifier = "file";
-            }
-            else
+            _optionsSourceGenContext = optionsSourceGenContext;
+
+            if (!_optionsSourceGenContext.IsLangVersion11AndAbove)
             {
-                _modifier = "internal";
-                string suffix = $"_{GetNonRandomizedHashCode(compilation.SourceModule.Name):X8}";
-                _staticValidationAttributeHolderClassName += suffix;
-                _staticValidatorHolderClassName += suffix;
+                _staticValidationAttributeHolderClassName += _optionsSourceGenContext.Suffix;
+                _staticValidatorHolderClassName += _optionsSourceGenContext.Suffix;
             }
 
             _staticValidationAttributeHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{_staticValidationAttributeHolderClassName}";
             _staticValidatorHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{_staticValidatorHolderClassName}";
             _TryGetValueNullableAnnotation = GetNullableAnnotationStringForTryValidateValueToUseInGeneratedCode(compilation);
+
+            _symbolHolder = symbolHolder;
         }
 
         public string Emit(
@@ -65,6 +69,7 @@ namespace Microsoft.Extensions.Options.Generators
 
             GenStaticClassWithStaticReadonlyFields(staticValidationAttributesDict.Values, StaticFieldHolderClassesNamespace, _staticValidationAttributeHolderClassName);
             GenStaticClassWithStaticReadonlyFields(staticValidatorsDict.Values, StaticFieldHolderClassesNamespace, _staticValidatorHolderClassName);
+            GenValidationAttributesClasses();
 
             return Capture();
         }
@@ -146,7 +151,7 @@ namespace Microsoft.Extensions.Options.Generators
             OutOpenBrace();
 
             OutGeneratedCodeAttribute();
-            OutLn($"{_modifier} static class {className}");
+            OutLn($"{_optionsSourceGenContext.ClassModifier} static class {className}");
             OutOpenBrace();
 
             var staticValidationAttributes = staticFields
@@ -186,6 +191,396 @@ namespace Microsoft.Extensions.Options.Generators
             OutCloseBrace();
         }
 
+        public void EmitMaxLengthAttribute(string modifier, string prefix, string className, string linesToInsert, string suffix)
+        {
+            OutGeneratedCodeAttribute();
+
+            string qualifiedClassName = $"{prefix}{suffix}_{className}";
+
+            OutLn($$"""
+[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
+    {
+        private const int MaxAllowableLength = -1;
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
+        public {{qualifiedClassName}}(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public {{qualifiedClassName}}(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
+        public int Length { get; }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+        public override bool IsValid(object? value)
+        {
+            if (Length == 0 || Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.");
+            }
+            if (value == null || MaxAllowableLength == Length)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            {{linesToInsert}}else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length <= Length;
+        }
+    }
+""");
+        }
+
+        public void EmitMinLengthAttribute(string modifier, string prefix, string className, string linesToInsert, string suffix)
+        {
+            OutGeneratedCodeAttribute();
+
+            string qualifiedClassName = $"{prefix}{suffix}_{className}";
+
+            OutLn($$"""
+[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public {{qualifiedClassName}}(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            {{linesToInsert}}else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+""");
+        }
+
+        public void EmitLengthAttribute(string modifier, string prefix, string className, string linesToInsert, string suffix)
+        {
+            OutGeneratedCodeAttribute();
+
+            string qualifiedClassName = $"{prefix}{suffix}_{className}";
+
+            OutLn($$"""
+[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'.";
+        public {{qualifiedClassName}}(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; }
+        public int MinimumLength { get; }
+        public int MaximumLength { get; }
+        public override bool IsValid(object? value)
+        {
+            if (MinimumLength < 0)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater.");
+            }
+            if (MaximumLength < MinimumLength)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            {{linesToInsert}}else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
+    }
+""");
+        }
+
+        public void EmitCompareAttribute(string modifier, string prefix, string className, string linesToInsert, string suffix)
+        {
+            OutGeneratedCodeAttribute();
+
+            string qualifiedClassName = $"{prefix}{suffix}_{className}";
+
+            OutLn($$"""
+[global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
+    {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
+    {
+        private static string DefaultErrorMessageString => "'{0}' and '{1}' do not match.";
+        public {{qualifiedClassName}}(string otherProperty) : base(() => DefaultErrorMessageString)
+        {
+            if (otherProperty == null)
+            {
+                throw new global::System.ArgumentNullException(nameof(otherProperty));
+            }
+            OtherProperty = otherProperty;
+        }
+        public string OtherProperty { get; }
+        public override bool RequiresValidationContext => true;
+
+        protected override {{StaticValidationResultType}}? IsValid(object? value, {{StaticValidationContextType}} validationContext)
+        {
+            bool result = true;
+
+            {{linesToInsert}}
+            if (!result)
+            {
+                string[]? memberNames = validationContext.MemberName is null ? null : new string[] { validationContext.MemberName };
+                return new {{StaticValidationResultType}}(FormatErrorMessage(validationContext.DisplayName), memberNames);
+            }
+
+            return null;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
+    }
+""");
+        }
+
+        public void EmitRangeAttribute(string modifier, string prefix, string className, string suffix)
+        {
+            OutGeneratedCodeAttribute();
+
+            string qualifiedClassName = $"{prefix}{suffix}_{className}";
+
+            OutLn($$"""
+[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}}
+    {
+        public {{qualifiedClassName}}(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public {{qualifiedClassName}}(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public {{qualifiedClassName}}(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+""");
+        }
+
+        private string GenerateStronglyTypedCodeForLengthAttributes(HashSet<object> data)
+        {
+            if (data.Count == 0)
+            {
+                return string.Empty;
+            }
+
+            StringBuilder sb = new();
+            string padding = GetPaddingString(3);
+
+            foreach (var type in data)
+            {
+                string typeName = (string)type;
+                sb.AppendLine($"else if (value is {typeName})");
+                sb.AppendLine($"{padding}{{");
+                sb.AppendLine($"{padding}    length = (({typeName})value).Count;");
+                sb.AppendLine($"{padding}}}");
+                sb.Append($"{padding}");
+            }
+
+            return sb.ToString();
+        }
+
+        private string GenerateStronglyTypedCodeForCompareAttribute(HashSet<object>? data)
+        {
+            if (data is null || data.Count == 0)
+            {
+                return string.Empty;
+            }
+
+            StringBuilder sb = new();
+            string padding = GetPaddingString(3);
+            bool first = true;
+
+            foreach (var obj in data)
+            {
+                (string type, string property) = ((string, string))obj;
+                sb.Append(first ? $"if " : $"{padding}else if ");
+                sb.AppendLine($"(validationContext.ObjectInstance is {type} && OtherProperty == \"{property}\")");
+                sb.AppendLine($"{padding}{{");
+                sb.AppendLine($"{padding}    result = Equals(value, (({type})validationContext.ObjectInstance).{property});");
+                sb.AppendLine($"{padding}}}");
+                first = false;
+            }
+
+            return sb.ToString();
+        }
+
+        private void GenValidationAttributesClasses()
+        {
+            if (_optionsSourceGenContext.AttributesToGenerate.Count == 0)
+            {
+                return;
+            }
+
+            var attributesData = _optionsSourceGenContext.AttributesToGenerate.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal).ToArray();
+
+            OutLn($"namespace {StaticGeneratedValidationAttributesClassesNamespace}");
+            OutOpenBrace();
+
+            foreach (var attributeData in attributesData)
+            {
+                if (attributeData.Key == _symbolHolder.MaxLengthAttributeSymbol.Name)
+                {
+                    string linesToInsert = attributeData.Value is not null ? GenerateStronglyTypedCodeForLengthAttributes((HashSet<object>)attributeData.Value) : string.Empty;
+                    EmitMaxLengthAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, linesToInsert, _optionsSourceGenContext.Suffix);
+                }
+                else if (attributeData.Key == _symbolHolder.MinLengthAttributeSymbol.Name)
+                {
+                    string linesToInsert = attributeData.Value is not null ? GenerateStronglyTypedCodeForLengthAttributes((HashSet<object>)attributeData.Value) : string.Empty;
+                    EmitMinLengthAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, linesToInsert, _optionsSourceGenContext.Suffix);
+                }
+                else if (_symbolHolder.LengthAttributeSymbol is not null && attributeData.Key == _symbolHolder.LengthAttributeSymbol.Name)
+                {
+                    string linesToInsert = attributeData.Value is not null ? GenerateStronglyTypedCodeForLengthAttributes((HashSet<object>)attributeData.Value) : string.Empty;
+                    EmitLengthAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, linesToInsert, _optionsSourceGenContext.Suffix);
+                }
+                else if (attributeData.Key == _symbolHolder.CompareAttributeSymbol.Name && attributeData.Value is not null)
+                {
+                    string linesToInsert = GenerateStronglyTypedCodeForCompareAttribute((HashSet<object>)attributeData.Value);
+                    EmitCompareAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, linesToInsert: linesToInsert, _optionsSourceGenContext.Suffix);
+                }
+                else if (attributeData.Key == _symbolHolder.RangeAttributeSymbol.Name)
+                {
+                    EmitRangeAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, _optionsSourceGenContext.Suffix);
+                }
+            }
+
+            OutCloseBrace();
+        }
+
         private void GenModelSelfValidationIfNecessary(ValidatedModel modelToValidate)
         {
             if (modelToValidate.SelfValidates)
@@ -209,10 +604,18 @@ namespace Microsoft.Extensions.Options.Generators
             OutLn($"/// <returns>Validation result.</returns>");
             OutGeneratedCodeAttribute();
 
+            if (_symbolHolder.UnconditionalSuppressMessageAttributeSymbol is not null)
+            {
+                // We disable the warning on `new ValidationContext(object)` usage as we use it in a safe way that not require executing the reflection code.
+                // This is done by initializing the DisplayName in the context which is the part trigger reflection if it is not initialized.
+                OutLn($"[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage(\"Trimming\", \"IL2026:RequiresUnreferencedCode\",");
+                OutLn($"     Justification = \"The created ValidationContext object is used in a way that never call reflection\")]");
+            }
+
             OutLn($"public {(makeStatic ? "static " : string.Empty)}global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, {modelToValidate.Name} options)");
             OutOpenBrace();
             OutLn($"global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;");
-            OutLn($"var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);");
+            OutLn($"var context = new {StaticValidationContextType}(options);");
 
             int capacity = modelToValidate.MembersToValidate.Max(static vm => vm.ValidationAttributes.Count);
             if (capacity > 0)
@@ -438,19 +841,5 @@ namespace Microsoft.Extensions.Options.Generators
 
             return staticValidatorInstance;
         }
-
-        /// <summary>
-        /// Returns a non-randomized hash code for the given string.
-        /// We always return a positive value.
-        /// </summary>
-        internal static int GetNonRandomizedHashCode(string s)
-        {
-            uint result = 2166136261u;
-            foreach (char c in s)
-            {
-                result = (c ^ result) * 16777619;
-            }
-            return Math.Abs((int)result);
-        }
     }
 }
index 34533fc..e9c1f55 100644 (file)
@@ -38,12 +38,14 @@ namespace Microsoft.Extensions.Options.Generators
                 return;
             }
 
-            var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, context.CancellationToken);
+            OptionsSourceGenContext optionsSourceGenContext = new(compilation);
+
+            var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, optionsSourceGenContext, context.CancellationToken);
 
             var validatorTypes = parser.GetValidatorTypes(types);
             if (validatorTypes.Count > 0)
             {
-                var emitter = new Emitter(compilation);
+                var emitter = new Emitter(compilation, symbolHolder!, optionsSourceGenContext);
                 var result = emitter.Emit(validatorTypes, context.CancellationToken);
 
                 context.AddSource("Validators.g.cs", SourceText.From(result, Encoding.UTF8));
index 5571341..f5bad27 100644 (file)
@@ -30,6 +30,7 @@
     <Compile Include="Model\ValidatedModel.cs" />
     <Compile Include="Model\ValidationAttributeInfo.cs" />
     <Compile Include="Model\ValidatorType.cs" />
+    <Compile Include="OptionsSourceGenContext.cs" />
     <Compile Include="Parser.cs" />
     <Compile Include="ParserUtilities.cs" />
     <Compile Include="SymbolHolder.cs" />
diff --git a/src/libraries/Microsoft.Extensions.Options/gen/OptionsSourceGenContext.cs b/src/libraries/Microsoft.Extensions.Options/gen/OptionsSourceGenContext.cs
new file mode 100644 (file)
index 0000000..8da3e31
--- /dev/null
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.Versioning;
+
+namespace Microsoft.Extensions.Options.Generators
+{
+    internal sealed class OptionsSourceGenContext
+    {
+        public OptionsSourceGenContext(Compilation compilation)
+        {
+            IsLangVersion11AndAbove = ((CSharpCompilation)compilation).LanguageVersion >= Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp11;
+            ClassModifier = IsLangVersion11AndAbove ? "file" : "internal";
+            Suffix = IsLangVersion11AndAbove ? "" : $"_{GetNonRandomizedHashCode(compilation.SourceModule.Name):X8}";
+        }
+
+        internal string Suffix { get; }
+        internal string ClassModifier { get; }
+        internal bool IsLangVersion11AndAbove { get; }
+        internal Dictionary<string, HashSet<object>?> AttributesToGenerate { get; set; } = new Dictionary<string, HashSet<object>?>();
+
+        internal void EnsureTrackingAttribute(string attributeName, bool createValue, out HashSet<object>? value)
+        {
+            bool exist = AttributesToGenerate.TryGetValue(attributeName, out value);
+            if (value is null)
+            {
+                if (createValue)
+                {
+                    value = new HashSet<object>();
+                }
+
+                if (!exist || createValue)
+                {
+                    AttributesToGenerate[attributeName] = value;
+                }
+            }
+        }
+
+        internal static bool IsConvertibleBasicType(ITypeSymbol typeSymbol)
+        {
+            return typeSymbol.SpecialType switch
+            {
+                SpecialType.System_Boolean => true,
+                SpecialType.System_Byte => true,
+                SpecialType.System_Char => true,
+                SpecialType.System_DateTime => true,
+                SpecialType.System_Decimal => true,
+                SpecialType.System_Double => true,
+                SpecialType.System_Int16 => true,
+                SpecialType.System_Int32 => true,
+                SpecialType.System_Int64 => true,
+                SpecialType.System_SByte => true,
+                SpecialType.System_Single => true,
+                SpecialType.System_UInt16 => true,
+                SpecialType.System_UInt32 => true,
+                SpecialType.System_UInt64 => true,
+                SpecialType.System_String => true,
+                _ => false,
+            };
+        }
+
+        /// <summary>
+        /// Returns a non-randomized hash code for the given string.
+        /// We always return a positive value.
+        /// </summary>
+        internal static int GetNonRandomizedHashCode(string s)
+        {
+            uint result = 2166136261u;
+            foreach (char c in s)
+            {
+                result = (c ^ result) * 16777619;
+            }
+
+            return Math.Abs((int)result);
+        }
+    }
+}
index 010b895..47cb71c 100644 (file)
@@ -25,6 +25,7 @@ namespace Microsoft.Extensions.Options.Generators
         private readonly Compilation _compilation;
         private readonly Action<Diagnostic> _reportDiagnostic;
         private readonly SymbolHolder _symbolHolder;
+        private readonly OptionsSourceGenContext _optionsSourceGenContext;
         private readonly Dictionary<ITypeSymbol, ValidatorType> _synthesizedValidators = new(SymbolEqualityComparer.Default);
         private readonly HashSet<ITypeSymbol> _visitedModelTypes = new(SymbolEqualityComparer.Default);
 
@@ -32,12 +33,14 @@ namespace Microsoft.Extensions.Options.Generators
             Compilation compilation,
             Action<Diagnostic> reportDiagnostic,
             SymbolHolder symbolHolder,
+            OptionsSourceGenContext optionsSourceGenContext,
             CancellationToken cancellationToken)
         {
             _compilation = compilation;
             _cancellationToken = cancellationToken;
             _reportDiagnostic = reportDiagnostic;
             _symbolHolder = symbolHolder;
+            _optionsSourceGenContext = optionsSourceGenContext;
         }
 
         public IReadOnlyList<ValidatorType> GetValidatorTypes(IEnumerable<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> classes)
@@ -288,7 +291,7 @@ namespace Microsoft.Extensions.Options.Generators
                     ? memberLocation
                     : lowerLocationInCompilation;
 
-                var memberInfo = GetMemberInfo(member, speculate, location, validatorType);
+                var memberInfo = GetMemberInfo(member, speculate, location, modelType, validatorType);
                 if (memberInfo is not null)
                 {
                     if (member.DeclaredAccessibility != Accessibility.Public)
@@ -304,7 +307,7 @@ namespace Microsoft.Extensions.Options.Generators
             return membersToValidate;
         }
 
-        private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate, Location location, ITypeSymbol validatorType)
+        private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate, Location location, ITypeSymbol modelType, ITypeSymbol validatorType)
         {
             ITypeSymbol memberType;
             switch (member)
@@ -325,7 +328,7 @@ namespace Microsoft.Extensions.Options.Generators
                     break;
                 */
                 default:
-                    // we only care about properties and fields
+                    // we only care about properties
                     return null;
             }
 
@@ -467,7 +470,26 @@ namespace Microsoft.Extensions.Options.Generators
                         continue;
                     }
 
-                    var validationAttr = new ValidationAttributeInfo(attributeType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+                    string attributeFullQualifiedName = attributeType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+                    if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.MaxLengthAttributeSymbol) ||
+                        SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.MinLengthAttributeSymbol) ||
+                        (_symbolHolder.LengthAttributeSymbol is not null && SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.LengthAttributeSymbol)))
+                    {
+                        if (!LengthBasedAttributeIsTrackedForSubstitution(memberType, location, attributeType, ref attributeFullQualifiedName))
+                        {
+                            continue;
+                        }
+                    }
+                    else if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.CompareAttributeSymbol))
+                    {
+                        TrackCompareAttributeForSubstitution(attribute, modelType, ref attributeFullQualifiedName);
+                    }
+                    else if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.RangeAttributeSymbol))
+                    {
+                        TrackRangeAttributeForSubstitution(attribute, memberType, ref attributeFullQualifiedName);
+                    }
+
+                    var validationAttr = new ValidationAttributeInfo(attributeFullQualifiedName);
                     validationAttrs.Add(validationAttr);
 
                     ImmutableArray<IParameterSymbol> parameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
@@ -567,6 +589,79 @@ namespace Microsoft.Extensions.Options.Generators
             return null;
         }
 
+        private bool LengthBasedAttributeIsTrackedForSubstitution(ITypeSymbol memberType, Location location, ITypeSymbol attributeType, ref string attributeFullQualifiedName)
+        {
+            if (memberType.SpecialType == SpecialType.System_String || ConvertTo(memberType, _symbolHolder.ICollectionSymbol))
+            {
+                _optionsSourceGenContext.EnsureTrackingAttribute(attributeType.Name, createValue: false, out _);
+            }
+            else if (ParserUtilities.TypeHasProperty(memberType, "Count", SpecialType.System_Int32))
+            {
+                _optionsSourceGenContext.EnsureTrackingAttribute(attributeType.Name, createValue: true, out HashSet<object>? trackedTypeList);
+                trackedTypeList!.Add(memberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+            }
+            else
+            {
+                Diag(DiagDescriptors.IncompatibleWithTypeForValidationAttribute, location, attributeType.Name, memberType.Name);
+                return false;
+            }
+
+            attributeFullQualifiedName = $"{Emitter.StaticGeneratedValidationAttributesClassesNamespace}.{Emitter.StaticAttributeClassNamePrefix}{_optionsSourceGenContext.Suffix}_{attributeType.Name}";
+            return true;
+        }
+
+        private void TrackCompareAttributeForSubstitution(AttributeData attribute, ITypeSymbol modelType, ref string attributeFullQualifiedName)
+        {
+            ImmutableArray<IParameterSymbol> constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
+            if (constructorParameters.Length == 1 && constructorParameters[0].Name == "otherProperty" && constructorParameters[0].Type.SpecialType == SpecialType.System_String)
+            {
+                _optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: true, out HashSet<object>? trackedTypeList);
+                trackedTypeList!.Add((modelType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), (string)attribute.ConstructorArguments[0].Value!));
+                attributeFullQualifiedName = $"{Emitter.StaticGeneratedValidationAttributesClassesNamespace}.{Emitter.StaticAttributeClassNamePrefix}{_optionsSourceGenContext.Suffix}_{attribute.AttributeClass!.Name}";
+            }
+        }
+
+        private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSymbol memberType, ref string attributeFullQualifiedName)
+        {
+            ImmutableArray<IParameterSymbol> constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty;
+            SpecialType argumentSpecialType = SpecialType.None;
+            if (constructorParameters.Length == 2)
+            {
+                argumentSpecialType = constructorParameters[0].Type.SpecialType;
+            }
+            else if (constructorParameters.Length == 3)
+            {
+                object? argumentValue = null;
+                for (int i = 0; i < constructorParameters.Length; i++)
+                {
+                    if (constructorParameters[i].Name == "type")
+                    {
+                        argumentValue = attribute.ConstructorArguments[i].Value;
+                        break;
+                    }
+                }
+
+                if (argumentValue is INamedTypeSymbol namedTypeSymbol && OptionsSourceGenContext.IsConvertibleBasicType(namedTypeSymbol))
+                {
+                    argumentSpecialType = namedTypeSymbol.SpecialType;
+                }
+            }
+
+            ITypeSymbol typeSymbol = memberType;
+            if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
+            {
+                typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];
+            }
+
+            if (argumentSpecialType != SpecialType.None &&
+                OptionsSourceGenContext.IsConvertibleBasicType(typeSymbol) &&
+                (constructorParameters.Length != 3 || typeSymbol.SpecialType == argumentSpecialType)) // When type is provided as a parameter, it has to match the property type.
+            {
+                _optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: false, out _);
+                attributeFullQualifiedName = $"{Emitter.StaticGeneratedValidationAttributesClassesNamespace}.{Emitter.StaticAttributeClassNamePrefix}{_optionsSourceGenContext.Suffix}_{attribute.AttributeClass!.Name}";
+            }
+        }
+
         private string? AddSynthesizedValidator(ITypeSymbol modelType, ISymbol member, Location location, ITypeSymbol validatorType)
         {
             var mt = modelType.WithNullableAnnotation(NullableAnnotation.None);
index d79ad4c..6752025 100644 (file)
@@ -68,6 +68,29 @@ namespace Microsoft.Extensions.Options.Generators
             return false;
         }
 
+        internal static bool TypeHasProperty(ITypeSymbol typeSymbol, string propertyName, SpecialType returnType)
+        {
+            ITypeSymbol? type = typeSymbol;
+            do
+            {
+                if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
+                {
+                    type = ((INamedTypeSymbol)type).TypeArguments[0]; // extract the T from a Nullable<T>
+                }
+
+                if (type.GetMembers(propertyName).OfType<IPropertySymbol>().Any(property =>
+                                                                                property.Type.SpecialType == returnType && property.DeclaredAccessibility == Accessibility.Public &&
+                                                                                !property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty))
+                {
+                    return true;
+                }
+
+                type = type.BaseType;
+            } while (type is not null && type.SpecialType != SpecialType.System_Object);
+
+            return false;
+        }
+
         // Check if parameter has either simplified (i.e. "int?") or explicit (Nullable<int>) nullable type declaration:
         internal static bool IsNullableOfT(this ITypeSymbol type)
             => type.SpecialType == SpecialType.System_Nullable_T || type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
index 6293431..7100030 100644 (file)
   <data name="OptionsUnsupportedLanguageVersionMessage" xml:space="preserve">
     <value>The options validation source generator is not available in C# {0}. Please use language version {1} or greater.</value>
   </data>
+  <data name="TypeCannotBeUsedWithTheValidationAttributeTitle" xml:space="preserve">
+    <value>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</value>
+  </data>
+  <data name="TypeCannotBeUsedWithTheValidationAttributeMessage" xml:space="preserve">
+    <value>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</value>
+  </data>
 </root>
index 953376c..77769cb 100644 (file)
         <target state="translated">U člena potenciálně chybí přenositelné ověření.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Typ validátoru {0} nemá konstruktor bez parametrů.</target>
index bbd4d29..e5defa5 100644 (file)
         <target state="translated">Dem Member fehlt möglicherweise die transitive Validierung.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Der Validierungssteuerelementtyp "{0}" hat keinen parameterlosen Konstruktor.</target>
index 0c46615..728e01d 100644 (file)
         <target state="translated">Posiblemente falta la validación transitiva en el miembro.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">El tipo de validador {0} no tiene un constructor sin parámetros.</target>
index e1996ee..d7f738a 100644 (file)
         <target state="translated">Le membre n’a peut-être pas de validation transitive.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Le type de validateur {0} n’a pas de constructeur sans paramètre.</target>
index edcf16b..d3903a9 100644 (file)
         <target state="translated">Il membro potrebbe non avere una convalida transitiva.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Il tipo di convalida {0} non dispone di un costruttore senza parametri.</target>
index 89b2f23..2afc63b 100644 (file)
         <target state="translated">メンバーに推移性の検証がない可能性があります。</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">バリデーター型 {0} にパラメーターなしのコンストラクターがありません。</target>
index 817bc64..6bb342f 100644 (file)
         <target state="translated">멤버에 전이적 유효성 검사가 누락되었을 수 있습니다.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">유효성 검사기 형식 {0}은(는) 매개 변수가 없는 생성자가 없습니다.</target>
index 190ca17..1262d4d 100644 (file)
         <target state="translated">W przypadku elementu członkowskiego może potencjalnie brakować weryfikacji przechodniej.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Typ modułu sprawdzania poprawności {0} nie ma konstruktora bez parametrów.</target>
index 24a4203..ced617b 100644 (file)
         <target state="translated">Membro potencialmente ausente na validação transitiva.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">O tipo de validador {0} não tem um construtor sem parâmetros.</target>
index f14d718..cf3c217 100644 (file)
         <target state="translated">Возможно, в элементе отсутствует транзитивная проверка.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">Тип проверяющего элемента управления {0} не имеет конструктора без параметров.</target>
index 79e094b..b9545e2 100644 (file)
         <target state="translated">Üyede geçişli doğrulama eksik olabilir.</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">{0} doğrulayıcı türü parametresiz bir oluşturucuya sahip değil.</target>
index 7688e0e..98d7e55 100644 (file)
         <target state="translated">成员可能缺少可传递验证。</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">验证程序类型 {0} 没有无参数构造函数。</target>
index 663bff9..a3b9657 100644 (file)
         <target state="translated">成員可能遺漏轉移的驗證。</target>
         <note />
       </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeMessage">
+        <source>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</source>
+        <target state="new">The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</target>
+        <note />
+      </trans-unit>
+      <trans-unit id="TypeCannotBeUsedWithTheValidationAttributeTitle">
+        <source>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</source>
+        <target state="new">The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ValidatorsNeedSimpleConstructorMessage">
         <source>Validator type {0} doesn't have a parameterless constructor.</source>
         <target state="translated">驗證程式類型 {0} 沒有無參數建構函式。</target>
index 55d382e..3447a07 100644 (file)
@@ -11,6 +11,13 @@ namespace Microsoft.Extensions.Options.Generators
     internal sealed record class SymbolHolder(
         INamedTypeSymbol OptionsValidatorSymbol,
         INamedTypeSymbol ValidationAttributeSymbol,
+        INamedTypeSymbol MaxLengthAttributeSymbol,
+        INamedTypeSymbol MinLengthAttributeSymbol,
+        INamedTypeSymbol CompareAttributeSymbol,
+        INamedTypeSymbol? LengthAttributeSymbol,
+        INamedTypeSymbol? UnconditionalSuppressMessageAttributeSymbol,
+        INamedTypeSymbol RangeAttributeSymbol,
+        INamedTypeSymbol ICollectionSymbol,
         INamedTypeSymbol DataTypeAttributeSymbol,
         INamedTypeSymbol ValidateOptionsSymbol,
         INamedTypeSymbol IValidatableObjectSymbol,
index 94035ce..ea55622 100644 (file)
@@ -9,6 +9,12 @@ namespace Microsoft.Extensions.Options.Generators
     {
         public const string OptionsValidatorAttribute = "Microsoft.Extensions.Options.OptionsValidatorAttribute";
         internal const string ValidationAttribute = "System.ComponentModel.DataAnnotations.ValidationAttribute";
+        internal const string MaxLengthAttribute = "System.ComponentModel.DataAnnotations.MaxLengthAttribute";
+        internal const string MinLengthAttribute = "System.ComponentModel.DataAnnotations.MinLengthAttribute";
+        internal const string CompareAttribute = "System.ComponentModel.DataAnnotations.CompareAttribute";
+        internal const string LengthAttribute = "System.ComponentModel.DataAnnotations.LengthAttribute";
+        internal const string RangeAttribute = "System.ComponentModel.DataAnnotations.RangeAttribute";
+        internal const string ICollectionType = "System.Collections.ICollection";
         internal const string DataTypeAttribute = "System.ComponentModel.DataAnnotations.DataTypeAttribute";
         internal const string IValidatableObjectType = "System.ComponentModel.DataAnnotations.IValidatableObject";
         internal const string IValidateOptionsType = "Microsoft.Extensions.Options.IValidateOptions`1";
@@ -16,6 +22,7 @@ namespace Microsoft.Extensions.Options.Generators
         internal const string ValidateObjectMembersAttribute = "Microsoft.Extensions.Options.ValidateObjectMembersAttribute";
         internal const string ValidateEnumeratedItemsAttribute = "Microsoft.Extensions.Options.ValidateEnumeratedItemsAttribute";
         internal const string GenericIEnumerableType = "System.Collections.Generic.IEnumerable`1";
+        internal const string UnconditionalSuppressMessageAttributeType = "System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute";
 
         public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHolder)
         {
@@ -24,6 +31,12 @@ namespace Microsoft.Extensions.Options.Generators
             // required
             var optionsValidatorSymbol = GetSymbol(OptionsValidatorAttribute);
             var validationAttributeSymbol = GetSymbol(ValidationAttribute);
+            var maxLengthAttributeSymbol = GetSymbol(MaxLengthAttribute);
+            var minLengthAttributeSymbol = GetSymbol(MinLengthAttribute);
+            var compareAttributeSymbol = GetSymbol(CompareAttribute);
+            var lengthAttributeSymbol = GetSymbol(LengthAttribute);
+            var rangeAttributeSymbol = GetSymbol(RangeAttribute);
+            var iCollectionSymbol = GetSymbol(ICollectionType);
             var dataTypeAttributeSymbol = GetSymbol(DataTypeAttribute);
             var ivalidatableObjectSymbol = GetSymbol(IValidatableObjectType);
             var validateOptionsSymbol = GetSymbol(IValidateOptionsType);
@@ -31,10 +44,27 @@ namespace Microsoft.Extensions.Options.Generators
             var typeSymbol = GetSymbol(TypeOfType);
             var validateObjectMembersAttribute = GetSymbol(ValidateObjectMembersAttribute);
             var validateEnumeratedItemsAttribute = GetSymbol(ValidateEnumeratedItemsAttribute);
+            var unconditionalSuppressMessageAttributeSymbol = GetSymbol(UnconditionalSuppressMessageAttributeType);
+            if (unconditionalSuppressMessageAttributeSymbol is not null)
+            {
+                var containingAssemblyName = unconditionalSuppressMessageAttributeSymbol.ContainingAssembly.Identity.Name;
+                if (!containingAssemblyName.Equals("System.Private.CoreLib", System.StringComparison.OrdinalIgnoreCase) &&
+                    !containingAssemblyName.Equals("System.Runtime", System.StringComparison.OrdinalIgnoreCase))
+                {
+                    // The compilation returns UnconditionalSuppressMessageAttribute symbol even if the attribute is not available like the case when running on .NET Framework.
+                    // We need to make sure that the attribute is really available by checking the containing assembly which in .NET Core will be either System.Private.CoreLib or System.Runtime.
+                    unconditionalSuppressMessageAttributeSymbol = null;
+                }
+            }
 
     #pragma warning disable S1067 // Expressions should not be too complex
             if (optionsValidatorSymbol == null ||
                 validationAttributeSymbol == null ||
+                maxLengthAttributeSymbol == null ||
+                minLengthAttributeSymbol == null ||
+                compareAttributeSymbol == null ||
+                rangeAttributeSymbol == null ||
+                iCollectionSymbol == null ||
                 dataTypeAttributeSymbol == null ||
                 ivalidatableObjectSymbol == null ||
                 validateOptionsSymbol == null ||
@@ -51,6 +81,13 @@ namespace Microsoft.Extensions.Options.Generators
             symbolHolder = new(
                 optionsValidatorSymbol,
                 validationAttributeSymbol,
+                maxLengthAttributeSymbol,
+                minLengthAttributeSymbol,
+                compareAttributeSymbol,
+                lengthAttributeSymbol,
+                unconditionalSuppressMessageAttributeSymbol,
+                rangeAttributeSymbol,
+                iCollectionSymbol,
                 dataTypeAttributeSymbol,
                 validateOptionsSymbol,
                 ivalidatableObjectSymbol,
index 7225edf..c7ea3e0 100644 (file)
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
     <Reference Include="System.ComponentModel.DataAnnotations" />
-    <PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />  
+    <PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\gen\Microsoft.Extensions.Options.SourceGeneration.csproj"
                       ReferenceOutputAssembly="false"
+                      SetTargetFramework="TargetFramework=netstandard2.0"
                       PackAsAnalyzer="true" />
   </ItemGroup>
 
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/DataAnnotationAttributesWithParams.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/DataAnnotationAttributesWithParams.g.cs
new file mode 100644 (file)
index 0000000..c51c551
--- /dev/null
@@ -0,0 +1,135 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace Test
+{
+    partial class MyOptionsValidator
+    {
+        /// <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")]
+        [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+             Justification = "The created ValidationContext object is used in a way that never call reflection")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Test.MyOptions 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P1";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P1" : $"{name}.P1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P2" : $"{name}.P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P3";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P3" : $"{name}.P3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P4";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P4" : $"{name}.P4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute(
+            (int)10,
+            (int)20);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.AllowedValuesAttribute A3 = new global::System.ComponentModel.DataAnnotations.AllowedValuesAttribute(
+            (int)10, (int)20, (int)30);
+
+        internal static readonly global::System.ComponentModel.DataAnnotations.DeniedValuesAttribute A4 = new global::System.ComponentModel.DataAnnotations.DeniedValuesAttribute(
+            "One", "Ten", "Hundred");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Validators
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__LengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'.";
+        public __SourceGen__LengthAttribute(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; }
+        public int MinimumLength { get; }
+        public int MaximumLength { get; }
+        public override bool IsValid(object? value)
+        {
+            if (MinimumLength < 0)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater.");
+            }
+            if (MaximumLength < MinimumLength)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs
new file mode 100644 (file)
index 0000000..2c5af12
--- /dev/null
@@ -0,0 +1,175 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace HelloWorld
+{
+    partial struct MyOptionsValidator
+    {
+        /// <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")]
+        [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+             Justification = "The created ValidationContext object is used in a way that never call reflection")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::HelloWorld.MyOptions 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val1" : $"{name}.Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val2" : $"{name}.Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
+            (int)1,
+            (int)3);
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Validators
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs
new file mode 100644 (file)
index 0000000..9dc3ded
--- /dev/null
@@ -0,0 +1,173 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace HelloWorld
+{
+    partial struct MyOptionsValidator
+    {
+        /// <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::HelloWorld.MyOptions 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "Val1";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val1" : $"{name}.Val1";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "Val2";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val2" : $"{name}.Val2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Attributes
+    {
+        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
+            (int)1,
+            (int)3);
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Validators
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs
new file mode 100644 (file)
index 0000000..cc9864a
--- /dev/null
@@ -0,0 +1,471 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace ValidationTest
+{
+    partial class OptionsUsingGeneratedAttributesValidator
+    {
+        /// <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")]
+        [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+             Justification = "The created ValidationContext object is used in a way that never call reflection")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValidationTest.OptionsUsingGeneratedAttributes 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P0";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P0" : $"{name}.P0";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P0, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P1";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P1" : $"{name}.P1";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P2" : $"{name}.P2";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P3";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P3" : $"{name}.P3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P4";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P4" : $"{name}.P4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P5";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P5" : $"{name}.P5";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P6";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P6" : $"{name}.P6";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A5);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P7";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P7" : $"{name}.P7";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P7, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P8";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P8" : $"{name}.P8";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P8, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P9";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P9" : $"{name}.P9";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P9, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P10";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P10" : $"{name}.P10";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P10, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P11";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P11" : $"{name}.P11";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P11, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P12";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P12" : $"{name}.P12";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P12, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Attributes_2C497155
+    {
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_LengthAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_LengthAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MinLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MinLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MaxLengthAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MaxLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute(
+            "P5");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Validators_2C497155
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_CompareAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "'{0}' and '{1}' do not match.";
+        public __SourceGen__2C497155_CompareAttribute(string otherProperty) : base(() => DefaultErrorMessageString)
+        {
+            if (otherProperty == null)
+            {
+                throw new global::System.ArgumentNullException(nameof(otherProperty));
+            }
+            OtherProperty = otherProperty;
+        }
+        public string OtherProperty { get; }
+        public override bool RequiresValidationContext => true;
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationResult? IsValid(object? value, global::System.ComponentModel.DataAnnotations.ValidationContext validationContext)
+        {
+            bool result = true;
+
+            if (validationContext.ObjectInstance is global::ValidationTest.OptionsUsingGeneratedAttributes && OtherProperty == "P5")
+            {
+                result = Equals(value, ((global::ValidationTest.OptionsUsingGeneratedAttributes)validationContext.ObjectInstance).P5);
+            }
+
+            if (!result)
+            {
+                string[]? memberNames = validationContext.MemberName is null ? null : new string[] { validationContext.MemberName };
+                return new global::System.ComponentModel.DataAnnotations.ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
+            }
+
+            return null;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_LengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'.";
+        public __SourceGen__2C497155_LengthAttribute(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; }
+        public int MinimumLength { get; }
+        public int MaximumLength { get; }
+        public override bool IsValid(object? value)
+        {
+            if (MinimumLength < 0)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater.");
+            }
+            if (MaximumLength < MinimumLength)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private const int MaxAllowableLength = -1;
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
+        public __SourceGen__2C497155_MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public __SourceGen__2C497155_MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
+        public int Length { get; }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+        public override bool IsValid(object? value)
+        {
+            if (Length == 0 || Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.");
+            }
+            if (value == null || MaxAllowableLength == Length)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length <= Length;
+        }
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__2C497155_MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__2C497155_RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__2C497155_RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__2C497155_RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs
new file mode 100644 (file)
index 0000000..2a33e51
--- /dev/null
@@ -0,0 +1,471 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace ValidationTest
+{
+    partial class OptionsUsingGeneratedAttributesValidator
+    {
+        /// <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")]
+        [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+             Justification = "The created ValidationContext object is used in a way that never call reflection")]
+        public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValidationTest.OptionsUsingGeneratedAttributes 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P0";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P0" : $"{name}.P0";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P0, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P1";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P1" : $"{name}.P1";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P2";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.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);
+            }
+
+            context.MemberName = "P3";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P3" : $"{name}.P3";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P4";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P4" : $"{name}.P4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P5";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P5" : $"{name}.P5";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P6";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P6" : $"{name}.P6";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P7";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P7" : $"{name}.P7";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P7, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P8";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P8" : $"{name}.P8";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P8, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P9";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P9" : $"{name}.P9";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P9, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P10";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P10" : $"{name}.P10";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P10, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P11";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P11" : $"{name}.P11";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P11, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P12";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P12" : $"{name}.P12";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P12, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Attributes
+    {
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute(
+            "P5");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Validators
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
+    file class __SourceGen__CompareAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "'{0}' and '{1}' do not match.";
+        public __SourceGen__CompareAttribute(string otherProperty) : base(() => DefaultErrorMessageString)
+        {
+            if (otherProperty == null)
+            {
+                throw new global::System.ArgumentNullException(nameof(otherProperty));
+            }
+            OtherProperty = otherProperty;
+        }
+        public string OtherProperty { get; }
+        public override bool RequiresValidationContext => true;
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationResult? IsValid(object? value, global::System.ComponentModel.DataAnnotations.ValidationContext validationContext)
+        {
+            bool result = true;
+
+            if (validationContext.ObjectInstance is global::ValidationTest.OptionsUsingGeneratedAttributes && OtherProperty == "P5")
+            {
+                result = Equals(value, ((global::ValidationTest.OptionsUsingGeneratedAttributes)validationContext.ObjectInstance).P5);
+            }
+
+            if (!result)
+            {
+                string[]? memberNames = validationContext.MemberName is null ? null : new string[] { validationContext.MemberName };
+                return new global::System.ComponentModel.DataAnnotations.ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
+            }
+
+            return null;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__LengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'.";
+        public __SourceGen__LengthAttribute(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; }
+        public int MinimumLength { get; }
+        public int MaximumLength { get; }
+        public override bool IsValid(object? value)
+        {
+            if (MinimumLength < 0)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater.");
+            }
+            if (MaximumLength < MinimumLength)
+            {
+                throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private const int MaxAllowableLength = -1;
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
+        public __SourceGen__MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public __SourceGen__MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
+        public int Length { get; }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+        public override bool IsValid(object? value)
+        {
+            if (Length == 0 || Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.");
+            }
+            if (value == null || MaxAllowableLength == Length)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length <= Length;
+        }
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs
new file mode 100644 (file)
index 0000000..7f5eb90
--- /dev/null
@@ -0,0 +1,386 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace ValidationTest
+{
+    partial class OptionsUsingGeneratedAttributesValidator
+    {
+        /// <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::ValidationTest.OptionsUsingGeneratedAttributes 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P3";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P3" : $"{name}.P3";
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A1);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P4";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P4" : $"{name}.P4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P5";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P5" : $"{name}.P5";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P6";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P6" : $"{name}.P6";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P7";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P7" : $"{name}.P7";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P7, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P8";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P8" : $"{name}.P8";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P8, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P9";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P9" : $"{name}.P9";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P9, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P10";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P10" : $"{name}.P10";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P10, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P11";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P11" : $"{name}.P11";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P11, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P12";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P12" : $"{name}.P12";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P12, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Attributes_2C497155
+    {
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MinLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MaxLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_MaxLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute(
+            "P5");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    internal static class __Validators_2C497155
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_CompareAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "'{0}' and '{1}' do not match.";
+        public __SourceGen__2C497155_CompareAttribute(string otherProperty) : base(() => DefaultErrorMessageString)
+        {
+            if (otherProperty == null)
+            {
+                throw new global::System.ArgumentNullException(nameof(otherProperty));
+            }
+            OtherProperty = otherProperty;
+        }
+        public string OtherProperty { get; }
+        public override bool RequiresValidationContext => true;
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationResult? IsValid(object? value, global::System.ComponentModel.DataAnnotations.ValidationContext validationContext)
+        {
+            bool result = true;
+
+            if (validationContext.ObjectInstance is global::ValidationTest.OptionsUsingGeneratedAttributes && OtherProperty == "P5")
+            {
+                result = Equals(value, ((global::ValidationTest.OptionsUsingGeneratedAttributes)validationContext.ObjectInstance).P5);
+            }
+
+            if (!result)
+            {
+                string[]? memberNames = validationContext.MemberName is null ? null : new string[] { validationContext.MemberName };
+                return new global::System.ComponentModel.DataAnnotations.ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
+            }
+
+            return null;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private const int MaxAllowableLength = -1;
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
+        public __SourceGen__2C497155_MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public __SourceGen__2C497155_MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
+        public int Length { get; }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+        public override bool IsValid(object? value)
+        {
+            if (Length == 0 || Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.");
+            }
+            if (value == null || MaxAllowableLength == Length)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length <= Length;
+        }
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__2C497155_MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    internal class __SourceGen__2C497155_RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__2C497155_RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__2C497155_RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__2C497155_RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs
new file mode 100644 (file)
index 0000000..3ab56e2
--- /dev/null
@@ -0,0 +1,386 @@
+
+    // <auto-generated/>
+    #nullable enable
+    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
+    namespace ValidationTest
+{
+    partial class OptionsUsingGeneratedAttributesValidator
+    {
+        /// <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::ValidationTest.OptionsUsingGeneratedAttributes 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
+            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
+
+            context.MemberName = "P3";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.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);
+            }
+
+            context.MemberName = "P4";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P4" : $"{name}.P4";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P5";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P5" : $"{name}.P5";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P6";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P6" : $"{name}.P6";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P7";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P7" : $"{name}.P7";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P7, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P8";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P8" : $"{name}.P8";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P8, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P9";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P9" : $"{name}.P9";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P9, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P10";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P10" : $"{name}.P10";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P10, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P11";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P11" : $"{name}.P11";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P11, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            context.MemberName = "P12";
+            context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P12" : $"{name}.P12";
+            validationResults.Clear();
+            validationAttributes.Clear();
+            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
+            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P12, context, validationResults, validationAttributes))
+            {
+                (builder ??= new()).AddResults(validationResults);
+            }
+
+            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
+        }
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Attributes
+    {
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
+            (int)1,
+            (int)3);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute(
+            (int)5);
+
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute(
+            "P5");
+    }
+}
+namespace __OptionValidationStaticInstances
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    file static class __Validators
+    {
+    }
+}
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false)]
+    file class __SourceGen__CompareAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "'{0}' and '{1}' do not match.";
+        public __SourceGen__CompareAttribute(string otherProperty) : base(() => DefaultErrorMessageString)
+        {
+            if (otherProperty == null)
+            {
+                throw new global::System.ArgumentNullException(nameof(otherProperty));
+            }
+            OtherProperty = otherProperty;
+        }
+        public string OtherProperty { get; }
+        public override bool RequiresValidationContext => true;
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationResult? IsValid(object? value, global::System.ComponentModel.DataAnnotations.ValidationContext validationContext)
+        {
+            bool result = true;
+
+            if (validationContext.ObjectInstance is global::ValidationTest.OptionsUsingGeneratedAttributes && OtherProperty == "P5")
+            {
+                result = Equals(value, ((global::ValidationTest.OptionsUsingGeneratedAttributes)validationContext.ObjectInstance).P5);
+            }
+
+            if (!result)
+            {
+                string[]? memberNames = validationContext.MemberName is null ? null : new string[] { validationContext.MemberName };
+                return new global::System.ComponentModel.DataAnnotations.ValidationResult(FormatErrorMessage(validationContext.DisplayName), memberNames);
+            }
+
+            return null;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, OtherProperty);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private const int MaxAllowableLength = -1;
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
+        public __SourceGen__MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public __SourceGen__MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
+        public int Length { get; }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+        public override bool IsValid(object? value)
+        {
+            if (Length == 0 || Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.");
+            }
+            if (value == null || MaxAllowableLength == Length)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length <= Length;
+        }
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else if (value is global::ValidationTest.FakeCount)
+            {
+                length = ((global::ValidationTest.FakeCount)value).Count;
+            }
+            else if (value is global::ValidationTest.FakeCountChild)
+            {
+                length = ((global::ValidationTest.FakeCountChild)value).Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
index aa51c9d..da89312 100644 (file)
@@ -52,77 +52,15 @@ public class EmitterTests
             }
             """;
 
-        string generatedSource = """
-
-    // <auto-generated/>
-    #nullable enable
-    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
-    namespace HelloWorld
-{
-    partial struct MyOptionsValidator
-    {
-        /// <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::HelloWorld.MyOptions 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
-
-            context.MemberName = "Val1";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val1" : $"{name}.Val1";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            context.MemberName = "Val2";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.Val2" : $"{name}.Val2";
-            validationResults.Clear();
-            validationAttributes.Clear();
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
-        }
-    }
-}
-namespace __OptionValidationStaticInstances
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    file static class __Attributes
-    {
-        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
-
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A2 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
-            (int)1,
-            (int)3);
-    }
-}
-namespace __OptionValidationStaticInstances
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    file static class __Validators
-    {
-    }
-}
-
-""";
-
         var (diagnostics, generatedSources) = await RunGeneratorOnOptionsSource(source);
         Assert.Empty(diagnostics);
         _ = Assert.Single(generatedSources);
 
+#if NETCOREAPP
+        string generatedSource = File.ReadAllText(@"Baselines/EmitterWithCustomValidator.netcore.g.cs");
+#else
+        string generatedSource = File.ReadAllText(@"Baselines/EmitterWithCustomValidator.netfx.g.cs");
+#endif // NETCOREAPP
         Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n"));
     }
 
@@ -1443,7 +1381,7 @@ namespace __OptionValidationStaticInstances
             Assert.Single(diagnostics);
             Assert.Equal(DiagDescriptors.InaccessibleValidationAttribute.Id, diagnostics[0].Id);
             string generatedSource = generatedSources[0].SourceText.ToString();
-            Assert.Contains("global::System.ComponentModel.DataAnnotations.RangeAttribute", generatedSource);
+            Assert.Contains("__OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute", generatedSource);
             Assert.Contains("global::System.ComponentModel.DataAnnotations.RequiredAttribute", generatedSource);
             Assert.DoesNotContain("Timeout", generatedSource);
 
@@ -1666,113 +1604,22 @@ namespace __OptionValidationStaticInstances
         Assert.Empty(diagnostics);
         Assert.Single(generatedSources);
 
-        var generatedSource = """
-
-    // <auto-generated/>
-    #nullable enable
-    #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
-    namespace Test
-{
-    partial class MyOptionsValidator
-    {
-        /// <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::Test.MyOptions 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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
-            var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
-
-            context.MemberName = "P1";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P1" : $"{name}.P1";
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            context.MemberName = "P2";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P2" : $"{name}.P2";
-            validationResults.Clear();
-            validationAttributes.Clear();
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            context.MemberName = "P3";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P3" : $"{name}.P3";
-            validationResults.Clear();
-            validationAttributes.Clear();
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            context.MemberName = "P4";
-            context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P4" : $"{name}.P4";
-            validationResults.Clear();
-            validationAttributes.Clear();
-            validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4);
-            if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
-            {
-                (builder ??= new()).AddResults(validationResults);
-            }
-
-            return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
-        }
-    }
-}
-namespace __OptionValidationStaticInstances
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    file static class __Attributes
-    {
-        internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
-
-        internal static readonly global::System.ComponentModel.DataAnnotations.LengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.LengthAttribute(
-            (int)10,
-            (int)20);
-
-        internal static readonly global::System.ComponentModel.DataAnnotations.AllowedValuesAttribute A3 = new global::System.ComponentModel.DataAnnotations.AllowedValuesAttribute(
-            (int)10, (int)20, (int)30);
-
-        internal static readonly global::System.ComponentModel.DataAnnotations.DeniedValuesAttribute A4 = new global::System.ComponentModel.DataAnnotations.DeniedValuesAttribute(
-            "One", "Ten", "Hundred");
-    }
-}
-namespace __OptionValidationStaticInstances
-{
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
-    file static class __Validators
-    {
-    }
-}
-
-""";
+        string generatedSource = File.ReadAllText(@"Baselines/DataAnnotationAttributesWithParams.g.cs");
         Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n"));
     }
 
-    private static CSharpCompilation CreateCompilationForOptionsSource(string assemblyName, string source, string? refAssemblyPath = null)
+    private static CSharpCompilation CreateCompilationForOptionsSource(string assemblyName, string source, string? refAssemblyPath = null, LanguageVersion languageVersion = LanguageVersion.Default)
     {
         // Ensure the generated source compiles
         var compilation = CSharpCompilation
-                .Create(Path.GetRandomFileName()+".dll", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
+                .Create($"{assemblyName}.dll", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                 .AddReferences(MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "System.Runtime").Location))
                 .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
                 .AddReferences(MetadataReference.CreateFromFile(typeof(RequiredAttribute).Assembly.Location))
                 .AddReferences(MetadataReference.CreateFromFile(typeof(OptionsValidatorAttribute).Assembly.Location))
                 .AddReferences(MetadataReference.CreateFromFile(typeof(IValidateOptions<object>).Assembly.Location))
                 .AddReferences(MetadataReference.CreateFromFile(typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).Assembly.Location))
-                .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
+                .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(languageVersion)));
 
         if (refAssemblyPath is not null)
         {
@@ -1861,4 +1708,109 @@ namespace __OptionValidationStaticInstances
 
         return result;
     }
+
+    [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+    [InlineData(LanguageVersion.CSharp10)]
+    [InlineData(LanguageVersion.CSharp11)]
+    public async Task GeneratedAttributesTest(LanguageVersion languageVersion)
+    {
+
+#if NETCOREAPP
+        string lengthAttribute = $$"""
+                    [LengthAttribute(1, 3)]
+                    public string? P0 { get; set; }
+
+                    [LengthAttribute(1, 3)]
+                    public FakeCount? P1 { get; set; }
+
+                    [LengthAttribute(1, 3)]
+                    public FakeCountChild? P2 { get; set; }
+        """;
+#else
+string lengthAttribute = "";
+#endif //NETCOREAPP
+
+        string source = $$"""
+            using System.Collections.Generic;
+            using Microsoft.Extensions.Options;
+            using System.ComponentModel.DataAnnotations;
+
+            #nullable enable
+
+            namespace ValidationTest
+            {
+                public class FakeCount
+                {
+                    public FakeCount(int count) { Count = count; }
+                    public int Count { get; }
+                }
+                public class FakeCountChild : FakeCount
+                {
+                    public FakeCountChild(int count) : base(count) { }
+                }
+
+                public class OptionsUsingGeneratedAttributes
+                {
+                    {{lengthAttribute}}
+
+                    [RangeAttribute(1, 3)]
+                    public int P3 { get; set; }
+
+                    [MinLengthAttribute(5)]
+                    public string? P4 { get; set; }
+
+                    [MaxLengthAttribute(5)]
+                    public string? P5 { get; set; }
+
+                    [CompareAttribute("P5")]
+                    public string? P6 { get; set; }
+
+                    [MinLengthAttribute(5)]
+                    public FakeCount? P7 { get; set; }
+
+                    [MinLengthAttribute(5)]
+                    public FakeCountChild? P8 { get; set; }
+
+                    [MaxLengthAttribute(5)]
+                    public FakeCount? P9 { get; set; }
+
+                    [MaxLengthAttribute(5)]
+                    public FakeCountChild? P10 { get; set; }
+
+                    [MinLengthAttribute(5)]
+                    public List<string>? P11 { get; set; }
+
+                    [MaxLengthAttribute(5)]
+                    public List<string>? P12 { get; set; }
+                }
+
+                [OptionsValidator]
+                public sealed partial class OptionsUsingGeneratedAttributesValidator : IValidateOptions<OptionsUsingGeneratedAttributes>
+                {
+                }
+            }
+        """;
+
+        var (diagnostics, generatedSources) = await RunGeneratorOnOptionsSource(source, null, languageVersion);
+        Assert.Empty(diagnostics);
+        Assert.Single(generatedSources);
+
+        string emittedSource = generatedSources[0].SourceText.ToString();
+        SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(emittedSource, new CSharpParseOptions(languageVersion));
+        var diags = syntaxTree.GetDiagnostics().ToArray();
+        Assert.Empty(diags);
+
+#if NETCOREAPP
+        string generatedSource = File.ReadAllText(languageVersion == LanguageVersion.CSharp10  ? @"Baselines/GeneratedAttributesTest.netcore.lang10.g.cs" : @"Baselines/GeneratedAttributesTest.netcore.lang11.g.cs");
+#else
+        string generatedSource = File.ReadAllText(languageVersion == LanguageVersion.CSharp10  ? @"Baselines/GeneratedAttributesTest.netfx.lang10.g.cs" : @"Baselines/GeneratedAttributesTest.netfx.lang11.g.cs");
+#endif // NET8_0_OR_GREATER
+        Assert.Equal(generatedSource.Replace("\r\n", "\n"), emittedSource.Replace("\r\n", "\n"));
+
+        CSharpCompilation compilation = CreateCompilationForOptionsSource(Path.GetRandomFileName(), source + emittedSource, refAssemblyPath: null, languageVersion);
+        var emitResult = compilation.Emit(new MemoryStream());
+
+        Assert.True(emitResult.Success);
+        // Console.WriteLine(emittedSource);
+    }
 }
index d76cbd4..f3a5f33 100644 (file)
@@ -14,6 +14,7 @@
     <Compile Include="$(CommonPath)..\tests\SourceGenerators\RoslynTestUtils.cs" Link="SourceGenerators\RoslynTestUtils.cs" />
     <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptorsBase.cs" Link="gen\DiagDescriptorsBase.cs" />
     <Compile Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\DiagDescriptors.cs" Link="gen\DiagDescriptors.cs" />
+    <Compile Remove="Baselines\**\*.cs" />
   </ItemGroup>
 
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
                               OutputItemType="Analyzer" ReferenceOutputAssembly="true" SetTargetFramework="TargetFramework=netstandard2.0"/>
   </ItemGroup>
 
+  <ItemGroup>
+    <Content Include="Baselines\**\*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
   <Target Name="FixIncrementalCoreCompileWithAnalyzers" BeforeTargets="CoreCompile">
     <ItemGroup>
       <CustomAdditionalCompileInputs Include="@(Analyzer)" />
index 6109bcc..78783d7 100644 (file)
@@ -266,6 +266,214 @@ namespace Microsoft.Gen.OptionsValidation.Unit.Test
             }, result.Failures);
         }
 #endif // NET8_0_OR_GREATER
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+        public void TestCustomGeneratedAttributes()
+        {
+            OptionsUsingGeneratedAttributes noFailures = new OptionsUsingGeneratedAttributes()
+            {
+#if NET8_0_OR_GREATER
+                P0 = "123",
+                P11 = new DateTime(2023, 2, 1),
+                P12 = 6,
+                P13 = 9,
+                P14 = new List<string>() { "1", "2" },
+                P15 = new FakeCount(5),
+                P16 = new FakeCountChild(5),
+                P17 = new int[] { 1, 2 },
+                P18 = new List<string>() { "1", "2", "3" },
+                P19 = new FakeCount(3),
+                P20 = new FakeCountChild(3),
+                P23 = new List<string>() { "1", "2", "3", "4" },
+                P24 = new FakeCount(4),
+                P25 = new FakeCountChild(4),
+#endif // NET8_0_OR_GREATER
+                P1 = 2,
+                P2 = "12345",
+                P3 = "12345",
+                P4 = "12345",
+                P5 = 4,
+                P6 = 4,
+                P7 = 15,
+                P8 = 15,
+                P9 = 2.5m,
+                P10 = 14.0,
+                P21 = new int[] { 1, 2, 3 },
+                P22 = new int[] { 1, 2, 3, 4 },
+                P26 = 14.0,
+            };
+            List<ValidationResult> results = new();
+            Assert.True(Validator.TryValidateObject(noFailures, new ValidationContext(noFailures), results, true));
+
+            OptionsUsingGeneratedAttributesValidator validator = new();
+            Assert.True(validator.Validate("OptionsUsingGeneratedAttributes", noFailures).Succeeded);
+
+            OptionsUsingGeneratedAttributes failing = new OptionsUsingGeneratedAttributes()
+            {
+#if NET8_0_OR_GREATER
+                P0 = "",
+                P11 = new DateTime(2023, 1, 1),
+                P12 = 5,
+                P13 = 10,
+                P14 = new List<string>() { "1" },
+                P15 = new FakeCount(1),
+                P16 = new FakeCountChild(11),
+                P17 = new int[] { 1 },
+                P18 = new List<string>() { "1", "2" },
+                P19 = new FakeCount(2),
+                P20 = new FakeCountChild(1),
+                P23 = new List<string>() { "1", "2", "3", "4", "5" },
+                P24 = new FakeCount(5),
+                P25 = new FakeCountChild(5),
+#endif // NET8_0_OR_GREATER
+                P1 = 4,
+                P2 = "1234",
+                P3 = "123456",
+                P4 = "12345",
+                P5 = 10,
+                P6 = 10,
+                P7 = 5,
+                P8 = 5,
+                P9 = 4.0m,
+                P10 = 20.0,
+                P21 = new int[] { 1, 2 },
+                P22 = new int[] { 1, 2, 3, 4, 5 },
+                P26 = 20.0,
+            };
+
+            Assert.False(Validator.TryValidateObject(failing, new ValidationContext(failing), results, true));
+
+            ValidateOptionsResult generatorResult = validator.Validate("OptionsUsingGeneratedAttributes", failing);
+            Assert.True(generatorResult.Failed);
+
+            Assert.Equal(new [] {
+#if NET8_0_OR_GREATER
+                "P0: The field OptionsUsingGeneratedAttributes.P0 must be a string or collection type with a minimum length of '1' and maximum length of '3'.",
+                "P11: The field OptionsUsingGeneratedAttributes.P11 must be between 1/30/2023 12:00:00 AM and 12/30/2023 12:00:00 AM.",
+                "P12: The field OptionsUsingGeneratedAttributes.P12 must be between 5 exclusive and 10.",
+                "P13: The field OptionsUsingGeneratedAttributes.P13 must be between 5 and 10 exclusive.",
+                "P14: The field OptionsUsingGeneratedAttributes.P14 must be a string or collection type with a minimum length of '2' and maximum length of '10'.",
+                "P15: The field OptionsUsingGeneratedAttributes.P15 must be a string or collection type with a minimum length of '2' and maximum length of '10'.",
+                "P16: The field OptionsUsingGeneratedAttributes.P16 must be a string or collection type with a minimum length of '2' and maximum length of '10'.",
+                "P17: The field OptionsUsingGeneratedAttributes.P17 must be a string or collection type with a minimum length of '2' and maximum length of '10'.",
+                "P18: The field OptionsUsingGeneratedAttributes.P18 must be a string or array type with a minimum length of '3'.",
+                "P19: The field OptionsUsingGeneratedAttributes.P19 must be a string or array type with a minimum length of '3'.",
+                "P20: The field OptionsUsingGeneratedAttributes.P20 must be a string or array type with a minimum length of '3'.",
+                "P23: The field OptionsUsingGeneratedAttributes.P23 must be a string or array type with a maximum length of '4'.",
+                "P24: The field OptionsUsingGeneratedAttributes.P24 must be a string or array type with a maximum length of '4'.",
+                "P25: The field OptionsUsingGeneratedAttributes.P25 must be a string or array type with a maximum length of '4'.",
+#endif // NET8_0_OR_GREATER
+                "P1: The field OptionsUsingGeneratedAttributes.P1 must be between 1 and 3.",
+                "P2: The field OptionsUsingGeneratedAttributes.P2 must be a string or array type with a minimum length of '5'.",
+                "P3: The field OptionsUsingGeneratedAttributes.P3 must be a string or array type with a maximum length of '5'.",
+                "P4: 'OptionsUsingGeneratedAttributes.P4' and 'P2' do not match.",
+                "P5: The field OptionsUsingGeneratedAttributes.P5 must be between 2 and 8.",
+                "P6: The field OptionsUsingGeneratedAttributes.P6 must be between 2 and 8.",
+                "P7: The field OptionsUsingGeneratedAttributes.P7 must be between 10 and 20.",
+                "P8: The field OptionsUsingGeneratedAttributes.P8 must be between 10 and 20.",
+                "P9: The field OptionsUsingGeneratedAttributes.P9 must be between 1.5 and 3.14.",
+                "P10: The field OptionsUsingGeneratedAttributes.P10 must be between 12.4 and 16.5.",
+                "P21: The field OptionsUsingGeneratedAttributes.P21 must be a string or array type with a minimum length of '3'.",
+                "P22: The field OptionsUsingGeneratedAttributes.P22 must be a string or array type with a maximum length of '4'.",
+                "P26: The field OptionsUsingGeneratedAttributes.P26 must be between 12.4 and 16.5.",
+            }, generatorResult.Failures);
+
+            Assert.Equal(results.Count(), generatorResult.Failures.Count());
+        }
+    }
+
+    public class FakeCount(int count) { public int Count { get { return count; } } }
+    public class FakeCountChild(int count) : FakeCount(count) { }
+
+    public class OptionsUsingGeneratedAttributes
+    {
+#if NET8_0_OR_GREATER
+        [LengthAttribute(1, 3)]
+        public string? P0 { get; set; }
+
+        [RangeAttribute(typeof(DateTime), "01/30/2023", "12/30/2023", ParseLimitsInInvariantCulture = true, ConvertValueInInvariantCulture = true)]
+        public DateTime P11 { get; set; }
+
+        [RangeAttribute(5, 10, MinimumIsExclusive = true)]
+        public int P12 { get; set; }
+
+        [RangeAttribute(5, 10, MaximumIsExclusive = true)]
+        public int P13 { get; set; }
+
+        [LengthAttribute(2, 10)]
+        public List<string> P14 { get; set; }
+
+        [LengthAttribute(2, 10)]
+        public FakeCount P15 { get; set; }
+
+        [LengthAttribute(2, 10)]
+        public FakeCountChild P16 { get; set; }
+
+        [LengthAttribute(2, 10)]
+        public int[] P17 { get; set; }
+
+        [MinLengthAttribute(3)]
+        public List<string> P18 { get; set; }
+
+        [MinLengthAttribute(3)]
+        public FakeCount P19 { get; set; }
+
+        [MinLengthAttribute(3)]
+        public FakeCountChild P20 { get; set; }
+
+        [MaxLengthAttribute(4)]
+        public List<string> P23 { get; set; }
+
+        [MaxLengthAttribute(4)]
+        public FakeCount P24 { get; set; }
+
+        [MaxLengthAttribute(4)]
+        public FakeCountChild P25 { get; set; }
+#endif // NET8_0_OR_GREATER
+
+        [RangeAttribute(1, 3)]
+        public int P1 { get; set; }
+
+        [MinLengthAttribute(5)]
+        public string? P2 { get; set; }
+
+        [MaxLengthAttribute(5)]
+        public string? P3 { get; set; }
+
+        [CompareAttribute("P2")]
+        public string? P4 { get; set; }
+
+        [RangeAttribute(typeof(byte), "2", "8")]
+        public byte P5 { get; set; }
+
+        [RangeAttribute(typeof(sbyte), "2", "8")]
+        public sbyte P6 { get; set; }
+
+        [RangeAttribute(typeof(short), "10", "20")]
+        public short P7 { get; set; }
+
+        [RangeAttribute(typeof(ulong), "10", "20")]
+        public ulong P8 { get; set; }
+
+        [RangeAttribute(typeof(decimal), "1.5", "3.14")]
+        public decimal P9 { get; set; }
+
+        [RangeAttribute(typeof(double), "12.40", "16.50")]
+        public double P10 { get; set; }
+
+        [MinLengthAttribute(3)]
+        public int[] P21 { get; set; }
+
+        [MaxLengthAttribute(4)]
+        public int[] P22 { get; set; }
+
+        [RangeAttribute(typeof(double), "12.40", "16.50")]
+        public double? P26 { get; set; }
+    }
+
+    [OptionsValidator]
+    public partial class OptionsUsingGeneratedAttributesValidator : IValidateOptions<OptionsUsingGeneratedAttributes>
+    {
     }
 
     public class MyOptions
@@ -356,4 +564,5 @@ namespace Microsoft.Gen.OptionsValidation.Unit.Test
     {
     }
 #endif // NET8_0_OR_GREATER
-}
\ No newline at end of file
+
+}
index 6b8167e..90e5b01 100644 (file)
   <data name="OptionsUnsupportedLanguageVersionMessage" xml:space="preserve">
     <value>The options validation source generator is not available in C# {0}. Please use language version {1} or greater.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="TypeCannotBeUsedWithTheValidationAttributeTitle" xml:space="preserve">
+    <value>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</value>
+  </data>
+  <data name="TypeCannotBeUsedWithTheValidationAttributeMessage" xml:space="preserve">
+    <value>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</value>
+  </data>
+</root>
index cc1ebda..c487888 100644 (file)
@@ -12,6 +12,8 @@ internal sealed partial class __ThirdModelNoNamespaceValidator__
     /// <param name="options">The options instance.</param>
     /// <returns>Validation result.</returns>
     [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::ThirdModelNoNamespace options)
     {
         global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -40,6 +42,8 @@ partial class FirstValidatorNoNamespace
     /// <param name="options">The options instance.</param>
     /// <returns>Validation result.</returns>
     [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FirstModelNoNamespace options)
     {
         global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -78,6 +82,8 @@ partial class SecondValidatorNoNamespace
     /// <param name="options">The options instance.</param>
     /// <returns>Validation result.</returns>
     [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SecondModelNoNamespace options)
     {
         global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -108,6 +114,8 @@ namespace CustomAttr
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::CustomAttr.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -149,6 +157,8 @@ namespace Enumeration
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::Enumeration.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -181,6 +191,8 @@ namespace Enumeration
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::Enumeration.ThirdModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -211,6 +223,8 @@ namespace Enumeration
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -363,6 +377,8 @@ namespace Enumeration
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -394,6 +410,8 @@ namespace FileScopedNamespace
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FileScopedNamespace.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -425,6 +443,8 @@ namespace FunnyStrings
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::FunnyStrings.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -456,6 +476,8 @@ namespace Generics
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::Generics.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -487,6 +509,8 @@ namespace Generics
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.FirstModel<T> options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -523,6 +547,8 @@ namespace MultiModelValidator
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -553,6 +579,8 @@ namespace MultiModelValidator
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::MultiModelValidator.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -585,6 +613,8 @@ namespace Nested
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::Nested.Container1.ThirdModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -618,6 +648,8 @@ namespace Nested
             /// <param name="options">The options instance.</param>
             /// <returns>Validation result.</returns>
             [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
             {
                 global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -654,6 +686,8 @@ namespace Nested
                 /// <param name="options">The options instance.</param>
                 /// <returns>Validation result.</returns>
                 [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.FirstModel options)
                 {
                     global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -701,6 +735,8 @@ namespace Nested
             /// <param name="options">The options instance.</param>
             /// <returns>Validation result.</returns>
             [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
             {
                 global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -737,6 +773,8 @@ namespace Nested
                 /// <param name="options">The options instance.</param>
                 /// <returns>Validation result.</returns>
                 [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
                 {
                     global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -774,6 +812,8 @@ namespace Nested
                 /// <param name="options">The options instance.</param>
                 /// <returns>Validation result.</returns>
                 [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.SecondModel options)
                 {
                     global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -807,6 +847,8 @@ namespace RandomMembers
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RandomMembers.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -839,6 +881,8 @@ namespace RecordTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::RecordTypes.ThirdModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -870,6 +914,8 @@ namespace RecordTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -913,6 +959,8 @@ namespace RecordTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -944,6 +992,8 @@ namespace RecordTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -976,6 +1026,8 @@ namespace RepeatedTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::RepeatedTypes.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1012,6 +1064,8 @@ namespace RepeatedTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::RepeatedTypes.ThirdModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1043,6 +1097,8 @@ namespace RepeatedTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1108,6 +1164,8 @@ namespace SelfValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::SelfValidation.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1141,6 +1199,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1172,6 +1232,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::TestClasses.OptionsValidation.RequiredAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1203,6 +1265,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::TestClasses.OptionsValidation.TypeWithoutOptionsValidator options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1248,6 +1312,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.AttributePropertyModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1288,6 +1354,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.ComplexModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1318,6 +1386,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomTypeCustomValidationAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1348,6 +1418,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.CustomValidationAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1378,6 +1450,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DataTypeAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1408,6 +1482,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.DerivedModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1458,6 +1534,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.EmailAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1488,6 +1566,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.LeafModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1538,6 +1618,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.MultipleAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1599,6 +1681,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDate options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1629,6 +1713,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1659,6 +1745,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelInt options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1689,6 +1777,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RegularExpressionAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1719,6 +1809,8 @@ namespace TestClasses.OptionsValidation
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1750,6 +1842,8 @@ namespace ValueTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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::ValueTypes.SecondModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1781,6 +1875,8 @@ namespace ValueTypes
         /// <param name="options">The options instance.</param>
         /// <returns>Validation result.</returns>
         [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 global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.FirstModel options)
         {
             global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
@@ -1820,7 +1916,7 @@ namespace __OptionValidationStaticInstances
     {
         internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute(
             (int)5);
 
         internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute(
@@ -1833,30 +1929,30 @@ namespace __OptionValidationStaticInstances
             false,
             "X");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)0,
             (int)10);
 
         internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
             "\"\r\n\\\\");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A7 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (double)0.5,
             (double)0.9);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A8 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             typeof(global::System.DateTime),
             "1/2/2004",
             "3/4/2004");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A9 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3)
         {
             ErrorMessage = "ErrorMessage"
         };
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A10 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3)
         {
@@ -1880,19 +1976,19 @@ namespace __OptionValidationStaticInstances
         internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
             (global::System.ComponentModel.DataAnnotations.DataType)11);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A16 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A17 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)3,
             (int)5);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A18 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)5,
             (int)9);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A19 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A19 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             typeof(global::System.DateTime),
             "1/2/2004",
             "3/4/2004")
@@ -1924,3 +2020,150 @@ namespace __OptionValidationStaticInstances
         internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
     }
 }
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
index ebdcb1a..7e998ce 100644 (file)
@@ -1820,7 +1820,7 @@ namespace __OptionValidationStaticInstances
     {
         internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute();
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute(
             (int)5);
 
         internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute(
@@ -1833,30 +1833,30 @@ namespace __OptionValidationStaticInstances
             false,
             "X");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)0,
             (int)10);
 
         internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute(
             "\"\r\n\\\\");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A7 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (double)0.5,
             (double)0.9);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A8 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             typeof(global::System.DateTime),
             "1/2/2004",
             "3/4/2004");
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A9 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3)
         {
             ErrorMessage = "ErrorMessage"
         };
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A10 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3)
         {
@@ -1880,15 +1880,15 @@ namespace __OptionValidationStaticInstances
         internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute(
             (global::System.ComponentModel.DataAnnotations.DataType)11);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A16 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)1,
             (int)3);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A17 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)3,
             (int)5);
 
-        internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute(
+        internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A18 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute(
             (int)5,
             (int)9);
 
@@ -1916,3 +1916,150 @@ namespace __OptionValidationStaticInstances
         internal static readonly global::RecordTypes.ThirdValidator V7 = new global::RecordTypes.ThirdValidator();
     }
 }
+namespace __OptionValidationGeneratedAttributes
+{
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
+
+        public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
+        public int Length { get; }
+        public override bool IsValid(object? value)
+        {
+            if (Length < -1)
+            {
+                throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
+            }
+            if (value == null)
+            {
+                return true;
+            }
+
+            int length;
+            if (value is string stringValue)
+            {
+                length = stringValue.Length;
+            }
+            else if (value is System.Collections.ICollection collectionValue)
+            {
+                length = collectionValue.Count;
+            }
+            else
+            {
+                throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
+            }
+
+            return length >= Length;
+        }
+        public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
+    }
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
+    [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
+    file class __SourceGen__RangeAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
+    {
+        public __SourceGen__RangeAttribute(int minimum, int maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(int);
+        }
+        public __SourceGen__RangeAttribute(double minimum, double maximum) : base()
+        {
+            Minimum = minimum;
+            Maximum = maximum;
+            OperandType = typeof(double);
+        }
+        public __SourceGen__RangeAttribute(global::System.Type type, string minimum, string maximum) : base()
+        {
+            OperandType = type;
+            NeedToConvertMinMax = true;
+            Minimum = minimum;
+            Maximum = maximum;
+        }
+        public object Minimum { get; private set; }
+        public object Maximum { get; private set; }
+        public bool MinimumIsExclusive { get; set; }
+        public bool MaximumIsExclusive { get; set; }
+        public global::System.Type OperandType { get; }
+        public bool ParseLimitsInInvariantCulture { get; set; }
+        public bool ConvertValueInInvariantCulture { get; set; }
+        public override string FormatErrorMessage(string name) =>
+                string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum);
+        private bool NeedToConvertMinMax { get; }
+        private bool Initialized { get; set; }
+        public override bool IsValid(object? value)
+        {
+            if (!Initialized)
+            {
+                if (Minimum is null || Maximum is null)
+                {
+                    throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                if (NeedToConvertMinMax)
+                {
+                    System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+                    Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                    Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values.");
+                }
+                int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum);
+                if (cmp > 0)
+                {
+                    throw new global::System.InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
+                }
+                else if (cmp == 0 && (MinimumIsExclusive || MaximumIsExclusive))
+                {
+                    throw new global::System.InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
+                }
+                Initialized = true;
+            }
+
+            if (value is null or string { Length: 0 })
+            {
+                return true;
+            }
+
+            System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture;
+            object? convertedValue;
+
+            try
+            {
+                convertedValue = ConvertValue(value, formatProvider);
+            }
+            catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException)
+            {
+                return false;
+            }
+
+            var min = (global::System.IComparable)Minimum;
+            var max = (global::System.IComparable)Maximum;
+
+            return
+                (MinimumIsExclusive ? min.CompareTo(convertedValue) < 0 : min.CompareTo(convertedValue) <= 0) &&
+                (MaximumIsExclusive ? max.CompareTo(convertedValue) > 0 : max.CompareTo(convertedValue) >= 0);
+        }
+        private string GetValidationErrorMessage()
+        {
+            return (MinimumIsExclusive, MaximumIsExclusive) switch
+            {
+                (false, false) => "The field {0} must be between {1} and {2}.",
+                (true, false) => "The field {0} must be between {1} exclusive and {2}.",
+                (false, true) => "The field {0} must be between {1} and {2} exclusive.",
+                (true, true) => "The field {0} must be between {1} exclusive and {2} exclusive.",
+            };
+        }
+        private object? ConvertValue(object? value, System.Globalization.CultureInfo formatProvider)
+        {
+            if (value is string stringValue)
+            {
+                value = global::System.Convert.ChangeType(stringValue, OperandType, formatProvider);
+            }
+            else
+            {
+                value = global::System.Convert.ChangeType(value, OperandType, formatProvider);
+            }
+            return value;
+        }
+    }
+}
index 6b8167e..90e5b01 100644 (file)
   <data name="OptionsUnsupportedLanguageVersionMessage" xml:space="preserve">
     <value>The options validation source generator is not available in C# {0}. Please use language version {1} or greater.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="TypeCannotBeUsedWithTheValidationAttributeTitle" xml:space="preserve">
+    <value>The validation attribute is only applicable to properties of type string, array, or ICollection; it cannot be used with other types.</value>
+  </data>
+  <data name="TypeCannotBeUsedWithTheValidationAttributeMessage" xml:space="preserve">
+    <value>The validation attribute {0} should only be applied to properties of type string, array, or ICollection. Using it with the type {1} could lead to runtime failures.</value>
+  </data>
+</root>
index 870b27b..7a39d8a 100644 (file)
@@ -4,6 +4,8 @@
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Options;
 using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 
 class Program
 {
@@ -37,6 +39,22 @@ class Program
             return -1;
         }
 
+        LocalOptionsValidator localOptionsValidator = new LocalOptionsValidator();
+        OptionsUsingValidationAttributes optionsUsingValidationAttributes = new OptionsUsingValidationAttributes
+        {
+            P1 = "12345",
+            P2 = new List<string> { "1234", "12345" },
+            P3 = "123456",
+            P4 = "12345",
+            P5 = 7
+        };
+
+        ValidateOptionsResult result = localOptionsValidator.Validate("", optionsUsingValidationAttributes);
+        if (result.Failed)
+        {
+            return -2;
+        }
+
         return 100;
     }
 
@@ -76,3 +94,29 @@ class Program
         public string OptionString { get; set; }
     }
 }
+
+public class OptionsUsingValidationAttributes
+{
+    [Required]
+    [MinLength(5)]
+    public string P1 { get; set; }
+
+    [Required]
+    [MaxLength(5)]
+    public List<string> P2 { get; set; }
+
+    [Length(2, 8)]
+    public string P3 { get; set; }
+
+    [Compare("P1")]
+    public string P4 { get; set; }
+
+    [Range(1, 10, MinimumIsExclusive = true, MaximumIsExclusive = true)]
+    public int P5 { get; set; }
+}
+
+[OptionsValidator]
+public partial class LocalOptionsValidator : IValidateOptions<OptionsUsingValidationAttributes>
+{
+}
+
index 669ac86..15b6dc0 100644 (file)
@@ -7,10 +7,15 @@
       Microsoft.Extensions.DependencyInjection
     </AdditionalProjectReferences>
   </PropertyGroup>
-  
+
   <ItemGroup>
     <TestConsoleAppSourceFiles Include="ConfigureTests.cs" />
   </ItemGroup>
 
+  <ItemGroup>
+    <!-- reference the options source generator -->
+    <_additionalProjectReference Include="&lt;ProjectReference Include=&quot;$(LibrariesProjectRoot)Microsoft.Extensions.Options\gen\Microsoft.Extensions.Options.SourceGeneration.csproj&quot; OutputItemType=&quot;Analyzer&quot; ReferenceOutputAssembly=&quot;true&quot; SetTargetFramework=&quot;TargetFramework=netstandard2.0&quot; /&gt;" />
+  </ItemGroup>
+
   <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
 </Project>