Skip to content

Commit fe1247b

Browse files
authored
Fix Options Source Gen with Length attributes applied on properties of Interface type (#93426)
1 parent 29678e2 commit fe1247b

File tree

5 files changed

+525
-1
lines changed

5 files changed

+525
-1
lines changed

src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,26 @@ internal static bool TypeHasProperty(ITypeSymbol typeSymbol, string propertyName
8080

8181
if (type.GetMembers(propertyName).OfType<IPropertySymbol>().Any(property =>
8282
property.Type.SpecialType == returnType && property.DeclaredAccessibility == Accessibility.Public &&
83-
!property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty))
83+
property.Kind == SymbolKind.Property && !property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty))
8484
{
8585
return true;
8686
}
8787

8888
type = type.BaseType;
8989
} while (type is not null && type.SpecialType != SpecialType.System_Object);
9090

91+
// When we have an interface type, we need to check all the interfaces that it extends.
92+
// Like IList<T> extends ICollection<T> where the property we're looking for is defined.
93+
foreach (var interfaceType in typeSymbol.AllInterfaces)
94+
{
95+
if (interfaceType.GetMembers(propertyName).OfType<IPropertySymbol>().Any(property =>
96+
property.Type.SpecialType == returnType && property.Kind == SymbolKind.Property &&
97+
!property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty))
98+
{
99+
return true;
100+
}
101+
}
102+
91103
return false;
92104
}
93105

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
2+
// <auto-generated/>
3+
#nullable enable
4+
#pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
5+
namespace Test
6+
{
7+
partial class MyOptionsValidator
8+
{
9+
/// <summary>
10+
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
11+
/// </summary>
12+
/// <param name="name">The name of the options instance being validated.</param>
13+
/// <param name="options">The options instance.</param>
14+
/// <returns>Validation result.</returns>
15+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
16+
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
17+
Justification = "The created ValidationContext object is used in a way that never call reflection")]
18+
public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Test.MyOptions options)
19+
{
20+
global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;
21+
var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);
22+
var validationResults = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationResult>();
23+
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(1);
24+
25+
context.MemberName = "P1";
26+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P1" : $"{name}.P1";
27+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
28+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes))
29+
{
30+
(builder ??= new()).AddResults(validationResults);
31+
}
32+
33+
context.MemberName = "P2";
34+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P2" : $"{name}.P2";
35+
validationResults.Clear();
36+
validationAttributes.Clear();
37+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
38+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes))
39+
{
40+
(builder ??= new()).AddResults(validationResults);
41+
}
42+
43+
context.MemberName = "P3";
44+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P3" : $"{name}.P3";
45+
validationResults.Clear();
46+
validationAttributes.Clear();
47+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
48+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes))
49+
{
50+
(builder ??= new()).AddResults(validationResults);
51+
}
52+
53+
context.MemberName = "P4";
54+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P4" : $"{name}.P4";
55+
validationResults.Clear();
56+
validationAttributes.Clear();
57+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1);
58+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes))
59+
{
60+
(builder ??= new()).AddResults(validationResults);
61+
}
62+
63+
context.MemberName = "P5";
64+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P5" : $"{name}.P5";
65+
validationResults.Clear();
66+
validationAttributes.Clear();
67+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2);
68+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes))
69+
{
70+
(builder ??= new()).AddResults(validationResults);
71+
}
72+
73+
context.MemberName = "P6";
74+
context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P6" : $"{name}.P6";
75+
validationResults.Clear();
76+
validationAttributes.Clear();
77+
validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3);
78+
if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes))
79+
{
80+
(builder ??= new()).AddResults(validationResults);
81+
}
82+
83+
return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build();
84+
}
85+
}
86+
}
87+
namespace __OptionValidationStaticInstances
88+
{
89+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
90+
file static class __Attributes
91+
{
92+
internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute(
93+
(int)10,
94+
(int)20);
95+
96+
internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute(
97+
(int)4);
98+
99+
internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute(
100+
(int)5);
101+
}
102+
}
103+
namespace __OptionValidationStaticInstances
104+
{
105+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
106+
file static class __Validators
107+
{
108+
}
109+
}
110+
namespace __OptionValidationGeneratedAttributes
111+
{
112+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
113+
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
114+
file class __SourceGen__LengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
115+
{
116+
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}'.";
117+
public __SourceGen__LengthAttribute(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; }
118+
public int MinimumLength { get; }
119+
public int MaximumLength { get; }
120+
public override bool IsValid(object? value)
121+
{
122+
if (MinimumLength < 0)
123+
{
124+
throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater.");
125+
}
126+
if (MaximumLength < MinimumLength)
127+
{
128+
throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength.");
129+
}
130+
if (value == null)
131+
{
132+
return true;
133+
}
134+
135+
int length;
136+
if (value is string stringValue)
137+
{
138+
length = stringValue.Length;
139+
}
140+
else if (value is System.Collections.ICollection collectionValue)
141+
{
142+
length = collectionValue.Count;
143+
}
144+
else if (value is global::System.Collections.Generic.IList<string>)
145+
{
146+
length = ((global::System.Collections.Generic.IList<string>)value).Count;
147+
}
148+
else if (value is global::System.Collections.Generic.ICollection<string>)
149+
{
150+
length = ((global::System.Collections.Generic.ICollection<string>)value).Count;
151+
}
152+
else
153+
{
154+
throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
155+
}
156+
157+
return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength);
158+
}
159+
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength);
160+
}
161+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
162+
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
163+
file class __SourceGen__MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
164+
{
165+
private const int MaxAllowableLength = -1;
166+
private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'.";
167+
public __SourceGen__MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
168+
public __SourceGen__MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; }
169+
public int Length { get; }
170+
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
171+
public override bool IsValid(object? value)
172+
{
173+
if (Length == 0 || Length < -1)
174+
{
175+
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.");
176+
}
177+
if (value == null || MaxAllowableLength == Length)
178+
{
179+
return true;
180+
}
181+
182+
int length;
183+
if (value is string stringValue)
184+
{
185+
length = stringValue.Length;
186+
}
187+
else if (value is System.Collections.ICollection collectionValue)
188+
{
189+
length = collectionValue.Count;
190+
}
191+
else if (value is global::System.Collections.Generic.IList<string>)
192+
{
193+
length = ((global::System.Collections.Generic.IList<string>)value).Count;
194+
}
195+
else if (value is global::System.Collections.Generic.ICollection<string>)
196+
{
197+
length = ((global::System.Collections.Generic.ICollection<string>)value).Count;
198+
}
199+
else
200+
{
201+
throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
202+
}
203+
204+
return length <= Length;
205+
}
206+
}
207+
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")]
208+
[global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)]
209+
file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute
210+
{
211+
private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'.";
212+
213+
public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; }
214+
public int Length { get; }
215+
public override bool IsValid(object? value)
216+
{
217+
if (Length < -1)
218+
{
219+
throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater.");
220+
}
221+
if (value == null)
222+
{
223+
return true;
224+
}
225+
226+
int length;
227+
if (value is string stringValue)
228+
{
229+
length = stringValue.Length;
230+
}
231+
else if (value is System.Collections.ICollection collectionValue)
232+
{
233+
length = collectionValue.Count;
234+
}
235+
else if (value is global::System.Collections.Generic.IList<string>)
236+
{
237+
length = ((global::System.Collections.Generic.IList<string>)value).Count;
238+
}
239+
else if (value is global::System.Collections.Generic.ICollection<string>)
240+
{
241+
length = ((global::System.Collections.Generic.ICollection<string>)value).Count;
242+
}
243+
else
244+
{
245+
throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type.");
246+
}
247+
248+
return length >= Length;
249+
}
250+
public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length);
251+
}
252+
}

0 commit comments

Comments
 (0)