diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs index a5a580a33a53..89392a93849f 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Extensions/ITypeSymbolExtensions.cs @@ -46,8 +46,9 @@ public static ITypeSymbol UnwrapType(this ITypeSymbol type, INamedTypeSymbol enu if (type.NullableAnnotation == NullableAnnotation.Annotated) { - // Extract the underlying type from a reference type - type = type.OriginalDefinition; + // Remove the nullable annotation but keep any generic arguments, e.g. List? → List + // so we can retain them in future steps. + type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated); } if (type is INamedTypeSymbol namedType && namedType.IsEnumerable(enumerable) && namedType.TypeArguments.Length == 1) diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs index 79bbc82e45a7..dd0092f31fd0 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/Parsers/ValidationsGenerator.TypesParser.cs @@ -29,13 +29,14 @@ internal ImmutableArray ExtractValidatableTypes(IInvocationOper List visitedTypes = []; foreach (var parameter in parameters) { - _ = TryExtractValidatableType(parameter.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), wellKnownTypes, ref validatableTypes, ref visitedTypes); + _ = TryExtractValidatableType(parameter.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); } return [.. validatableTypes]; } - internal bool TryExtractValidatableType(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, ref HashSet validatableTypes, ref List visitedTypes) + internal bool TryExtractValidatableType(ITypeSymbol incomingTypeSymbol, WellKnownTypes wellKnownTypes, ref HashSet validatableTypes, ref List visitedTypes) { + var typeSymbol = incomingTypeSymbol.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)); if (typeSymbol.SpecialType != SpecialType.None) { return false; @@ -126,7 +127,7 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb // Check if the property's type is validatable, this resolves // validatable types in the inheritance hierarchy var hasValidatableType = TryExtractValidatableType( - correspondingProperty.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), + correspondingProperty.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); @@ -153,7 +154,7 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb continue; } - var hasValidatableType = TryExtractValidatableType(member.Type.UnwrapType(wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Collections_IEnumerable)), wellKnownTypes, ref validatableTypes, ref visitedTypes); + var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired); // If the member has no validation attributes or validatable types and is not required, skip it. diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs index 75ebb1c81652..50cd7eca1769 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.ComplexType.cs @@ -45,7 +45,8 @@ public class ComplexType public SubTypeWithInheritance PropertyWithInheritance { get; set; } = new SubTypeWithInheritance("some-value", default); - public List ListOfSubTypes { get; set; } = []; + // Nullable to validate https://github.com/dotnet/aspnetcore/issues/61737 + public List? ListOfSubTypes { get; set; } = []; [DerivedValidation(ErrorMessage = "Value must be an even number")] public int IntegerWithDerivedValidationAttribute { get; set; } diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs index 79bd2916ae12..6cebd6df8584 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/ValidationsGenerator.Parsable.cs @@ -39,7 +39,7 @@ public class ComplexTypeWithParsableProperties public TimeOnly? TimeOnlyWithRequiredValue { get; set; } = TimeOnly.FromDateTime(DateTime.UtcNow); [Url(ErrorMessage = "The field Url must be a valid URL.")] - public Uri? Url { get; set; } = new Uri("https://example.com"); + public string? Url { get; set; } = "https://example.com"; [Required] [Range(typeof(DateOnly), "2023-01-01", "2025-12-31", ErrorMessage = "Date must be between 2023-01-01 and 2025-12-31")] diff --git a/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs b/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs index ef6057388d2a..7eea5401c9c1 100644 --- a/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs +++ b/src/Http/Http.Extensions/test/ValidationsGenerator/snapshots/ValidationsGeneratorTests.CanValidateTypeWithParsableProperties#ValidatableInfoResolver.g.verified.cs @@ -95,7 +95,7 @@ private ValidatableTypeInfo CreateComplexTypeWithParsableProperties() ), new GeneratedValidatablePropertyInfo( containingType: typeof(global::ComplexTypeWithParsableProperties), - propertyType: typeof(global::System.Uri), + propertyType: typeof(string), name: "Url", displayName: "Url" ),