RequestDelegateGenerator: [AsParameters] emits unqualified type names — CS0234 when user namespace collides
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Is there an existing issue for this?
- [x] 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.
```csharp
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:
```csharp
namespace Http;
public record TenantArgs(HttpContext HttpContext, [FromRoute] int TenantId, MyBody Payload);
public record MyBody(string Value);
```
2. Map:
```csharp
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`).
Related https://github.com/dotnet/aspnetcore/issues/58633
Contributor guide
Assessment
This issue has not been assessed yet.