In AoT/trimmed Web APIs, default values of primary constructor parameters inside [AsParameters] record structs are ignored
- 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
Assessment
This issue has not been assessed yet.