In AoT/trimmed Web APIs, [AsParameters] cannot be used on a class with a constructor that contains nullable reference type parameters
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
### Describe the bug
With .NET 9 we can no longer build a "dotnet new webapiaot" project when the endpoints have `[AsParameters]` attribute applied to a class with constructor parameters of a nullable reference type. The build fails with "CS8639 The typeof operator cannot be used on a nullable reference type". This worked fine on .NET 8.
The example below shows several usages of app.MapGet() with query parameters. The comments describe the results.
```csharp
using System.Text.Json.Serialization;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default));
var app = builder.Build();
// Builds and works fine.
app.MapGet("/test1", (string str) => str);
// Builds fine. Works fine if you call it with ?str=something, throws ArgumentNullException otherwise
// because in the GeneratedRouteBuilderExtensions.g.cs the return type for this endpoint is a non-nullable string
app.MapGet("/test2", (string? str = null) => str);
// Builds and works fine.
app.MapGet("/test3", ([AsParameters] ParametersThree parameters) => parameters.Str);
// Build fails with CS8639 The typeof operator cannot be used on a nullable reference type.
// On .NET 8 this builds fine and works like "/test2".
app.MapGet("/test4", ([AsParameters] ParametersFour parameters) => parameters.Str);
app.Run();
public sealed record ParametersThree(string Str);
public sealed record ParametersFour(string? Str = null);
[JsonSerializable(typeof(ParametersThree))]
[JsonSerializable(typeof(ParametersFour))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
```
And here's the generated code for "/test4".
```csharp
MetadataPopulator populateMetadata = (methodInfo, options) =>
{
Debug.Assert(options != null, "RequestDelegateFactoryOptions not found.");
Debug.Assert(options.EndpointBuilder != null, "EndpointBuilder not found.");
options.EndpointBuilder.Metadata.Add(new System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.RequestDelegateGenerator, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "9.0.0.0"));
options.EndpointBuilder.Metadata.Add(new ParameterBindingMetadata("Str", new PropertyAsParameterInfo(true, typeof(ParametersFour)!.GetProperty("Str")!, typeof(ParametersFour).GetConstructor(new[] { typeof(string?) })?.GetParameters()[0]), hasTryParse: false, hasBindAsync: false, isOptional: true));
options.EndpointBuilder.Metadata.Add(new ProducesResponseTypeMetadata(statusCode: StatusCodes.Status200OK, type: typeof(string), contentTypes: GeneratedMetadataConstants.PlaintextContentType));
return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
};
```
### Expected Behavior
The code compiles without throwing CS8639 and works the same as "/test2", like it was on .NET 8.
Ideally, the generator also respects the return type for both "/test2" and "/test4", which is a nullable `string?`.
### Steps To Reproduce
Run .NET 9's `dotnet new webapiaot`, replace the code in Program.cs with the above example and start a build.
### Exceptions (if any)
_No response_
### .NET Version
9.0.100
### Anything else?
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.