diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
index 37186fee8a8a..408ec7defb89 100644
--- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
+++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
@@ -138,4 +138,18 @@ attr.AttributeClass is not null &&
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
}
+
+ ///
+ /// Checks if the property is marked with [FromServices] or [FromKeyedServices] attributes.
+ ///
+ /// The property to check.
+ /// The symbol representing the [FromServices] attribute.
+ /// The symbol representing the [FromKeyedServices] attribute.
+ internal static bool IsServiceProperty(this IPropertySymbol property, INamedTypeSymbol fromServiceMetadataSymbol, INamedTypeSymbol fromKeyedServiceAttributeSymbol)
+ {
+ return property.GetAttributes().Any(attr =>
+ attr.AttributeClass is not null &&
+ (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
+ }
}
diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
index c7155b8c049b..b0617d79b3a6 100644
--- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
+++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
@@ -110,6 +110,11 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
var members = new List();
var resolvedRecordProperty = new List();
+ var fromServiceMetadataSymbol = wellKnownTypes.Get(
+ WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata);
+ var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get(
+ WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute);
+
// Special handling for record types to extract properties from
// the primary constructor.
if (typeSymbol is INamedTypeSymbol { IsRecord: true } namedType)
@@ -137,6 +142,12 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
{
resolvedRecordProperty.Add(correspondingProperty);
+ // Skip parameters that are injected as services
+ if (parameter.IsServiceParameter(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
+ {
+ continue;
+ }
+
// Check if the property's type is validatable, this resolves
// validatable types in the inheritance hierarchy
var hasValidatableType = TryExtractValidatableType(
@@ -162,13 +173,19 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
{
// Skip compiler generated properties and properties already processed via
// the record processing logic above.
- if (member.IsImplicitlyDeclared
- || member.IsEqualityContract(wellKnownTypes)
+ if (member.IsImplicitlyDeclared
+ || member.IsEqualityContract(wellKnownTypes)
|| resolvedRecordProperty.Contains(member, SymbolEqualityComparer.Default))
{
continue;
}
+ // Skip properties that are injected as services
+ if (member.IsServiceProperty(fromServiceMetadataSymbol, fromKeyedServiceAttributeSymbol))
+ {
+ continue;
+ }
+
var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes);
var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired);
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
index 7bdf0bea29b2..46158b90632d 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
@@ -21,10 +21,12 @@ public async Task CanValidateComplexTypes()
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddValidation();
+builder.Services.AddSingleton();
var app = builder.Build();
@@ -58,6 +60,10 @@ public class ComplexType
[DerivedValidation, Range(10, 100)]
public int PropertyWithMultipleAttributes { get; set; } = 10;
+
+ [FromServices]
+ [Required] // This should be ignored because of [FromServices]
+ public TestService ServiceProperty { get; set; } = null!;
}
public class DerivedValidationAttribute : ValidationAttribute
@@ -96,6 +102,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
return ValidationResult.Success;
}
}
+
+public class TestService
+{
+ [Range(10, 100)]
+ public int Value { get; set; } = 4;
+}
""";
await Verify(source, out var compilation);
await VerifyEndpoint(compilation, "/complex-type", async (endpoint, serviceProvider) =>
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.RecordType.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.RecordType.cs
index cfc1372b9a7a..a5a4b2e09ed0 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.RecordType.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.RecordType.cs
@@ -21,10 +21,12 @@ public async Task CanValidateRecordTypes()
using Microsoft.Extensions.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder();
builder.Services.AddValidation();
+builder.Services.AddSingleton();
var app = builder.Build();
@@ -48,6 +50,10 @@ public record SubTypeWithoutConstructor
[StringLength(10)]
public string? StringWithLength { get; set; }
+
+ [FromServices]
+ [Required]
+ public TestService ServiceProperty { get; set; } = null!;
}
public static class CustomValidators
@@ -66,6 +72,12 @@ public static ValidationResult Validate(int number, ValidationContext validation
}
}
+public class TestService
+{
+ [Range(10, 100)]
+ public int Value { get; set; } = 4;
+}
+
public record ValidatableRecord(
[Range(10, 100)]
int IntegerWithRange = 10,
@@ -81,7 +93,9 @@ public record ValidatableRecord(
[CustomValidation(typeof(CustomValidators), nameof(CustomValidators.Validate))]
int IntegerWithCustomValidation = 0,
[DerivedValidation, Range(10, 100)]
- int PropertyWithMultipleAttributes = 10
+ int PropertyWithMultipleAttributes = 10,
+ [FromServices] [Required] TestService ServiceProperty = null!, // This should be ignored because of [FromServices]
+ [FromKeyedServices("serviceKey")] [Range(10, 100)] int KeyedServiceProperty = 5 // This should be ignored because of [FromKeyedServices]
);
""";
await Verify(source, out var compilation);