Azure / Azure/azure-functions-dotnet-worker

DateOnly and TimeOnly parameter binding throws exception

Open
#2,714 2 comments 1 reaction 0 assignees View on GitHub
potential-bug
Dominant language
C#
Stars
466
Forks
215
Avg merge
3d 10h
Merged PRs (30d)
7

Description

### Description

When using a DateOnly or TimeOnly parameter binding in a function (for example when extracting from the route of a HTTP Trigger or as a parameter in a SignalR binding), the function invocator throws the following exception respectively.

DateOnly
```
Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'DateOnlyTest': Cannot convert input parameter 'date' to type 'System.DateOnly' from type 'System.String'. Error:System.Text.Json.JsonException: '-' is an invalid end of a number. Expected a delimiter. Path: $ | LineNumber: 0 | BytePositionInLine: 4.
---> System.Text.Json.JsonReaderException: '-' is an invalid end of a number. Expected a delimiter. LineNumber: 0 | BytePositionInLine: 4.
at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
at System.Text.Json.Utf8JsonReader.TryGetNumber(ReadOnlySpan`1 data, Int32& consumed)
at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
at System.Text.Json.Utf8JsonReader.Read()
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
--- End of inner exception stack trace ---
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 97
at SignalRSandbox.Functions.DirectFunctionExecutor.ExecuteAsync(FunctionContext context)
```

TimeOnly
```
Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'TimeOnlyTest': Cannot convert input parameter 'time' to type 'System.TimeOnly' from type 'System.String'. Error:System.Text.Json.JsonException: Invalid leading zero before '9'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
---> System.Text.Json.JsonReaderException: Invalid leading zero before '9'. LineNumber: 0 | BytePositionInLine: 1.
at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
at System.Text.Json.Utf8JsonReader.ConsumeZero(ReadOnlySpan`1& data, Int32& i)
at System.Text.Json.Utf8JsonReader.TryGetNumber(ReadOnlySpan`1 data, Int32& consumed)
at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
at System.Text.Json.Utf8JsonReader.Read()
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
--- End of inner exception stack trace ---
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
at Microsoft.Azure.Functions.Worker.Converters.JsonPocoConverter.GetConversionResultFromDeserialization(Byte[] bytes, Type type) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\JsonPocoConverter.cs:line 66
at Microsoft.Azure.Functions.Worker.Context.Features.DefaultFunctionInputBindingFeature.BindFunctionInputAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultFunctionInputBindingFeature.cs:line 97
at SignalRSandbox.Functions.DirectFunctionExecutor.ExecuteAsync(FunctionContext context)
```

Things of note:
I'm using the Asp.NET Core integration for this test.
The parameter binding however works for a DateTime parameter, as can be seen in the sample code below.

Hereby also the list of used libraries (all are latest version at the time of writing)
```xml

```

### Steps to reproduce

Test functions being used.
```csharp
[Function("DateTimeTest")]
public HttpResponseData DateTimeTest(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "datetime/{dateTime}")] HttpRequestData req,
DateTime dateTime)
{
_logger.LogInformation("Received datetime: {DateTime}", dateTime);

return req.CreateResponse(HttpStatusCode.NoContent);
}

[Function("DateOnlyTest")]
public HttpResponseData DateOnlyTest(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "dateonly/{date}")] HttpRequestData req,
DateOnly date)
{
_logger.LogInformation("Received date: {Date}", date);

return req.CreateResponse(HttpStatusCode.NoContent);
}

[Function("TimeOnlyTest")]
public HttpResponseData TimeOnlyTest(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "timeonly/{time}")] HttpRequestData req,
TimeOnly time)
{
_logger.LogInformation("Received time: {Time}", time);

return req.CreateResponse(HttpStatusCode.NoContent);
}
```

And the HTTP file being used to invoke the functions above.
```http
### GET request for working datetime test

GET http://localhost:7071/api/datetime/2024-09-16T09:15:40

### GET request for broken dateonly test

GET http://localhost:7071/api/dateonly/2024-09-16

### GET request for broken timeonly test

GET http://localhost:7071/api/timeonly/09:15:40
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at src/DotNetWorker.Core/Converters/JsonPocoConverter.cs around line 66, then follow binding through DefaultFunctionInputBindingFeature.cs around line 97. Reproduce the DateOnly and TimeOnly routes shown in the issue and compare them with DateTime binding. Done means the dateonly and timeonly requests bind successfully to their parameters without FunctionInputConverterException.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.