dotnet / dotnet/aspnetcore

In AoT/trimmed Web APIs, [AsParameters] cannot be used on a class with a constructor that contains nullable reference type parameters

Open
#58,953 2 comments 2 reactions 0 assignees View on GitHub
area-minimal NativeAOT
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.