Is there an existing issue for this?
Describe the bug
Minimal API endpoint discovery fails when a handler has an optional non-nullable struct parameter initialized with = default, for example:
app.MapGet("/gonnafail", ([FromQuery] Guid skip = default, [FromQuery] int take = default) => Results.Ok());
The app starts, but the first request causes endpoint construction and routing initialization to fail with:
System.ArgumentException: Argument types do not match
at System.Linq.Expressions.Expression.Constant(Object value, Type type)
at Microsoft.AspNetCore.Http.RequestDelegateFactory.BindParameterFromValue(...)
Expected Behavior
Guid skip = default should behave like an optional query parameter whose default value is default(Guid).
Steps To Reproduce
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/ok", ([FromQuery] Guid? skip = default, [FromQuery] int take = default) => Results.Ok());
app.MapGet("/gonnafail", ([FromQuery] Guid skip = default, [FromQuery] int take = default) => Results.Ok());
app.Run();
Then request either endpoint. Once the route table is materialized, endpoint initialization fails because of /gonnafail.
Exceptions (if any)
No response
.NET Version
10.0.100
Anything else?
Why this happens
Reflection reports:
([FromQuery] Guid skip = default) => { }
as:
HasDefaultValue == true
DefaultValue == null
for Guid and some other structs such as DateTime and TimeSpan.
ASP.NET Core treats the parameter as optional because of HasDefaultValue, then calls:
CreateDefaultValueExpression(parameter.DefaultValue, parameter.ParameterType)
Inside CreateDefaultValueExpression, when defaultValue is null, it currently does:
Expression.Constant(null, parameterType)
That is invalid for non-nullable value types such as Guid, which produces the exception above.
Relevant source
ASP.NET Core release/10.0 source:
if (defaultValue is null)
{
return Expression.Constant(null, parameterType);
}
This is the problematic behavior for non-nullable value types.
Suspected fix
In CreateDefaultValueExpression, when defaultValue is null and parameterType is a non-nullable value type, use:
Expression.Default(parameterType)
instead of:
Expression.Constant(null, parameterType)
Something like:
if (defaultValue is null)
{
return Nullable.GetUnderlyingType(parameterType) is not null || !parameterType.IsValueType
? Expression.Constant(null, parameterType)
: Expression.Default(parameterType);
}
Additional notes
- This does not repro with
int x = default, because reflection returns 0 for int.
- It does repro with at least
Guid, DateTime, and TimeSpan, where reflection reports DefaultValue == null.
- Using
Guid? skip = default works as expected.
Is there an existing issue for this?
Describe the bug
Minimal API endpoint discovery fails when a handler has an optional non-nullable struct parameter initialized with
= default, for example:The app starts, but the first request causes endpoint construction and routing initialization to fail with:
Expected Behavior
Guid skip = defaultshould behave like an optional query parameter whose default value isdefault(Guid).Steps To Reproduce
Then request either endpoint. Once the route table is materialized, endpoint initialization fails because of
/gonnafail.Exceptions (if any)
No response
.NET Version
10.0.100
Anything else?
Why this happens
Reflection reports:
as:
HasDefaultValue == trueDefaultValue == nullfor
Guidand some other structs such asDateTimeandTimeSpan.ASP.NET Core treats the parameter as optional because of
HasDefaultValue, then calls:Inside
CreateDefaultValueExpression, whendefaultValue is null, it currently does:That is invalid for non-nullable value types such as
Guid, which produces the exception above.Relevant source
ASP.NET Core
release/10.0source:BindParameterFromValueusesCreateDefaultValueExpression(parameter.DefaultValue, parameter.ParameterType)for optional parseable parameters:https://raw.githubusercontent.com/dotnet/aspnetcore/release/10.0/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
CreateDefaultValueExpressioncurrently does:This is the problematic behavior for non-nullable value types.
Suspected fix
In
CreateDefaultValueExpression, whendefaultValue is nullandparameterTypeis a non-nullable value type, use:instead of:
Something like:
Additional notes
int x = default, because reflection returns0forint.Guid,DateTime, andTimeSpan, where reflection reportsDefaultValue == null.Guid? skip = defaultworks as expected.