From 690e927d6654da34966cc889d149521740d13330 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 01:33:56 +0000
Subject: [PATCH 1/5] Initial plan
From 07e71a67a6d99f334c7bf36b1054279dc655fc07 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 01:46:25 +0000
Subject: [PATCH 2/5] Add JsonIgnore attribute support to validation generator
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
---
src/Shared/RoslynUtils/WellKnownTypeData.cs | 2 +
.../gen/Extensions/ITypeSymbolExtensions.cs | 24 +++
src/Validation/gen/Models/RequiredSymbols.cs | 1 +
.../ValidationsGenerator.TypesParser.cs | 14 ++
.../ValidationsGenerator.ComplexType.cs | 88 ++++++++++
...nore#ValidatableInfoResolver.g.received.cs | 164 ++++++++++++++++++
6 files changed, 293 insertions(+)
create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
diff --git a/src/Shared/RoslynUtils/WellKnownTypeData.cs b/src/Shared/RoslynUtils/WellKnownTypeData.cs
index 1afb045fc713..1549ad178eac 100644
--- a/src/Shared/RoslynUtils/WellKnownTypeData.cs
+++ b/src/Shared/RoslynUtils/WellKnownTypeData.cs
@@ -119,6 +119,7 @@ public enum WellKnownType
Microsoft_AspNetCore_Authorization_IAuthorizeData,
System_AttributeUsageAttribute,
System_Text_Json_Serialization_JsonDerivedTypeAttribute,
+ System_Text_Json_Serialization_JsonIgnoreAttribute,
System_ComponentModel_DataAnnotations_DisplayAttribute,
System_ComponentModel_DataAnnotations_ValidationAttribute,
System_ComponentModel_DataAnnotations_RequiredAttribute,
@@ -240,6 +241,7 @@ public enum WellKnownType
"Microsoft.AspNetCore.Authorization.IAuthorizeData",
"System.AttributeUsageAttribute",
"System.Text.Json.Serialization.JsonDerivedTypeAttribute",
+ "System.Text.Json.Serialization.JsonIgnoreAttribute",
"System.ComponentModel.DataAnnotations.DisplayAttribute",
"System.ComponentModel.DataAnnotations.ValidationAttribute",
"System.ComponentModel.DataAnnotations.RequiredAttribute",
diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
index 408ec7defb89..e5cf8f06e882 100644
--- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
+++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
@@ -152,4 +152,28 @@ attr.AttributeClass is not null &&
(attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) ||
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol)));
}
+
+ ///
+ /// Checks if the property is marked with [JsonIgnore] attribute.
+ ///
+ /// The property to check.
+ /// The symbol representing the [JsonIgnore] attribute.
+ internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol jsonIgnoreAttributeSymbol)
+ {
+ return property.GetAttributes().Any(attr =>
+ attr.AttributeClass is not null &&
+ SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol));
+ }
+
+ ///
+ /// Checks if the parameter is marked with [JsonIgnore] attribute.
+ ///
+ /// The parameter to check.
+ /// The symbol representing the [JsonIgnore] attribute.
+ internal static bool IsJsonIgnoredParameter(this IParameterSymbol parameter, INamedTypeSymbol jsonIgnoreAttributeSymbol)
+ {
+ return parameter.GetAttributes().Any(attr =>
+ attr.AttributeClass is not null &&
+ SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol));
+ }
}
diff --git a/src/Validation/gen/Models/RequiredSymbols.cs b/src/Validation/gen/Models/RequiredSymbols.cs
index 51f8c92ccf9e..f6d48f40eb7a 100644
--- a/src/Validation/gen/Models/RequiredSymbols.cs
+++ b/src/Validation/gen/Models/RequiredSymbols.cs
@@ -11,6 +11,7 @@ internal sealed record class RequiredSymbols(
INamedTypeSymbol IEnumerable,
INamedTypeSymbol IValidatableObject,
INamedTypeSymbol JsonDerivedTypeAttribute,
+ INamedTypeSymbol JsonIgnoreAttribute,
INamedTypeSymbol RequiredAttribute,
INamedTypeSymbol CustomValidationAttribute,
INamedTypeSymbol HttpContext,
diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
index b0617d79b3a6..51088713f3ef 100644
--- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
+++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
@@ -114,6 +114,8 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata);
var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get(
WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute);
+ var jsonIgnoreAttributeSymbol = wellKnownTypes.Get(
+ WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonIgnoreAttribute);
// Special handling for record types to extract properties from
// the primary constructor.
@@ -148,6 +150,12 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
continue;
}
+ // Skip parameters that have JsonIgnore attribute
+ if (parameter.IsJsonIgnoredParameter(jsonIgnoreAttributeSymbol))
+ {
+ continue;
+ }
+
// Check if the property's type is validatable, this resolves
// validatable types in the inheritance hierarchy
var hasValidatableType = TryExtractValidatableType(
@@ -186,6 +194,12 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
continue;
}
+ // Skip properties that have JsonIgnore attribute
+ if (member.IsJsonIgnoredProperty(jsonIgnoreAttributeSymbol))
+ {
+ 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 46158b90632d..08d86a052083 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
@@ -7,6 +7,94 @@ namespace Microsoft.Extensions.Validation.GeneratorTests;
public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase
{
+ [Fact]
+ public async Task CanValidateComplexTypesWithJsonIgnore()
+ {
+ // Arrange
+ var source = """
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Validation;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json.Serialization;
+
+var builder = WebApplication.CreateBuilder();
+
+builder.Services.AddValidation();
+
+var app = builder.Build();
+
+app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!));
+
+app.Run();
+
+public class ComplexTypeWithJsonIgnore
+{
+ [Range(10, 100)]
+ public int ValidatedProperty { get; set; } = 10;
+
+ [JsonIgnore]
+ [Required] // This should be ignored because of [JsonIgnore]
+ public string IgnoredProperty { get; set; } = null!;
+
+ [JsonIgnore]
+ public CircularReferenceType CircularReference { get; set; } = new CircularReferenceType();
+}
+
+public class CircularReferenceType
+{
+ [JsonIgnore]
+ public ComplexTypeWithJsonIgnore Parent { get; set; } = new ComplexTypeWithJsonIgnore();
+
+ public string Name { get; set; } = "test";
+}
+""";
+ await Verify(source, out var compilation);
+ await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) =>
+ {
+ await ValidInputWithJsonIgnoreProducesNoWarnings(endpoint);
+ await InvalidValidatedPropertyProducesError(endpoint);
+
+ async Task ValidInputWithJsonIgnoreProducesNoWarnings(Endpoint endpoint)
+ {
+ var payload = """
+ {
+ "ValidatedProperty": 50
+ }
+ """;
+ var context = CreateHttpContextWithPayload(payload, serviceProvider);
+ await endpoint.RequestDelegate(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ }
+
+ async Task InvalidValidatedPropertyProducesError(Endpoint endpoint)
+ {
+ var payload = """
+ {
+ "ValidatedProperty": 5
+ }
+ """;
+ var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+ await endpoint.RequestDelegate(context);
+
+ var problemDetails = await AssertBadRequest(context);
+ Assert.Collection(problemDetails.Errors, kvp =>
+ {
+ Assert.Equal("ValidatedProperty", kvp.Key);
+ Assert.Equal("The field ValidatedProperty must be between 10 and 100.", kvp.Value.Single());
+ });
+ }
+ });
+ }
+
[Fact]
public async Task CanValidateComplexTypes()
{
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
new file mode 100644
index 000000000000..506f94c467a3
--- /dev/null
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
@@ -0,0 +1,164 @@
+//HintName: ValidatableInfoResolver.g.cs
+#nullable enable annotations
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+#nullable enable
+#pragma warning disable ASP0029
+
+namespace System.Runtime.CompilerServices
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ file sealed class InterceptsLocationAttribute : System.Attribute
+ {
+ public InterceptsLocationAttribute(int version, string data)
+ {
+ }
+ }
+}
+
+namespace Microsoft.Extensions.Validation.Generated
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
+ {
+ public GeneratedValidatablePropertyInfo(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type containingType,
+ global::System.Type propertyType,
+ string name,
+ string displayName) : base(containingType, propertyType, name, displayName)
+ {
+ ContainingType = containingType;
+ Name = name;
+ }
+
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ internal global::System.Type ContainingType { get; }
+ internal string Name { get; }
+
+ protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
+ => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name);
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo
+ {
+ public GeneratedValidatableTypeInfo(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+ global::System.Type type,
+ ValidatablePropertyInfo[] members) : base(type, members) { }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver
+ {
+ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
+ {
+ validatableInfo = null;
+ if (type == typeof(global::ComplexTypeWithJsonIgnore))
+ {
+ validatableInfo = new GeneratedValidatableTypeInfo(
+ type: typeof(global::ComplexTypeWithJsonIgnore),
+ members: [
+ new GeneratedValidatablePropertyInfo(
+ containingType: typeof(global::ComplexTypeWithJsonIgnore),
+ propertyType: typeof(int),
+ name: "ValidatedProperty",
+ displayName: "ValidatedProperty"
+ ),
+ ]
+ );
+ return true;
+ }
+
+ return false;
+ }
+
+ // No-ops, rely on runtime code for ParameterInfo-based resolution
+ public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
+ {
+ validatableInfo = null;
+ return false;
+ }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file static class GeneratedServiceCollectionExtensions
+ {
+ [InterceptsLocation]
+ public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null)
+ {
+ // Use non-extension method to avoid infinite recursion.
+ return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options =>
+ {
+ options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver());
+ if (configureOptions is not null)
+ {
+ configureOptions(options);
+ }
+ });
+ }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file static class ValidationAttributeCache
+ {
+ private sealed record CacheKey(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type ContainingType,
+ string PropertyName);
+ private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new();
+
+ public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type containingType,
+ string propertyName)
+ {
+ var key = new CacheKey(containingType, propertyName);
+ return _cache.GetOrAdd(key, static k =>
+ {
+ var results = new global::System.Collections.Generic.List();
+
+ // Get attributes from the property
+ var property = k.ContainingType.GetProperty(k.PropertyName);
+ if (property != null)
+ {
+ var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
+ .GetCustomAttributes(property, inherit: true);
+
+ results.AddRange(propertyAttributes);
+ }
+
+ // Check constructors for parameters that match the property name
+ // to handle record scenarios
+ foreach (var constructor in k.ContainingType.GetConstructors())
+ {
+ // Look for parameter with matching name (case insensitive)
+ var parameter = global::System.Linq.Enumerable.FirstOrDefault(
+ constructor.GetParameters(),
+ p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase));
+
+ if (parameter != null)
+ {
+ var paramAttributes = global::System.Reflection.CustomAttributeExtensions
+ .GetCustomAttributes(parameter, inherit: true);
+
+ results.AddRange(paramAttributes);
+
+ break;
+ }
+ }
+
+ return results.ToArray();
+ });
+ }
+ }
+}
\ No newline at end of file
From 2247d7fff68d35e4cd67b738cd41c49317e3b694 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 01:51:17 +0000
Subject: [PATCH 3/5] Fix JsonIgnore test case to prevent circular reference
stack overflow
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
---
.../ValidationsGenerator.ComplexType.cs | 4 ++--
...TypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs} | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
rename src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/{ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs => ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs} (99%)
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 08d86a052083..74f588a7ebd0 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
@@ -44,13 +44,13 @@ public class ComplexTypeWithJsonIgnore
public string IgnoredProperty { get; set; } = null!;
[JsonIgnore]
- public CircularReferenceType CircularReference { get; set; } = new CircularReferenceType();
+ public CircularReferenceType? CircularReference { get; set; }
}
public class CircularReferenceType
{
[JsonIgnore]
- public ComplexTypeWithJsonIgnore Parent { get; set; } = new ComplexTypeWithJsonIgnore();
+ public ComplexTypeWithJsonIgnore? Parent { get; set; }
public string Name { get; set; } = "test";
}
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
similarity index 99%
rename from src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
rename to src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
index 506f94c467a3..e82395ab046d 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
@@ -1,4 +1,4 @@
-//HintName: ValidatableInfoResolver.g.cs
+//HintName: ValidatableInfoResolver.g.cs
#nullable enable annotations
//------------------------------------------------------------------------------
//
From d038ce74211883043059be4569b1c8464b1bc41f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 02:27:25 +0000
Subject: [PATCH 4/5] Update CanValidateComplexTypesWithJsonIgnore test with
record type cases
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
---
.../ValidationsGenerator.ComplexType.cs | 61 +++++-
...nore#ValidatableInfoResolver.g.received.cs | 179 ++++++++++++++++++
2 files changed, 239 insertions(+), 1 deletion(-)
create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
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 74f588a7ebd0..0a5f023ba00b 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs
@@ -31,6 +31,7 @@ public async Task CanValidateComplexTypesWithJsonIgnore()
var app = builder.Build();
app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!));
+app.MapPost("/record-type-with-json-ignore", (RecordTypeWithJsonIgnore recordType) => Results.Ok("Passed"!));
app.Run();
@@ -54,6 +55,27 @@ public class CircularReferenceType
public string Name { get; set; } = "test";
}
+
+public record RecordTypeWithJsonIgnore
+{
+ [Range(10, 100)]
+ public int ValidatedProperty { get; set; } = 10;
+
+ [JsonIgnore]
+ [Required] // This should be ignored because of [JsonIgnore]
+ public string IgnoredProperty { get; set; } = null!;
+
+ [JsonIgnore]
+ public CircularReferenceRecord? CircularReference { get; set; }
+}
+
+public record CircularReferenceRecord
+{
+ [JsonIgnore]
+ public RecordTypeWithJsonIgnore? Parent { get; set; }
+
+ public string Name { get; set; } = "test";
+}
""";
await Verify(source, out var compilation);
await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) =>
@@ -93,8 +115,45 @@ async Task InvalidValidatedPropertyProducesError(Endpoint endpoint)
});
}
});
- }
+
+ await VerifyEndpoint(compilation, "/record-type-with-json-ignore", async (endpoint, serviceProvider) =>
+ {
+ await ValidInputWithJsonIgnoreProducesNoWarningsForRecord(endpoint);
+ await InvalidValidatedPropertyProducesErrorForRecord(endpoint);
+
+ async Task ValidInputWithJsonIgnoreProducesNoWarningsForRecord(Endpoint endpoint)
+ {
+ var payload = """
+ {
+ "ValidatedProperty": 50
+ }
+ """;
+ var context = CreateHttpContextWithPayload(payload, serviceProvider);
+ await endpoint.RequestDelegate(context);
+ Assert.Equal(200, context.Response.StatusCode);
+ }
+
+ async Task InvalidValidatedPropertyProducesErrorForRecord(Endpoint endpoint)
+ {
+ var payload = """
+ {
+ "ValidatedProperty": 5
+ }
+ """;
+ var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+ await endpoint.RequestDelegate(context);
+
+ var problemDetails = await AssertBadRequest(context);
+ Assert.Collection(problemDetails.Errors, kvp =>
+ {
+ Assert.Equal("ValidatedProperty", kvp.Key);
+ Assert.Equal("The field ValidatedProperty must be between 10 and 100.", kvp.Value.Single());
+ });
+ }
+ });
+ }
[Fact]
public async Task CanValidateComplexTypes()
{
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
new file mode 100644
index 000000000000..496844084845
--- /dev/null
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
@@ -0,0 +1,179 @@
+//HintName: ValidatableInfoResolver.g.cs
+#nullable enable annotations
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+#nullable enable
+#pragma warning disable ASP0029
+
+namespace System.Runtime.CompilerServices
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ file sealed class InterceptsLocationAttribute : System.Attribute
+ {
+ public InterceptsLocationAttribute(int version, string data)
+ {
+ }
+ }
+}
+
+namespace Microsoft.Extensions.Validation.Generated
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
+ {
+ public GeneratedValidatablePropertyInfo(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type containingType,
+ global::System.Type propertyType,
+ string name,
+ string displayName) : base(containingType, propertyType, name, displayName)
+ {
+ ContainingType = containingType;
+ Name = name;
+ }
+
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ internal global::System.Type ContainingType { get; }
+ internal string Name { get; }
+
+ protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
+ => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name);
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo
+ {
+ public GeneratedValidatableTypeInfo(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+ global::System.Type type,
+ ValidatablePropertyInfo[] members) : base(type, members) { }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver
+ {
+ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
+ {
+ validatableInfo = null;
+ if (type == typeof(global::ComplexTypeWithJsonIgnore))
+ {
+ validatableInfo = new GeneratedValidatableTypeInfo(
+ type: typeof(global::ComplexTypeWithJsonIgnore),
+ members: [
+ new GeneratedValidatablePropertyInfo(
+ containingType: typeof(global::ComplexTypeWithJsonIgnore),
+ propertyType: typeof(int),
+ name: "ValidatedProperty",
+ displayName: "ValidatedProperty"
+ ),
+ ]
+ );
+ return true;
+ }
+ if (type == typeof(global::RecordTypeWithJsonIgnore))
+ {
+ validatableInfo = new GeneratedValidatableTypeInfo(
+ type: typeof(global::RecordTypeWithJsonIgnore),
+ members: [
+ new GeneratedValidatablePropertyInfo(
+ containingType: typeof(global::RecordTypeWithJsonIgnore),
+ propertyType: typeof(int),
+ name: "ValidatedProperty",
+ displayName: "ValidatedProperty"
+ ),
+ ]
+ );
+ return true;
+ }
+
+ return false;
+ }
+
+ // No-ops, rely on runtime code for ParameterInfo-based resolution
+ public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
+ {
+ validatableInfo = null;
+ return false;
+ }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file static class GeneratedServiceCollectionExtensions
+ {
+ [InterceptsLocation]
+ public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null)
+ {
+ // Use non-extension method to avoid infinite recursion.
+ return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options =>
+ {
+ options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver());
+ if (configureOptions is not null)
+ {
+ configureOptions(options);
+ }
+ });
+ }
+ }
+
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
+ file static class ValidationAttributeCache
+ {
+ private sealed record CacheKey(
+ [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type ContainingType,
+ string PropertyName);
+ private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new();
+
+ public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
+ [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+ global::System.Type containingType,
+ string propertyName)
+ {
+ var key = new CacheKey(containingType, propertyName);
+ return _cache.GetOrAdd(key, static k =>
+ {
+ var results = new global::System.Collections.Generic.List();
+
+ // Get attributes from the property
+ var property = k.ContainingType.GetProperty(k.PropertyName);
+ if (property != null)
+ {
+ var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
+ .GetCustomAttributes(property, inherit: true);
+
+ results.AddRange(propertyAttributes);
+ }
+
+ // Check constructors for parameters that match the property name
+ // to handle record scenarios
+ foreach (var constructor in k.ContainingType.GetConstructors())
+ {
+ // Look for parameter with matching name (case insensitive)
+ var parameter = global::System.Linq.Enumerable.FirstOrDefault(
+ constructor.GetParameters(),
+ p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase));
+
+ if (parameter != null)
+ {
+ var paramAttributes = global::System.Reflection.CustomAttributeExtensions
+ .GetCustomAttributes(parameter, inherit: true);
+
+ results.AddRange(paramAttributes);
+
+ break;
+ }
+ }
+
+ return results.ToArray();
+ });
+ }
+ }
+}
\ No newline at end of file
From cabe2b1c5c5a18317bb4a55796f19c8d027dfdd3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 03:08:27 +0000
Subject: [PATCH 5/5] Remove unused IsJsonIgnoredParameter method and clean up
test snapshots
- Remove IsJsonIgnoredParameter method since JsonIgnore can only be applied to properties and fields, not constructor parameters
- Update record parameter validation to check corresponding property for JsonIgnore instead
- Delete stale received snapshot file
- Update verified snapshot to include record type validation results
Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
---
.../gen/Extensions/ITypeSymbolExtensions.cs | 12 --
.../ValidationsGenerator.TypesParser.cs | 4 +-
...nore#ValidatableInfoResolver.g.received.cs | 179 ------------------
...nore#ValidatableInfoResolver.g.verified.cs | 17 +-
4 files changed, 18 insertions(+), 194 deletions(-)
delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
index e5cf8f06e882..364ee29b0b40 100644
--- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
+++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs
@@ -164,16 +164,4 @@ internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamed
attr.AttributeClass is not null &&
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol));
}
-
- ///
- /// Checks if the parameter is marked with [JsonIgnore] attribute.
- ///
- /// The parameter to check.
- /// The symbol representing the [JsonIgnore] attribute.
- internal static bool IsJsonIgnoredParameter(this IParameterSymbol parameter, INamedTypeSymbol jsonIgnoreAttributeSymbol)
- {
- return parameter.GetAttributes().Any(attr =>
- attr.AttributeClass is not null &&
- SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol));
- }
}
diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
index 51088713f3ef..20e761deb12d 100644
--- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
+++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs
@@ -150,8 +150,8 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb
continue;
}
- // Skip parameters that have JsonIgnore attribute
- if (parameter.IsJsonIgnoredParameter(jsonIgnoreAttributeSymbol))
+ // Skip properties that have JsonIgnore attribute
+ if (correspondingProperty.IsJsonIgnoredProperty(jsonIgnoreAttributeSymbol))
{
continue;
}
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
deleted file mode 100644
index 496844084845..000000000000
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-//HintName: ValidatableInfoResolver.g.cs
-#nullable enable annotations
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-#nullable enable
-#pragma warning disable ASP0029
-
-namespace System.Runtime.CompilerServices
-{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
- file sealed class InterceptsLocationAttribute : System.Attribute
- {
- public InterceptsLocationAttribute(int version, string data)
- {
- }
- }
-}
-
-namespace Microsoft.Extensions.Validation.Generated
-{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo
- {
- public GeneratedValidatablePropertyInfo(
- [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
- global::System.Type containingType,
- global::System.Type propertyType,
- string name,
- string displayName) : base(containingType, propertyType, name, displayName)
- {
- ContainingType = containingType;
- Name = name;
- }
-
- [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
- internal global::System.Type ContainingType { get; }
- internal string Name { get; }
-
- protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
- => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name);
- }
-
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo
- {
- public GeneratedValidatableTypeInfo(
- [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
- global::System.Type type,
- ValidatablePropertyInfo[] members) : base(type, members) { }
- }
-
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver
- {
- public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
- {
- validatableInfo = null;
- if (type == typeof(global::ComplexTypeWithJsonIgnore))
- {
- validatableInfo = new GeneratedValidatableTypeInfo(
- type: typeof(global::ComplexTypeWithJsonIgnore),
- members: [
- new GeneratedValidatablePropertyInfo(
- containingType: typeof(global::ComplexTypeWithJsonIgnore),
- propertyType: typeof(int),
- name: "ValidatedProperty",
- displayName: "ValidatedProperty"
- ),
- ]
- );
- return true;
- }
- if (type == typeof(global::RecordTypeWithJsonIgnore))
- {
- validatableInfo = new GeneratedValidatableTypeInfo(
- type: typeof(global::RecordTypeWithJsonIgnore),
- members: [
- new GeneratedValidatablePropertyInfo(
- containingType: typeof(global::RecordTypeWithJsonIgnore),
- propertyType: typeof(int),
- name: "ValidatedProperty",
- displayName: "ValidatedProperty"
- ),
- ]
- );
- return true;
- }
-
- return false;
- }
-
- // No-ops, rely on runtime code for ParameterInfo-based resolution
- public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo)
- {
- validatableInfo = null;
- return false;
- }
- }
-
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- file static class GeneratedServiceCollectionExtensions
- {
- [InterceptsLocation]
- public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null)
- {
- // Use non-extension method to avoid infinite recursion.
- return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options =>
- {
- options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver());
- if (configureOptions is not null)
- {
- configureOptions(options);
- }
- });
- }
- }
-
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")]
- file static class ValidationAttributeCache
- {
- private sealed record CacheKey(
- [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
- [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
- global::System.Type ContainingType,
- string PropertyName);
- private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new();
-
- public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes(
- [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
- global::System.Type containingType,
- string propertyName)
- {
- var key = new CacheKey(containingType, propertyName);
- return _cache.GetOrAdd(key, static k =>
- {
- var results = new global::System.Collections.Generic.List();
-
- // Get attributes from the property
- var property = k.ContainingType.GetProperty(k.PropertyName);
- if (property != null)
- {
- var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
- .GetCustomAttributes(property, inherit: true);
-
- results.AddRange(propertyAttributes);
- }
-
- // Check constructors for parameters that match the property name
- // to handle record scenarios
- foreach (var constructor in k.ContainingType.GetConstructors())
- {
- // Look for parameter with matching name (case insensitive)
- var parameter = global::System.Linq.Enumerable.FirstOrDefault(
- constructor.GetParameters(),
- p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase));
-
- if (parameter != null)
- {
- var paramAttributes = global::System.Reflection.CustomAttributeExtensions
- .GetCustomAttributes(parameter, inherit: true);
-
- results.AddRange(paramAttributes);
-
- break;
- }
- }
-
- return results.ToArray();
- });
- }
- }
-}
\ No newline at end of file
diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
index e82395ab046d..496844084845 100644
--- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
+++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs
@@ -1,4 +1,4 @@
-//HintName: ValidatableInfoResolver.g.cs
+//HintName: ValidatableInfoResolver.g.cs
#nullable enable annotations
//------------------------------------------------------------------------------
//
@@ -77,6 +77,21 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.
);
return true;
}
+ if (type == typeof(global::RecordTypeWithJsonIgnore))
+ {
+ validatableInfo = new GeneratedValidatableTypeInfo(
+ type: typeof(global::RecordTypeWithJsonIgnore),
+ members: [
+ new GeneratedValidatablePropertyInfo(
+ containingType: typeof(global::RecordTypeWithJsonIgnore),
+ propertyType: typeof(int),
+ name: "ValidatedProperty",
+ displayName: "ValidatedProperty"
+ ),
+ ]
+ );
+ return true;
+ }
return false;
}