question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`string[]` bound to querystring in minimal API should return `400` when not present

See original GitHub issue

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When you have a minimal API that takes a string[] (or StringValues) parameter which binds to the querystring, for example:

app.MapGet("/", (string[] q) => q);

calling the API without a querystring (e.g. /) should give a 400 response saying BadHttpRequestException: Required parameter "string[] q" was not provided from query string.

But this does not happen - instead q is an empty array. This differs from both of the following other examples:

app.MapGet("/", (string q) => q); //
app.MapGet("/", (StringValues q) => q); 

which both return a 400 response as expected.

Expected Behavior

When binding a required parameter in a minimal API, a value must be present in the binding source (typically the querystring when binding arrays), otherwise the API returns a 400. This is the documented behaviour which works for other parameter types.

Steps To Reproduce

To reproduce, create an api like this: app.MapGet("/", (string[] q) => q); and call the URL / (without a querystring). The response is [], where it should be a 400 Bad Request.

Exceptions (if any)

No response

.NET Version

7.0.101

Anything else?

I found this behaviour while reading some of the Expression generation code in RequestDelegateFactory. In particular CreateArgument() calls BindParameterFromValue which calls BindParameterFromExpression.

The argument expression generated by this code when binding a string[] parameter (called q in this example) to the querystring is similar to the following (some artisitic license):

Task Invoke(HttpContext httpContext)
{
    bool wasParamCheckFailure = false; // Added by RequestDelegateFactory.Create()

    string[] q_local = httpContext.Request.Query["q"] // 👈 This is the problem
    if (q_local == null) // 👈 Because this is never true.
    {
        wasParamCheckFailure = true;
        Log.RequiredParameterNotProvided(httpContext, "string[]", "q", "query");
    }

    if(wasParamCheckFailure) // Added by RequestDelegateFactory.Create()
    {
        httpContext.Response.StatusCode = 400;
        return Task.CompletedTask;
    }

    // .. call handler, handle response
}

I think the problem lies in the line above. Query["q"] returns StringValues.Empty when the item is not present, and is implicitly converted to a string[]. (string[])StringValues.Empty always returns an empty array, so is never null, so the check never succeeds.

Issue Analytics

  • State:open
  • Created 8 months ago
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
halter73commented, Jan 25, 2023

That said, it seems… odd… to have the inconsistency. As far as I can work out, this is now the only case where a nullable parameter can’t ever actually be null.

I think you’re right that arrays are currently the only type of parameter that can never be null, but it’s also the only supported parameter type other than StringValues that can already uniquely represent missing parameters without using null. If you were binding an array from the body, there’s a difference between [] and an empty body that needs to be represented.

To me this is similar to the following in DI where dependency will never be null:

public MyService(IEnumerable<MyMissingDependency> dependency = null)

The fact that (StringValues? myval) => { } gives myval as null, yet (string[]? myval) => { } won’t be null is a weird disconnect IMO.

I completely agree this is a weird disconnect. In a world where more people were aware of the existence of StringValues.Empty and redundancy of Nullable<StringValues>, I would have liked to use StringValues.Empty instead of null here as well. I don’t think empty arrays are nearly as obscure.

As for breaking people, I’d be surprised people would be marking arrays as explicitly nullable if they weren’t ever expecting the parameter to be null 🤔

I’m not as worried about passing null into a string[]? parameter. If someone went out of their way to make the parameter nullable, passing in null seems reasonable to me. After all, the original proposal from @davidfowl in https://github.com/dotnet/aspnetcore/issues/32516#issuecomment-1020275918 was, “If there are no values we pass an empty array unless the user makes the array nullable.”

However, going from calling the route handler with an empty array to short-circuiting with a 400 in the non-nullable string[]/int[]/whatever[] case does seem very risky. And then if we’re going to pass in an empty array in the non-nullable case, passing in null for the exact same request in the nullable case also seems inconsistent.

That said, I understand it could be a breaking change, in which case I’d suggest at least updating the documentation to highlight this exception to the rule?

Agreed. We should update that section to call out the behavior of both StringValues and string/tryparsable arrays.

0reactions
mitchdennycommented, Apr 14, 2023

@halter73 what was the actual action here? From my reading we won’t be changing any implementation here - but maybe a docs update?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using Query String Parameters with Minimal APIs
It seems that we cannot bind complex objects from query string ... This will result in returning HTTP 400 Bad Request to the...
Read more >
Generating argument expressions for minimal APIs ...
CreateArgument() generates to bind minimal API handler parameters. ... If not, the endpoint should return a 400 BadRequest response.
Read more >
How to Access Query Strings in Minimal APIs
This works, but I found it clunky. I wanted binding to work. I saw some examples that broke out the lambda to a...
Read more >
Parameter binding in Minimal API applications
Parameter binding is the process of converting request data into strongly typed parameters that are expressed by route handlers.
Read more >
Long string in JSON array causes 400 Bad Request ...
1 Answer. If you're getting a 400 Bad Request with ≈25 lines of relatively short strings then the most likely explanation is that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found