独立模式Durable Azure Function传递DateOnly触发JsonException问题
开发独立模式的Durable Azure Function时,通过TaskOrchestrationContext.CallActivityAsync传递DateOnly作为带[ActivityTrigger]的活动函数输入参数会抛出JsonException,但将DateOnly包装在类中传递则可正常运行。
错误信息
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'WriteDateOnly': Cannot convert input parameter 'today' 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.
问题
在Durable Azure Function中,是否必须将DateOnly参数包装在类中?
可复现代码示例
主函数类
using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.DurableTask; using Microsoft.DurableTask.Client; namespace Sandbox.Functions { public class DateOnlyOrchestrator { [Function("DateOnlyOrchestratorApi")] public async Task<HttpResponseData> RunApi( [HttpTrigger] HttpRequestData request, [DurableClient] DurableTaskClient durableTaskClient) { var today = new DateOnly(2023, 4, 11); var orchestrationInstanceId = await durableTaskClient .ScheduleNewOrchestrationInstanceAsync("DateOnlyOrchestrator", today) .ConfigureAwait(false); return durableTaskClient.CreateCheckStatusResponse(request, orchestrationInstanceId); } [Function("DateOnlyOrchestrator")] public async Task Run( [OrchestrationTrigger] TaskOrchestrationContext taskOrchestrationContext, DateOnly today) { var wrappedDateOnly = new WrappedDateOnly { Today = today, }; // 此活动调用成功 await taskOrchestrationContext .CallActivityAsync("WriteWrappedDateOnly", wrappedDateOnly) .ConfigureAwait(true); // 调用此活动时抛出JsonException await taskOrchestrationContext .CallActivityAsync("WriteDateOnly", today) .ConfigureAwait(true); } [Function("WriteWrappedDateOnly")] public Task WriteWrappedDateOnly( [ActivityTrigger] WrappedDateOnly today) { Console.WriteLine($"Today is: {today.Today}"); return Task.CompletedTask; } [Function("WriteDateOnly")] public Task WriteDateOnly( [ActivityTrigger] DateOnly today) { Console.WriteLine($"Today is: {today}"); return Task.CompletedTask; } } public class WrappedDateOnly { public DateOnly Today { get; set; } } }
Program.cs
using Microsoft.Extensions.Hosting; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .Build(); host.Run();
解答
原因
这是Durable Task框架默认的System.Text.Json序列化/反序列化逻辑导致的问题:
- 当传递单独的
DateOnly值时,它会被序列化为纯字符串(比如"2023-04-11"); - 活动函数的输入转换器尝试直接将该字符串反序列化为
DateOnly类型时,会错误地将字符串开头的数字部分当作数值解析,遇到-符号时触发格式错误; - 当
DateOnly被包装在类中时,JSON结构是标准对象({"Today":"2023-04-11"}),转换器能正确识别字符串对应的属性类型,完成反序列化。
解决方案:并非必须包装类
你可以通过以下两种方式替代包装类的方案:
1. 配置自定义Json转换器
在Program.cs中为Functions Worker添加DateOnly的自定义Json转换器,让框架能正确处理单独的DateOnly值:
using Microsoft.Extensions.Hosting; using System.Text.Json; using System.Text.Json.Serialization; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults(builder => { builder.Services.Configure<Microsoft.Azure.Functions.Worker.Json.JsonSerializerOptions>(options => { options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new DateOnlyJsonConverter()); }); }) .Build(); host.Run(); // 自定义DateOnly转换器 public class DateOnlyJsonConverter : JsonConverter<DateOnly> { public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return DateOnly.Parse(reader.GetString()!); } public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString("O")); } }
2. 手动传递字符串并解析
在编排器中传递DateOnly的字符串表示,在活动函数内手动解析:
- 编排器端修改调用代码:
// 传递格式化后的字符串 await taskOrchestrationContext.CallActivityAsync("WriteDateOnly", today.ToString("O"));
- 活动函数修改参数类型并解析:
[Function("WriteDateOnly")] public Task WriteDateOnly([ActivityTrigger] string todayStr) { DateOnly today = DateOnly.Parse(todayStr); Console.WriteLine($"Today is: {today}"); return Task.CompletedTask; }
总结
将DateOnly包装在类中是可行方案,但并非必须。通过配置自定义Json转换器或手动处理字符串解析,同样可以解决单独传递DateOnly参数时的序列化问题。
内容的提问来源于stack exchange,提问作者DeMaki

