Skip to content

Implement runtime-based IValidatableTypeInfoResolver implementation #61220

Open
@captainsafia

Description

@captainsafia

🚀 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

Metadata

Metadata

Assignees

Labels

area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-validationIssues related to model validation in minimal and controller-based APIs

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions