Skip to content

Treat empty query strings as null for nullable value types#65816

Open
BloodShop wants to merge 5 commits intodotnet:mainfrom
BloodShop:fix/nullable-query-empty-string
Open

Treat empty query strings as null for nullable value types#65816
BloodShop wants to merge 5 commits intodotnet:mainfrom
BloodShop:fix/nullable-query-empty-string

Conversation

@BloodShop
Copy link

Summary

Minimal APIs throw a 500 error when binding empty query string values (?myBool=) to nullable value type parameters like bool?, int?, etc.

Controllers handle this correctly — they treat empty query values as null for nullable parameters. Minimal APIs should match that behavior.

Reproduction

app.MapGet("/test", ([FromQuery] bool? myBool) => Results.Ok());

Calling /test?myBool= throws:

System.InvalidOperationException: Failed to bind parameter "Nullable<Boolean> myBool" from "".

Root Cause

In RequestDelegateFactory.cs, when constructing the parameter binding expression for parseable types, the code checks if (tempSourceString != null) before attempting TryParse.

But when the query string is ?myBool=, the value is an empty string (""), not null. So:

  1. The null check passes
  2. bool.TryParse("", out var result) is called
  3. TryParse returns false (empty string is not a valid bool)
  4. The fail block logs an error and throws

For nullable value types, empty strings should be treated as null, not as invalid input.

The Fix

For nullable value types (Nullable<T>), use IsNotNullOrEmpty instead of IsNotNull when deciding whether to attempt parsing:

var isNullableValueType = Nullable.GetUnderlyingType(parameter.ParameterType) != null;
var sourceCheckExpr = isOptional && isNullableValueType
    ? TempSourceStringIsNotNullOrEmptyExpr  // Skip empty strings
    : TempSourceStringNotNullExpr;

When the query value is empty, we skip the TryParse block entirely, and the parameter remains at its default value (null for nullable types).

Changes

RequestDelegateFactory.cs

  • Detect nullable value types with Nullable.GetUnderlyingType
  • Use IsNotNullOrEmpty check for nullable value types
  • Existing behavior unchanged for non-nullable types and reference types

RequestDelegateCreationTests.QueryParameters.cs (tests)

  • MapAction_NullableBoolParam_WithEmptyQueryStringValue_SetsNull — verifies ?myBool= binds to null
  • MapAction_NullableIntParam_WithEmptyQueryStringValue_SetsNull — verifies ?value= binds to null

Behavior After Fix

Query String Parameter Type Before After
?myBool= bool? 500 error null
?myBool=true bool? true true
?myInt= int? 500 error null
?name= string? "" (empty string) "" (unchanged) ✅

Breaking Changes

None. This only changes error behavior to match controller semantics.

Fixes #65754

When binding query parameters like ?myBool= (empty value) to nullable
value types (bool?, int?, etc.), the framework was attempting to parse
the empty string and failing with a 500 error.

Controllers handle this correctly by treating empty query values as null
for nullable parameters. Minimal APIs should do the same.

The fix: for nullable value types with FromQuery binding, check for
empty strings before attempting TryParse. Empty strings are skipped,
leaving the parameter at its default value (null).

Changes:
- RequestDelegateFactory: Use NotNullOrEmpty check for nullable value
  types instead of NotNull
- Add tests for bool? and int? with empty query string values

Fixes dotnet#65754
@github-actions github-actions bot added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Mar 17, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 17, 2026
@BloodShop
Copy link
Author

@dotnet-policy-service agree

The conditional expression was mixing UnaryExpression and BinaryExpression types,
which caused CS0173 compiler error. Cast to common Expression type to fix.

Also removed incorrect 'isOptional' check - nullable value types should always
treat empty query strings as null, not just when optional.
@BloodShop
Copy link
Author

🔧 Root Cause Found & Fixed

The PR was failing to compile due to a type mismatch in the conditional expression:

The Problem

Line 1804 in RequestDelegateFactory.cs mixes two incompatible Expression types:

  • TempSourceStringIsNotNullOrEmptyExpr is a UnaryExpression
  • TempSourceStringNotNullExpr is a BinaryExpression

This causes CS0173: Type of conditional expression cannot be determined — which cascades to fail ALL aspnetcore-ci builds.

The Fix

// Before (❌ compilation error)
var sourceCheckExpr = isOptional && isNullableValueType
    ? TempSourceStringIsNotNullOrEmptyExpr
    : TempSourceStringNotNullExpr;

// After (✅ compiles cleanly)
var sourceCheckExpr = (Expression)(isNullableValueType
    ? TempSourceStringIsNotNullOrEmptyExpr
    : TempSourceStringNotNullExpr);

Also removed the faulty isOptional check — nullable value types should treat empty strings as null regardless of optionality.

Verification

Microsoft.AspNetCore.Http.Extensions builds successfully
Fix has been committed locally

The fix is minimal (2-line change) and surgical. Ready for testing.

@BloodShop BloodShop force-pushed the fix/nullable-query-empty-string branch from 3182533 to a736080 Compare March 18, 2026 10:49
BloodShop and others added 3 commits March 18, 2026 13:01
…otnet#65805)

When an endpoint has multiple parameters including [FromBody] and other injected
dependencies, the request body description should use the [FromBody] parameter's
XML comment, not another parameter's comment.

Example:
  PostData([FromBody] SomeData data, ILogger logger, CancellationToken ct)
  Expected: description from 'data' parameter
  Actual: description from 'ct' parameter (incorrectly)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Minimal API : Converting empty string to Nullable (ex: bool?) with [FromQuery] binding

2 participants