Azure / azure-webjobs-sdk-extensions

Azure WebJobs SDK Extensions
MIT License
339 stars 204 forks source link

CosmosDBAsyncCollector uses Newtonsoft instead of System.Text.Json (.NET Core 3.1 and Azure Function runtime version ~3) #629

Open scholtes opened 4 years ago

scholtes commented 4 years ago

Adoption of System.Text.Json is encouraged for applications targeting .NET Core 3+. The CosmosDBAsyncCollector output binding uses Newtonsoft as its JSON Serializer.

For Azure Functions on runtime version ~3 targeting .NET Core 3.1 it would be preferred if all of the bindings directly supported System.Text.Json (preferably by default).

Repro steps

  1. Create an empty Cosmos DB database example-db collection example-collection.

  2. Create an Azure Function App project targeting runtime ~3 and .NET Core 3.1.

  3. Set example-db-connection-string in the local.settings.json.

  4. Target Azure Functions runtime ~3 and .NET Core 3.1, then add a FunctionExample.cs to your project with the following code:

  ...
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  ...
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;

namespace FunctionAppExample
{
    public class MyModel
    {
        [JsonPropertyName("id")]
        public string DocumentId { get; set; }
    }

    public static class FunctionExample
    {
        [FunctionName("FunctionExample")]
        public static async Task Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer,
            [CosmosDB("example-db",
                "example-collection",
                ConnectionStringSetting = "example-db-connection-string")]
            IAsyncCollector<MyModel> output,
            ILogger log)
        {
            var thing = new MyModel
            {
                DocumentId = "abc"
            };

            await output.AddAsync(thing);
        }
    }
}
  1. Run the function host and allow FunctionExample to execute once.

  2. Inspect the document created by the function in example-collection .

Expected behavior

Document is serialized by System.Text.Json: image

Actual behavior

Document is serialized by Newtonsoft: image

Known workarounds

Related information

Relevant code:

Possibly other Azure Function bindings and triggers follow the same pattern (not investigated).

scholtes commented 4 years ago

Using the CosmosDB client from the CosmosDB v3 SDK instead of the older DocumentClient would also solve this issue. This is requested in #610.

v-anvari commented 3 years ago

@brettsam , Any known workarounds here?

v-anvari commented 3 years ago

We cannot support this right now due to impact on breaking changes.

@brettsam , Tagging Brett for more context

ealsur commented 3 years ago

Bindings use the V2 Cosmos DB SDK which only works with Newtonsoft.Json, supporting System.Text.Json is not possible unless a new version of the binding is created using the V3 Cosmos DB SDK, which is a breaking change.

It's a good feature for a new version, but cannot be implemented in the current one.

Only workaround would be to issue the query with a V3 SDK instance with custom serialization using System.Text.Json (which is possible). Remember to either have a static/singleton client or use Functions DI (https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/AzureFunctions/Startup.cs).

bmwaechter commented 2 years ago

@ealsur I noticed this PR #717 that seems to upgrade to the V3 SDK, should be expect that if we are using the preview nuget package for the cosmos extension? I tried it recently and it didn't seem to respect system.text.json annotation, which I assume means we are still using Newtonsoft under the hood.

ealsur commented 2 years ago

V3 SDK uses Newtonsoft.Json as default serialization engine, so it's the same behavior for the 4.0.0-preview package. But you can plug in/replace with any serializer you want. We don't have official docs yet but you can leverage the new serialization factory. In your Startup.cs file:

[assembly: FunctionsStartup(typeof(FunctionApp1.Startup))]
namespace FunctionApp1
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<ICosmosDBSerializerFactory, MyCosmosDBSerializerFactory>();
        }
    }

    public class MyCosmosDBSerializerFactory : ICosmosDBSerializerFactory
    {
        public CosmosSerializer CreateSerializer()
        {
            var options = new JsonSerializerOptions();

            return new CosmosSystemTextJsonSerializer(options);
        }
    }
}

You can have any custom implementation of CosmosSerializer, for example, one for System.Text.Json: https://github.com/Azure/azure-cosmos-dotnet-v3/tree/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson