Search code examples
c#.netwebapinodatimeabp-framework

Calling Web api POST (abp appservice) with a NodaTime LocalDate fails to serialize


I am trying to write a web api using abp.io and implementing NodaTime. Whenever I try to call the POST action, I get

The following errors were detected during validation.\r\n - The JSON value could not be converted to NodaTime.LocalDate.

I have configured NodaTime correctly in the module, I also tested it in a conventional .net 5 web api and is working correctly.

Here is my config:

private void ConfigureNodaSerialization()
    {
        Configure<JsonSerializer>(options =>
        {
            options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
            //options.Converters.Add(NodaConverters.LocalDateConverter);
        });
    }

I am using NodaTime.Serialization.JsonNet

I have also tried NodaTime.Serialization.SystemTextJson to no avail, same results.

..and here is the appservice:

[AllowAnonymous]
public class NodaTestAppService : TestAppService, INodaTestAppService
{
    public Task PostNodaTest([FromBody]NodaTestDto dto)
    {
        return Task.CompletedTask;
    }

    public Task GetNodaTest(NodaTestDto dto)
    {
        return Task.CompletedTask;
    }
}

The GET is working fine, problem is with POST.

This is the dto:

public class NodaTestDto
{
    public LocalDate Date { get; set; }
    public string NodaString { get; set; }
}

I suspect the problem is with Abp serialization.

I have my test solution on github here.

This is the curl for my attempted request:

curl -X 'POST'
'https://localhost:44333/api/app/noda-test/noda-test'
-H 'accept: /'
-H 'Content-Type: application/json'
-H 'RequestVerificationToken: CfDJ8N-JsQhR2mhIqO-RkZtn61KwWgiFfjD60I0EMa07QTooLF9dC8LPmQEDtTws8MpTUtl8b0gtuE-NAiBAXxHEu8IyU8-4w0MfVB4IeZTRsWwXIgzc7pQYReLYnV1IVp0icR5Aj-fMvBqRyvPaNTgVJBc'
-d '{ "date": "1991-08-28", "nodaString": "string" }'


Solution

  • The source code of ABP shows that Newtonsoft.Json is being configured upon MvcNewtonsoftJsonOptions (instead of JsonSerializer):

    services.Configure<MvcNewtonsoftJsonOptions>(jsonOptions => { 
        // configuration settings go here.
    }
    

    You might want to do in a similar way using NodaTime.Serialization.JsonNet.

    private void ConfigureNodaSerialization(ServiceConfigurationContext context)
    {
        context.Services.Configure<MvcNewtonsoftJsonOptions>(options =>
        {
            options.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
        });
    }
    

    I use the below package reference.

    <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
    

    UPDATE

    The documentation states that ABP is using a hybrid approach using System.Text.Json and falling back to Newtonsoft.Json for unsupported types; it shows how you can keep using Newtonsoft.Json for specific classes or all over the place.

    I took the approach to only use Newtonsoft.Json for the NodaTestDto class, together with the NodaTime configuration shown above.

    Your ConfigureNodaSerialization method within your TestHttpApiHostModule should look like below.

    private void ConfigureNodaSerialization()
    {
        // Use Newtonsoft.Json for NodaTestDto.
        Configure<AbpSystemTextJsonSerializerOptions>(options =>
        {
            options.UnsupportedTypes.AddIfNotContains(typeof(NodaTestDto));
        });
        
        // Configure Nodatime (de)serialization.
        Configure<MvcNewtonsoftJsonOptions>(jsonOptions =>
        {
            jsonOptions.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
        });
    }
    

    Now, when posting a { "date": "1991-08-28", "nodaString": "string" } json payload using the Swagger UI, your NodaTestAppService receives a valid LocalDate instance:

    enter image description here