dotnet / dotnet/aspnetcore

Request model binding with `FromQuery` properties `AsParameters` results in all properties being required.

Open
#52,881 10 comments 8 reactions 0 assignees View on GitHub
area-minimal
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

Binding a model exclusively using `FromQuery` properties `AsParameters` results in all properties being treated as if required.

An endpoint binding a POCO model as parameters treats each and every property in the `MyReq` model as required.
```csharp
[AsParameters] MyReq request
```

Properties in the model which are not required should not be required in the search string.
The definition below illustrates the expectation, but not the current implementation, because currently each one of these properties is required, even those with default values.
```csharp
public sealed record MyReq
{
[FromQuery()]
public string Prop1{ get; init; } = "*"; // not required
[FromQuery()]
public decimal? Prop2 { get; init; } // not required
[FromQuery()]
public string? Prop3 { get; init; } // not required
[FromQuery()]
public required string Prop4 {get; init; } // required
[FromQuery(), Required]
public string Prop5 { get; init; } = ""; // required
[FromQuery()]
public int Prop6 { get; init; } // not required
[FromQuery()]
public string Prop7 { get; init; } // not required (has compiler warning)
}
```

### Expected Behavior

Given a request model, properties with default values and without the `required` modifier or annotation, must not be recognized as required parameters.

### Steps To Reproduce

Model:
```csharp
public sealed record ProfitRequest
{
[FromQuery(Name = "gem_name")]
public string GemNameWindcard { get; init; } = "*";
[FromQuery(Name = "min_sell_price_chaos")]
public decimal? MinSellPriceChaos { get; init; } = null;
[FromQuery(Name = "max_buy_price_chaos")]
public decimal? MaxBuyPriceChaos { get; init; } = null;
[FromQuery(Name = "min_experience_delta")]
public decimal? MinExperienceDelta { get; init; } = null;
}
```

Builder:
```csharp
var app = builder.Build();

app
.MapGet("gem-profit", async (
[FromServices] ProfitService profitService,
[AsParameters] ProfitRequest request,
CancellationToken cancellationToken = default
) =>
{
var data = await profitService.GetProfitAsync(request, cancellationToken).ConfigureAwait(false);
return data;
});
```

Failed request:
```http
GET http://127.0.0.1:5000/gem-profit?min_experience_delta=65000000
```

Successful request:
```http
GET http://127.0.0.1:5000/gem-profit?gem_name=*&min_sell_price_chaos=0&max_buy_price_chaos=0&min_experience_delta=65000000&items_offset=0&items_count=10
```

### Exceptions (if any)

Microsoft.AspNetCore.Http.BadHttpRequestException: Required parameter "string GemNameWindcard" was not provided from query string.
at lambda_method933(Closure, Object, HttpContext)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

### .NET Version

8.0.100

### Anything else?

.NET SDK:
Version: 8.0.100
Commit: 57efcf1350
Workload version: 8.0.100-manifests.6a1e483a

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19045
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.100\

.NET workloads installed:
Workload version: 8.0.100-manifests.6a1e483a
There are no installed workloads to display.

Host:
Version: 8.0.0
Architecture: x64
Commit: 5535e31a71

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
6.0.417 [C:\Program Files\dotnet\sdk]
7.0.404 [C:\Program Files\dotnet\sdk]
8.0.100 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

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.