Skip to content

Implement runtime-based IValidatableTypeInfoResolver implementation #61220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
captainsafia opened this issue Mar 28, 2025 · 0 comments · May be fixed by #62091
Open

Implement runtime-based IValidatableTypeInfoResolver implementation #61220

captainsafia opened this issue Mar 28, 2025 · 0 comments · May be fixed by #62091
Assignees
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-validation Issues related to model validation in minimal and controller-based APIs
Milestone

Comments

@captainsafia
Copy link
Member

captainsafia commented Mar 28, 2025

🚀 Goal

Provide a runtime implementation of IValidatableTypeInfoResolver so that minimal-API validation still works when the source-generator path is unavailable (e.g., dynamic compilation, IDEs without generators, or environments where generators are turned off).

We already have a runtime implementation for parameter discovery (RuntimeValidatableParameterInfoResolver), but type discovery still falls back to the generated-code path. This issue tracks filling that gap.


📚 Background & Current State

  • Compile-time story
    The Microsoft.AspNetCore.Http.ValidationsGenerator source-generator analyzes user code and emits a GeneratedValidatableInfoResolver that can resolve every validatable type/property via static look-ups (no reflection, very AOT-friendly).

  • Runtime story

    • RuntimeValidatableParameterInfoResolver already examines method parameters with reflection.
    • The type side (TryGetValidatableTypeInfo) is currently a stub that always returns false.
  • Why we need a runtime fallback

    • Enables validation in projects that do not reference the generator-capable SDK.
    • Keeps behavior consistent when developers disable generators during debugging.
    • Unblocks dynamic scenarios (e.g., plugins, Roslyn scripting).

🗺️ High-level Design

Concern Runtime behavior
Discovery algorithm Reflect over the supplied Type, walking public instance properties recursively to build a ValidatableTypeInfo graph that mirrors the compile-time generator’s output.
Performance Cache results in ConcurrentDictionary<Type, IValidatableInfo?> to avoid repeated reflection.
Cycles Track a HashSet<Type> during the walk to break infinite recursion (return null for already-seen types).
Trimming Avoid type.GetProperties() overloads that allocate attribute arrays; use BindingFlags filters and store only needed PropertyInfos.
Thread-safety All caches must be static and thread-safe; rely on ConcurrentDictionary.GetOrAdd instead of lock.
Registration order Add the new resolver to ValidationOptions.Resolvers after generated resolvers so compile-time wins when present, but before any user-added fallback.

🔨 Step-by-Step Tasks

  1. Create the file
    src/Http/Http.Abstractions/src/Validation/RuntimeValidatableTypeInfoResolver.cs
    (namespace Microsoft.AspNetCore.Http.Validation).

  2. Scaffold the class

    internal sealed class RuntimeValidatableTypeInfoResolver : IValidatableInfoResolver
    {
        private static readonly ConcurrentDictionary&lt;Type, IValidatableInfo?&gt; _cache = new();
    
        public bool TryGetValidatableTypeInfo(
            Type type,
            [NotNullWhen(true)] out IValidatableInfo? info)
        {
            // TODO – implement
        }
    
        // Parameter discovery is handled elsewhere
        public bool TryGetValidatableParameterInfo(
            ParameterInfo p,
            [NotNullWhen(true)] out IValidatableInfo? i)
        {
            i = null;
            return false;
        }
    }
  3. Implement the discovery walk

    • Bail out early if the type is primitive, enum, or one of the special cases handled by RuntimeValidatableParameterInfoResolver.IsClass.
    • Collect [ValidationAttribute] instances applied to the type.
    • Iterate over each PropertyInfo:
      • Determine flags:
        • IsEnumerable → implements IEnumerable but not string.
        • IsNullableNullable.GetUnderlyingType != null or reference type.
        • IsRequired → property has [Required] or is a non-nullable reference type.
        • HasValidatableType → recurse into property.PropertyType and check result.
      • Construct a RuntimeValidatablePropertyInfo object (mirrors the pattern in the parameter resolver).
    • Create a RuntimeValidatableTypeInfo instance derived from ValidatableTypeInfo.
    • Cache the result (null counts) before returning.
  4. Unit tests

    • Add tests under src/Http/Http.Extensions/test/….

    • Test cases:

      Scenario Expectation
      POCO with [Required] properties Attributes surfaced
      Nested complex types Recursion works
      Collection of complex types IsEnumerable == true, HasValidatableType == true
      Cyclic reference (A ↔ B) No stack overflow; duplicate types handled
    • Use Validator.TryValidateObject in assertions to validate behavior end-to-end.

  5. Wire-up
    In ServiceCollectionValidationExtensions.AddValidation, register:

    options.Resolvers.Add(new RuntimeValidatableTypeInfoResolver());

    Place it after generated resolver registration.


✅ Acceptance Criteria

  • A sample minimal-API app without the ValidationsGenerator package validates request bodies & query parameters successfully at runtime.
  • All new and existing unit tests pass

🔗 Helpful Code References

  • Generator logic: src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.ValidationsGenerator/*
  • Existing runtime parameter resolver: src/Http/Http.Abstractions/src/Validation/RuntimeValidatableParameterInfoResolver.cs
@ghost ghost added the area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc label Mar 28, 2025
@captainsafia captainsafia added feature-validation Issues related to model validation in minimal and controller-based APIs area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc labels Mar 28, 2025
@captainsafia captainsafia added this to the Backlog milestone Mar 28, 2025
@captainsafia captainsafia assigned Copilot and unassigned Copilot May 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-validation Issues related to model validation in minimal and controller-based APIs
Projects
None yet
1 participant