Răsfoiți Sursa

Skip indexer properties in validation source generator (#65432)

Aditya Mandaleeka 3 săptămâni în urmă
părinte
comite
df6c8e9b58

+ 7 - 2
src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs

@@ -210,9 +210,14 @@ public sealed partial class ValidationsGenerator : IIncrementalGenerator
         // Handle properties for classes and any properties not handled by the constructor
         foreach (var member in typeSymbol.GetMembers().OfType<IPropertySymbol>())
         {
-            // Skip compiler generated properties and properties already processed via
-            // the record processing logic above.
+            // Skip compiler generated properties, indexers, static properties, properties without
+            // a public getter, and properties already processed via the record processing logic above.
             if (member.IsImplicitlyDeclared
+                || member.IsIndexer
+                || member.IsStatic
+                || member.IsWriteOnly
+                || member.GetMethod is null
+                || member.GetMethod.DeclaredAccessibility is not Accessibility.Public
                 || member.IsEqualityContract(wellKnownTypes)
                 || resolvedRecordProperty.Contains(member, SymbolEqualityComparer.Default))
             {

+ 145 - 0
src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs

@@ -791,4 +791,149 @@ public class JsonIgnoreConditionsModel
             }
         });
     }
+
+    [Fact]
+    public async Task SkipsIndexerPropertiesOnTypes()
+    {
+        var source = """
+using System;
+using System.Text.Json;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Validation;
+using Microsoft.Extensions.DependencyInjection;
+
+var builder = WebApplication.CreateBuilder();
+
+builder.Services.AddValidation();
+
+var app = builder.Build();
+
+app.MapPost("/json-element", ([FromBody] JsonElement request) => Results.Ok("Passed"));
+app.MapPost("/type-with-json-element", ([FromBody] TypeWithJsonElement request) => Results.Ok("Passed"));
+
+app.Run();
+
+public class TypeWithJsonElement
+{
+    [Required]
+    public string Name { get; set; } = "";
+    public JsonElement Extra { get; set; }
+}
+""";
+        await Verify(source, out var compilation);
+
+        // Verify that JsonElement parameter doesn't crash validation
+        await VerifyEndpoint(compilation, "/json-element", async (endpoint, serviceProvider) =>
+        {
+            var payload = """{"foo": "bar"}""";
+            var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+            await endpoint.RequestDelegate(context);
+
+            Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+        });
+
+        // Verify that a type containing a JsonElement property still validates its other properties
+        await VerifyEndpoint(compilation, "/type-with-json-element", async (endpoint, serviceProvider) =>
+        {
+            // Empty Name should fail validation since it has [Required]
+            var payload = """{"Name": "", "Extra": {"a": 1}}""";
+            var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+            await endpoint.RequestDelegate(context);
+
+            var problemDetails = await AssertBadRequest(context);
+            Assert.Collection(problemDetails.Errors, kvp =>
+            {
+                Assert.Equal("Name", kvp.Key);
+            });
+        });
+
+        // Verify valid input passes
+        await VerifyEndpoint(compilation, "/type-with-json-element", async (endpoint, serviceProvider) =>
+        {
+            var payload = """{"Name": "test", "Extra": {"a": 1}}""";
+            var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+            await endpoint.RequestDelegate(context);
+
+            Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+        });
+    }
+
+    [Fact]
+    public async Task SkipsNonReadableAndStaticProperties()
+    {
+        var source = """
+using System;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Validation;
+using Microsoft.Extensions.DependencyInjection;
+
+var builder = WebApplication.CreateBuilder();
+
+builder.Services.AddValidation();
+
+var app = builder.Build();
+
+app.MapPost("/order", ([FromBody] Order request) => Results.Ok("Passed"));
+
+app.Run();
+
+public class Address
+{
+    [Required]
+    public string Street { get; set; } = "";
+}
+
+public class Order
+{
+    [Required]
+    public string CustomerName { get; set; } = "";
+
+    [Required]
+    public Address ShippingAddress { get; set; }
+
+    // Static property with a validatable type — should not be emitted
+    public static Address DefaultAddress { get; set; } = new();
+
+    // Write-only property with a validatable type — should not be emitted
+    public Address InternalAddress { set { } }
+
+    // Property with non-public getter — should not be emitted
+    public Address CachedAddress { internal get; set; }
+}
+""";
+        await Verify(source, out var compilation);
+
+        // Only CustomerName and ShippingAddress should be validated
+        await VerifyEndpoint(compilation, "/order", async (endpoint, serviceProvider) =>
+        {
+            var payload = """{"CustomerName": "", "ShippingAddress": {"Street": ""}}""";
+            var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+            await endpoint.RequestDelegate(context);
+
+            var problemDetails = await AssertBadRequest(context);
+            Assert.Equal(2, problemDetails.Errors.Count);
+            Assert.Contains(problemDetails.Errors, kvp => kvp.Key == "CustomerName");
+            Assert.Contains(problemDetails.Errors, kvp => kvp.Key == "ShippingAddress.Street");
+        });
+
+        await VerifyEndpoint(compilation, "/order", async (endpoint, serviceProvider) =>
+        {
+            var payload = """{"CustomerName": "Alice", "ShippingAddress": {"Street": "123 Main St"}}""";
+            var context = CreateHttpContextWithPayload(payload, serviceProvider);
+
+            await endpoint.RequestDelegate(context);
+
+            Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+        });
+    }
 }

+ 0 - 15
src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateParameters#ValidatableInfoResolver.g.verified.cs

@@ -86,21 +86,6 @@ namespace Microsoft.Extensions.Validation.Generated
                 );
                 return true;
             }
-            if (type == typeof(global::System.Collections.Generic.Dictionary<string, global::TestService>))
-            {
-                validatableInfo = new GeneratedValidatableTypeInfo(
-                    type: typeof(global::System.Collections.Generic.Dictionary<string, global::TestService>),
-                    members: [
-                        new GeneratedValidatablePropertyInfo(
-                            containingType: typeof(global::System.Collections.Generic.Dictionary<string, global::TestService>),
-                            propertyType: typeof(global::TestService),
-                            name: "this[]",
-                            displayName: "this[]"
-                        ),
-                    ]
-                );
-                return true;
-            }
 
             return false;
         }

+ 189 - 0
src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.SkipsIndexerPropertiesOnTypes#ValidatableInfoResolver.g.verified.cs

@@ -0,0 +1,189 @@
+//HintName: ValidatableInfoResolver.g.cs
+#nullable enable annotations
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+#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.GetPropertyValidationAttributes(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)
+        {
+            Type = type;
+        }
+
+        [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+        internal global::System.Type Type { get; }
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
+            => ValidationAttributeCache.GetTypeValidationAttributes(Type);
+    }
+
+    [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::TypeWithJsonElement))
+            {
+                validatableInfo = new GeneratedValidatableTypeInfo(
+                    type: typeof(global::TypeWithJsonElement),
+                    members: [
+                        new GeneratedValidatablePropertyInfo(
+                            containingType: typeof(global::TypeWithJsonElement),
+                            propertyType: typeof(string),
+                            name: "Name",
+                            displayName: "Name"
+                        ),
+                    ]
+                );
+                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<global::Microsoft.Extensions.Validation.ValidationOptions>? 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<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _propertyCache = new();
+        private static readonly global::System.Lazy<global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]>> _lazyTypeCache = new (() => new ());
+        private static global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> TypeCache => _lazyTypeCache.Value;
+
+        public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetPropertyValidationAttributes(
+            [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 _propertyCache.GetOrAdd(key, static k =>
+            {
+                var results = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>();
+
+                // Get attributes from the property
+                var property = k.ContainingType.GetProperty(k.PropertyName);
+                if (property != null)
+                {
+                    var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
+                        .GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(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<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(parameter, inherit: true);
+
+                        results.AddRange(paramAttributes);
+
+                        break;
+                    }
+                }
+
+                return results.ToArray();
+            });
+        }
+
+
+        public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetTypeValidationAttributes(
+            [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+            global::System.Type type
+        )
+        {
+            return TypeCache.GetOrAdd(type, static t =>
+            {
+                var typeAttributes = global::System.Reflection.CustomAttributeExtensions
+                        .GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(t, inherit: true);
+                return global::System.Linq.Enumerable.ToArray(typeAttributes);
+            });
+        }
+    }
+}

+ 210 - 0
src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.SkipsNonReadableAndStaticProperties#ValidatableInfoResolver.g.verified.cs

@@ -0,0 +1,210 @@
+//HintName: ValidatableInfoResolver.g.cs
+#nullable enable annotations
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+#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.GetPropertyValidationAttributes(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)
+        {
+            Type = type;
+        }
+
+        [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+        internal global::System.Type Type { get; }
+
+        protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes()
+            => ValidationAttributeCache.GetTypeValidationAttributes(Type);
+    }
+
+    [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::Address))
+            {
+                validatableInfo = new GeneratedValidatableTypeInfo(
+                    type: typeof(global::Address),
+                    members: [
+                        new GeneratedValidatablePropertyInfo(
+                            containingType: typeof(global::Address),
+                            propertyType: typeof(string),
+                            name: "Street",
+                            displayName: "Street"
+                        ),
+                    ]
+                );
+                return true;
+            }
+            if (type == typeof(global::Order))
+            {
+                validatableInfo = new GeneratedValidatableTypeInfo(
+                    type: typeof(global::Order),
+                    members: [
+                        new GeneratedValidatablePropertyInfo(
+                            containingType: typeof(global::Order),
+                            propertyType: typeof(string),
+                            name: "CustomerName",
+                            displayName: "CustomerName"
+                        ),
+                        new GeneratedValidatablePropertyInfo(
+                            containingType: typeof(global::Order),
+                            propertyType: typeof(global::Address),
+                            name: "ShippingAddress",
+                            displayName: "ShippingAddress"
+                        ),
+                    ]
+                );
+                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<global::Microsoft.Extensions.Validation.ValidationOptions>? 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<CacheKey, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> _propertyCache = new();
+        private static readonly global::System.Lazy<global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]>> _lazyTypeCache = new (() => new ());
+        private static global::System.Collections.Concurrent.ConcurrentDictionary<global::System.Type, global::System.ComponentModel.DataAnnotations.ValidationAttribute[]> TypeCache => _lazyTypeCache.Value;
+
+        public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetPropertyValidationAttributes(
+            [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 _propertyCache.GetOrAdd(key, static k =>
+            {
+                var results = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>();
+
+                // Get attributes from the property
+                var property = k.ContainingType.GetProperty(k.PropertyName);
+                if (property != null)
+                {
+                    var propertyAttributes = global::System.Reflection.CustomAttributeExtensions
+                        .GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(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<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(parameter, inherit: true);
+
+                        results.AddRange(paramAttributes);
+
+                        break;
+                    }
+                }
+
+                return results.ToArray();
+            });
+        }
+
+
+        public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetTypeValidationAttributes(
+            [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)]
+            global::System.Type type
+        )
+        {
+            return TypeCache.GetOrAdd(type, static t =>
+            {
+                var typeAttributes = global::System.Reflection.CustomAttributeExtensions
+                        .GetCustomAttributes<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(t, inherit: true);
+                return global::System.Linq.Enumerable.ToArray(typeAttributes);
+            });
+        }
+    }
+}