1

I'm in the process of implementing a REST API using ASP.NET Core Web API (v3.1). At the moment I'm not sure how to distinguish between an "undefined" value and a "null" value when passed through a query string in an URL.

As an example, let's assume that the API manages users, that all have a first name and a last name. In addition all users may have a middle name.

The REST API allows querying for these users like so:

GET api/user?lastName=doe

(this returns a collection of user resources, that have the last name "Doe"). The related controller class could look like this:

[HttpGet]
public IActionResult GetUsers(string firstName, string lastName, string middleName)
{
    // Search database for users
    // Ignore firstName and middleName, because they are null
}

My problem arises, when queries include the middle name:

GET api/user?middleName=null&firstName=john

This query (where middleName is explicitly set to null) should return users with the first name "John" and NO middle name. In contrast the request

GET api/user?firstName=john

(where middleName is undefined) should return all users with the first name "John" regardless of whether they have a middle name or not.

In both cases the middleName parameter inside my controller method is set to null. How can I distinguish between these queries in my controller?

EDIT:

My example is misleading and does not behave as I expected it to, because

GET api/user?middleName=null&firstName=john

sets middleName to the string value "null" instead of null (as in nothing).

I still want to let clients search for users without a middle name (where the database value for the middle name is NULL). How can this be passed through the URL and processed further by the controller?

Adrian at DMI
  • 75
  • 2
  • 8
  • 3
    In the query `api/user?middleName=null&firstName=john`, `null` is a string. So, `middleName` is NOT null, it has a value, and the value is `null`. – Tân Apr 01 '20 at 09:47
  • @Tân Do you mean `"null"`? – Adrian at DMI Apr 01 '20 at 09:53
  • Yes. I just try it on my localhost. – Tân Apr 01 '20 at 09:58
  • You are right, in `api/user?middleName=null&firstName=john` the value of `middleName` is `"null"`. So, inside my controller, should I treat the C# `null` as "undefined" and a specific string like `"null"` or the URLEncoded NUL value (%00) (as suggested [here](https://stackoverflow.com/questions/31254490/how-to-send-null-in-http-query-string)) as the C# `null`. Is this considered common or good practice? – Adrian at DMI Apr 01 '20 at 10:03
  • There is no `undefined` in C#. The value of `middleName` is `null` when it is not specified; so don’t specify it if you don’t want it to have a value. – poke Apr 01 '20 at 10:05
  • @AdrianatDMI: *This query (where middleName is explicitly set to null) should return users with the first name "John" and NO middle name.* No, it should return users with the middle name "null". "Null" is not the same thing as `null`. Treat the string value "null" as an actual name. You could use the `string.IsNullOrEmpty` method to determine whether a name was specified in the query. – mm8 Apr 01 '20 at 11:28
  • I can't see that there's any ideal answer to this. I expect you are going to have to come up with some convention to indicate either situation, or use some other additional parameters to decide what to do. Like `&firstName=john&middleNameDefined=true` or something. – Craig H Apr 01 '20 at 11:44
  • 2
    What if my kid's middle name is "null"? – ps2goat Apr 01 '20 at 13:19
  • you will need a magic string of sorts.... why not pass "undefined" when it is undefined then in your logic make the comparisson? – Jonathan Alfaro Apr 01 '20 at 15:05

1 Answers1

1

Generally speaking, there's no inherent way to distinguish between null and undefined, when coming from a request, because they are essentially the same thing. All values are initially strings, and then coerced to their actual types by the modelbinder.

As others have already noted, here, middleName=null means you're setting middleName to "null", not null, because it's a string going into a string. If you want to allow this convention, you'll need to do something like:

if (middleName == "null")
    middleName = null;

However, the true way to specify null in a query string is to provide the key without the value, e.g. param1=&param2=foo. Here, param1 has no no value. Still, though, this will be interpreted by the model binder as "", not null. You can of course do the same as the above, checking for this condition and explicitly setting it to null, or just use string.IsNullOrEmpty as a test of the value instead.

This still doesn't cover how to determine whether it's explicitly set or not. All action params are optional by default. The best you can do here is to explicitly check whether the value exists in the query:

if (Request.Query.ContainsKey("middleName"))
    // explicitly passed

Then, you can make decisions on how to handle the value based on whether it was actually present in the query or not.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Thanks for your answer and to everyone else for the comments. I have decided for now to use a combination of `Request.Query.ContainsKey()` and a specific string like the url encoded NULL string (`%00`) in order to be able to filter for null values and decide whether to include a filter or not. So you should be able to give your kid "null" as a middle name (but not `%00`) and search for it through the api ;) – Adrian at DMI Apr 02 '20 at 13:38
  • I've done this before by supporting (optional) prefixes on the values. /api/user?firstName=sue&middleName=isnull:true&lastName=eq:doe It's pretty easy to write a method to strip out the optional prefix into a structure that has the desired operator and value. I did something similar in a more advanced solution to this type of API in our Csg.ListQuery library, that provides a standardized request/response for this type of API call (lists of things). https://github.com/csgsolutions/Csg.ListQuery/blob/master/src/Csg.ListQuery.AspNetCore/ModelBinding/ListRequestFactory.cs#L203 – jbuch Jan 07 '21 at 23:39
  • In the `param1=&param2=foo`example, param1 will be interpreted by the model binder as `null`. This is from what I tried myself. – Eric Mar 17 '21 at 22:59