Skip to content

RequestDelegateGenerator: [AsParameters] emits unqualified type names — CS0234 when user namespace collides #557

@DeagleGross

Description

@DeagleGross

RequestDelegateGenerator: [AsParameters] emits unqualified type names — CS0234 when user namespace collides

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

The minimal-API request delegate source generator emits unqualified type names in the PropertyAsParameterInfo metadata it builds for [AsParameters] containers. The generated file is wrapped in namespace Microsoft.AspNetCore.Http.Generated { ... }, so any user type whose top-level namespace shadows a name on the C# resolution walk-up path resolves to the wrong namespace and the generated file fails to compile with CS0234.

A small repro: an [AsParameters] container that lives in a top-level namespace called Http (or Routing, Builder, Mvc, Json, Features, Metadata, Extensions, AspNetCore, …) will produce a generator output that references e.g.

typeof(Http.MyContainer)!.GetProperty("…")

inside namespace Microsoft.AspNetCore.Http.Generated. C# walks enclosing namespaces first, finds Http as the child of Microsoft.AspNetCore (= Microsoft.AspNetCore.Http), looks for MyContainer there, and reports CS0234.

Same story for the constructor lookup helpers in SymbolExtensions.GetParameterInfoFromConstructorCode (used by PropertyAsParameterInfo), which emit both the container type and each constructor parameter type unqualified.

Affected call sites

  • src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/EndpointParameter.cs:46
  • src/Shared/RoslynUtils/SymbolExtensions.cs:299
  • src/Shared/RoslynUtils/SymbolExtensions.cs:300

All three call ITypeSymbol.ToDisplayString() with no SymbolDisplayFormat, which defaults to a short form that does not include global::. By contrast, other emission sites in the generator already use SymbolDisplayFormat.FullyQualifiedFormat (or the project-local EmitterConstants.DisplayFormat, which sets globalNamespaceStyle: Included) and therefore correctly emit global::Http.MyType.

Why this is rarely hit today

Two conditions must coincide:

  1. The code path emits via the buggy typeof(...) metadata. Only [AsParameters] triggers that path — direct body / route / query / header binding for the same user type goes through EmitterConstants.DisplayFormat, which already includes global::.
  2. The user type's namespace first segment shadows a namespace visible during the resolution walk-up inside Microsoft.AspNetCore.Http.Generated. The repository's own [AsParameters] test containers all live in Microsoft.AspNetCore.Http.Generators.Tests, which never collides, so the latent bug never surfaced in CI.

Real applications that organize types under short top-level namespaces (e.g. Models, Http.Dtos, Mvc.Inputs, Routing.Configuration, Json.Models) will trip CS0234 in generated .g.cs files with diagnostics that point at lines they cannot edit.

Expected behavior

The generator should always emit fully-qualified type references (with global::) in the metadata it constructs. Switching the three ToDisplayString() calls above to ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) is enough; the existing VerifyAsParametersBaseline.generated.txt baseline needs to be regenerated to pick up global:: prefixes on the affected typeof(...) expressions.

Steps to reproduce

  1. In a Minimal API project with EnableRequestDelegateGenerator=true, declare:

    namespace Http;
    
    public record TenantArgs(HttpContext HttpContext, [FromRoute] int TenantId, MyBody Payload);
    public record MyBody(string Value);
  2. Map:

    app.MapPost("/t/{TenantId}", ([AsParameters] Http.TenantArgs args) => args.TenantId);
  3. Build. Generated GeneratedRouteBuilderExtensions.g.cs fails with:

    error CS0234: The type or namespace name 'TenantArgs' does not exist in the namespace 'Microsoft.AspNetCore.Http'

Workaround

Rename the user namespace so its first segment does not collide with any namespace reachable from Microsoft.AspNetCore.Http.Generated (e.g. MyApp.Http instead of Http).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions