Skip to content

Commit b684b82

Browse files
CopilotcaptainsafiaCopilot
authored
Fix validation generator to ignore properties with [FromServices] or [FromKeyedServices] attributes (#62669)
Co-authored-by: captainsafia <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Safia Abdalla <[email protected]>
1 parent f9a5ddf commit b684b82

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

src/Validation/gen/Extensions/ITypeSymbolExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,18 @@ attr.AttributeClass is not null &&
138138
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
139139
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
140140
}
141+
142+
/// <summary>
143+
/// Checks if the property is marked with [FromServices] or [FromKeyedServices] attributes.
144+
/// </summary>
145+
/// <param name="property">The property to check.</param>
146+
/// <param name="fromServiceMetadataSymbol">The symbol representing the [FromServices] attribute.</param>
147+
/// <param name="fromKeyedServiceAttributeSymbol">The symbol representing the [FromKeyedServices] attribute.</param>
148+
internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol)
149+
{
150+
return property.GetAttributes().Any(attr =>
151+
attr.AttributeClass is not null &&
152+
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
153+
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
154+
}
141155
}

src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
110110
var members = new List<ValidatableProperty>();
111111
var resolvedRecordProperty = new List<IPropertySymbol>();
112112

113+
var fromServiceMetadataSymbol = wellKnownTypes.Get(
114+
WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata);
115+
var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get(
116+
WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute);
117+
113118
// Special handling for record types to extract properties from
114119
// the primary constructor.
115120
if (typeSymbol is INamedTypeSymbol { IsRecord: true } namedType)
@@ -137,6 +142,12 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
137142
{
138143
resolvedRecordProperty.Add(correspondingProperty);
139144

145+
// Skip parameters that are injected as services
146+
if (parameter.IsServiceParameter(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
147+
{
148+
continue;
149+
}
150+
140151
// Check if the property's type is validatable, this resolves
141152
// validatable types in the inheritance hierarchy
142153
var hasValidatableType = TryExtractValidatableType(
@@ -162,13 +173,19 @@ internal ImmutableArray<ValidatableProperty> ExtractValidatableMembers(ITypeSymb
162173
{
163174
// Skip compiler generated properties and properties already processed via
164175
// the record processing logic above.
165-
if (member.IsImplicitlyDeclared
166-
|| member.IsEqualityContract(wellKnownTypes)
176+
if (member.IsImplicitlyDeclared
177+
|| member.IsEqualityContract(wellKnownTypes)
167178
|| resolvedRecordProperty.Contains(member, SymbolEqualityComparer.Default))
168179
{
169180
continue;
170181
}
171182

183+
// Skip properties that are injected as services
184+
if (member.IsServiceProperty(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
185+
{
186+
continue;
187+
}
188+
172189
var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes);
173190
var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired);
174191

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ public async Task CanValidateComplexTypes()
2121
using Microsoft.Extensions.Validation;
2222
using Microsoft.AspNetCore.Routing;
2323
using Microsoft.Extensions.DependencyInjection;
24+
using Microsoft.AspNetCore.Mvc;
2425
2526
var builder = WebApplication.CreateBuilder();
2627
2728
builder.Services.AddValidation();
29+
builder.Services.AddSingleton<TestService>();
2830
2931
var app = builder.Build();
3032
@@ -58,6 +60,10 @@ public class ComplexType
5860
5961
[DerivedValidation, Range(10, 100)]
6062
public int PropertyWithMultipleAttributes { get; set; } = 10;
63+
64+
[FromServices]
65+
[Required] // This should be ignored because of [FromServices]
66+
public TestService ServiceProperty { get; set; } = null!;
6167
}
6268
6369
public class DerivedValidationAttribute : ValidationAttribute
@@ -96,6 +102,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
96102
return ValidationResult.Success;
97103
}
98104
}
105+
106+
public class TestService
107+
{
108+
[Range(10, 100)]
109+
public int Value { get; set; } = 4;
110+
}
99111
""";
100112
await Verify(source, out var compilation);
101113
await VerifyEndpoint(compilation, "/complex-type", async (endpoint, serviceProvider) =>

src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.RecordType.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ public async Task CanValidateRecordTypes()
2121
using Microsoft.Extensions.Validation;
2222
using Microsoft.AspNetCore.Routing;
2323
using Microsoft.Extensions.DependencyInjection;
24+
using Microsoft.AspNetCore.Mvc;
2425
2526
var builder = WebApplication.CreateBuilder();
2627
2728
builder.Services.AddValidation();
29+
builder.Services.AddSingleton<TestService>();
2830
2931
var app = builder.Build();
3032
@@ -48,6 +50,10 @@ public record SubTypeWithoutConstructor
4850
4951
[StringLength(10)]
5052
public string? StringWithLength { get; set; }
53+
54+
[FromServices]
55+
[Required]
56+
public TestService ServiceProperty { get; set; } = null!;
5157
}
5258
5359
public static class CustomValidators
@@ -66,6 +72,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
6672
}
6773
}
6874
75+
public class TestService
76+
{
77+
[Range(10, 100)]
78+
public int Value { get; set; } = 4;
79+
}
80+
6981
public record ValidatableRecord(
7082
[Range(10, 100)]
7183
int IntegerWithRange = 10,
@@ -81,7 +93,9 @@ public record ValidatableRecord(
8193
[CustomValidation(typeof(CustomValidators), nameof(CustomValidators.Validate))]
8294
int IntegerWithCustomValidation = 0,
8395
[DerivedValidation, Range(10, 100)]
84-
int PropertyWithMultipleAttributes = 10
96+
int PropertyWithMultipleAttributes = 10,
97+
[FromServices] [Required] TestService ServiceProperty = null!, // This should be ignored because of [FromServices]
98+
[FromKeyedServices("serviceKey")] [Range(10, 100)] int KeyedServiceProperty = 5 // This should be ignored because of [FromKeyedServices]
8599
);
86100
""";
87101
await Verify(source, out var compilation);

0 commit comments

Comments
 (0)