dotnet / dotnet/aspnetcore

In AoT/trimmed Web APIs, default values of primary constructor parameters inside [AsParameters] record structs are ignored

Open
#56,396 1 comment 0 reactions 0 assignees View on GitHub
area-minimal NativeAOT
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 5h
Merged PRs (30d)
276

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

The following short code sample shows several scenarios of using `app.MapGet()` with default parameter values.
Please note the comments after each `app.MapGet()`.

`Program.cs`
```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();

app.MapGet("/test1/{text}", (string text, int number = 1) => $"{text} - {number}"); // works
app.MapGet("/test2/{text}", ([AsParameters] PrmsClass prms) => prms); // works
app.MapGet("/test3/{text}", ([AsParameters] PrmsStruct prms) => prms); // throws when Number is not provided

app.Run();

sealed record PrmsClass(string Text, int Number = 1);
readonly record struct PrmsStruct(string Text, int Number = 1);

[JsonSerializable(typeof(PrmsClass))]
[JsonSerializable(typeof(PrmsStruct))]
partial class AppJsonSerializerContext : JsonSerializerContext
{
}
```

`WebApiAot.csproj`
```xml


net8.0
enable
enable
true
true

```

Both `PrmsClass` and `PrmsStruct` have a default value for the parameter `Number` but according to `GeneratedRouteBuilderExtensionsCore`, in case of the class this parameter is considered optional whereas for `PrmsStruct` it is mandatory.

Parsing logic for `PrmsClass.Number`:
```csharp
// Endpoint Parameter: Number (Type = int, IsOptional = True, IsParsable = True, IsArray = False, Source = RouteOrQuery)
var Number_raw = Number_RouteOrQueryResolver(httpContext);
var Number_temp = Number_raw.Count > 0 ? (string?)Number_raw : null;
global::System.Int32 Number_parsed_temp = default;
if (GeneratedRouteBuilderExtensionsCore.TryParseExplicit(Number_temp!, CultureInfo.InvariantCulture, out var Number_temp_parsed_non_nullable))
{
Number_parsed_temp = Number_temp_parsed_non_nullable;
}
else if (string.IsNullOrEmpty(Number_temp))
{
Number_parsed_temp = 1;
}
else
{
wasParamCheckFailure = true;
}
int Number_local = Number_parsed_temp!;

var prms_local = new global::PrmsClass(Text_local, Number_local);
```

Parsing logic for `PrmsStruct.Number`:
```csharp
// Endpoint Parameter: Number (Type = int, IsOptional = False, IsParsable = True, IsArray = False, Source = RouteOrQuery)
var Number_raw = Number_RouteOrQueryResolver(httpContext);
if (Number_raw is StringValues { Count: 0 })
{
wasParamCheckFailure = true;
logOrThrowExceptionHelper.RequiredParameterNotProvided("int", "Number", "route or query string"); // throws here
}
var Number_temp = (string?)Number_raw;
if (!GeneratedRouteBuilderExtensionsCore.TryParseExplicit(Number_temp!, CultureInfo.InvariantCulture, out var Number_parsed_temp))
{
if (!string.IsNullOrEmpty(Number_temp))
{
logOrThrowExceptionHelper.ParameterBindingFailed("int", "Number", Number_temp);
wasParamCheckFailure = true;
}
}
int Number_local = Number_parsed_temp!;

var prms_local = new global::PrmsStruct { Text = Text_local, Number = Number_local };
```

### Expected Behavior

The generated code accounts for the default value of `PrmsStruct.Number` and treats it as an optional parameter.

### Steps To Reproduce

_No response_

### Exceptions (if any)

_No response_

### .NET Version

8.0.302

### 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.