Skip to content

Minimal APIs: optional non-nullable struct query parameters with = default throw in RequestDelegateFactory #66090

@vinnikov

Description

@vinnikov

Is there an existing issue for this?

  • I have searched the existing issues

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.

Metadata

Metadata

Labels

area-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-rdg

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions