admin管理员组

文章数量:1122846

Background problem:

I have a document POCO that I would like to use with the CosmosDbOutput binding extension (latest, v4).

I prefer to have my POCO property names in PascaLCase, and my Cosmos / Json documents in camelCase respectively. However, I cannot find the correct way to modify the serialization options to get this to work.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;

namespace FunctionCrudSample;

// Function
    [Function(nameof(CreatePascalCase))]
    public async Task<CreateResponsePascalCase> CreatePascalCase(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
        [FromBody] MyDocumentPascalCase myDocument,
        FunctionContext executionContext
        )
    {
        var logger = executionContext.GetLogger(nameof(Api));
        logger.LogInformation("C# HTTP trigger function processed a request.");
        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(myDocument);
        return new() { Response = response, Document = myDocument };
    }

// Multi-Return type
    public class CreateResponsePascalCase
    {
        [HttpResult]
        public required HttpResponseData Response { get; set; }

        [CosmosDBOutput(cosmosDbName, cosmosContainerName, Connection = connectionString, CreateIfNotExists = true, PartitionKey = "/id")]
        public MyDocumentPascalCase? Document { get; set; }
    }

// POCO
    public class MyDocumentPascalCase
    {
        public string? Id { get; set; }
        public string? Message { get; set; }
    }

What I've tried

1. Modify the POCO to use camelCase property names

This works, but again. I would prefer to use PascalCase. Not to mention using lower case property names violates code style IDE1006.

public class MyDocumentCamelCase
{
    public string? id { get; set; }
    public string? message { get; set; }
}

2. Configuring IOptions<WorkerOptions> and ConfigureCosmosDBExtensionOptions()

I found no success with either, or both of these configuration options in the Program.cs

Not to mention they are undocumented. I spent a few hours in the dotnet worker runtime repository.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var host = new HostBuilder().ConfigureFunctionsWorkerDefaults(options =>
{
    options.Services.AddOptions<CosmosDBExtensionOptions>().Configure<IOptions<WorkerOptions>>((cosmos, worker) =>
    {
        cosmos.ClientOptions.SerializerOptions.PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase;
    });

    options.ConfigureCosmosDBExtensionOptions(options =>
    {
        options.ClientOptions.SerializerOptions.PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase;
    });

}).Build();

await host.RunAsync();

Error details

[2025-01-06T00:12:14.733Z] Executing 'Functions.CreatePascalCase' (Reason='This function was programmatically called via the host APIs.', Id=e3502c80-8f5d-45f8-8060-3f79526d959a)
[2025-01-06T00:12:14.835Z] C# HTTP trigger function processed a request.
[2025-01-06T00:12:15.542Z] Host lock lease acquired by instance ID '000000000000000000000000451B1ABA'.
[2025-01-06T00:12:22.899Z] Executed 'Functions.CreatePascalCase' (Failed, Id=e3502c80-8f5d-45f8-8060-3f79526d959a, Duration=8170ms)
[2025-01-06T00:12:22.900Z] System.Private.CoreLib: Exception while executing function: Functions.CreatePascalCase. Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4; Reason: ({"code":"BadRequest","message":"Message: {\"Errors\":[\"One of the specified inputs is invalid\"]}\r\nActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4, Request URI: /apps/DocDbApp/services/DocDbServer1/partitions/a4cb494d-38c8-11e6-8106-8cdcd42c33be/replicas/1p/, RequestStats: \r\nRequestStartTime: 2025-01-06T00:12:22.8309413Z, RequestEndTime: 2025-01-06T00:12:22.8326386Z,  Number of regions attempted:1\r\n{\"systemHistory\":[{\"dateUtc\":\"2025-01-06T00:11:28.7225106Z\",\"cpu\":0.400,\"memory\":7185016.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1436,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:38.7228784Z\",\"cpu\":0.303,\"memory\":7172964.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1101,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:48.7368240Z\",\"cpu\":0.361,\"memory\":7234704.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0365,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:58.7376400Z\",\"cpu\":0.557,\"memory\":7209968.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1008,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:12:08.7520453Z\",\"cpu\":4.037,\"memory\":6609924.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0644,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:12:18.7587838Z\",\"cpu\":3.106,\"memory\":6173820.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0562,\"availableThreads\":32765,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1}]}\r\nRequestStart: 2025-01-06T00:12:22.8311613Z; ResponseTime: 2025-01-06T00:12:22.8326386Z; StoreResult: StorePhysicalAddress: rntbd://127.0.0.1:10253/apps/DocDbApp/services/DocDbServer1/partitions/a4cb494d-38c8-11e6-8106-8cdcd42c33be/replicas/1p/, LSN: 30, GlobalCommittedLsn: -1, PartitionKeyRangeId: 0, IsValid: True, StatusCode: 400, SubStatusCode: 0, RequestCharge: 0, ItemLSN: -1, SessionToken: -1#30, UsingLocalLSN: False, TransportException: null, BELatencyMs: 0.297, ActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4, RetryAfterInMs: , ReplicaHealthStatuses: [(port: 10253 | status: Connected | lkt: 1/4/2025 6:59:48 PM)], TransportRequestTimeline: {\"requestTimeline\":[{\"event\": \"Created\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311628Z\", \"durationInMs\": 0.0208},{\"event\": \"ChannelAcquisitionStarted\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311836Z\", \"durationInMs\": 0.0031},{\"event\": \"Pipelined\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311867Z\", \"durationInMs\": 0.1507},{\"event\": \"Transit Time\", \"startTimeUtc\": \"2025-01-06T00:12:22.8313374Z\", \"durationInMs\": 0.7515},{\"event\": \"Received\", \"startTimeUtc\": \"2025-01-06T00:12:22.8320889Z\", \"durationInMs\": 0.0735},{\"event\": \"Completed\", \"startTimeUtc\": \"2025-01-06T00:12:22.8321624Z\", \"durationInMs\": 0}],\"serviceEndpointStats\":{\"inflightRequests\":1,\"openConnections\":1},\"connectionStats\":{\"waitforConnectionInit\":\"False\",\"callsPendingReceive\":0,\"lastSendAttempt\":\"2025-01-06T00:12:22.7832281Z\",\"lastSend\":\"2025-01-06T00:12:22.7832850Z\",\"lastReceive\":\"2025-01-06T00:12:22.7847877Z\"},\"requestSizeInBytes\":545,\"requestBodySizeInBytes\":40,\"responseMetadataSizeInBytes\":183,\"responseBodySizeInBytes\":53};\r\n ResourceType: Document, OperationType: Upsert\r\n, SDK: Microsoft.Azure.Documents.Common/2.14.0"}

Reproducible sample

Related, but not relevant to v4 isolated functions

  • Azure Functions v4 serialize to Cosmos DB in camel case
  • Access Json Serializer options to avoid serializing null in Azure Functions v3

Background problem:

I have a document POCO that I would like to use with the CosmosDbOutput binding extension (latest, v4).

I prefer to have my POCO property names in PascaLCase, and my Cosmos / Json documents in camelCase respectively. However, I cannot find the correct way to modify the serialization options to get this to work.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using FromBodyAttribute = Microsoft.Azure.Functions.Worker.Http.FromBodyAttribute;

namespace FunctionCrudSample;

// Function
    [Function(nameof(CreatePascalCase))]
    public async Task<CreateResponsePascalCase> CreatePascalCase(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
        [FromBody] MyDocumentPascalCase myDocument,
        FunctionContext executionContext
        )
    {
        var logger = executionContext.GetLogger(nameof(Api));
        logger.LogInformation("C# HTTP trigger function processed a request.");
        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteAsJsonAsync(myDocument);
        return new() { Response = response, Document = myDocument };
    }

// Multi-Return type
    public class CreateResponsePascalCase
    {
        [HttpResult]
        public required HttpResponseData Response { get; set; }

        [CosmosDBOutput(cosmosDbName, cosmosContainerName, Connection = connectionString, CreateIfNotExists = true, PartitionKey = "/id")]
        public MyDocumentPascalCase? Document { get; set; }
    }

// POCO
    public class MyDocumentPascalCase
    {
        public string? Id { get; set; }
        public string? Message { get; set; }
    }

What I've tried

1. Modify the POCO to use camelCase property names

This works, but again. I would prefer to use PascalCase. Not to mention using lower case property names violates code style IDE1006.

public class MyDocumentCamelCase
{
    public string? id { get; set; }
    public string? message { get; set; }
}

2. Configuring IOptions<WorkerOptions> and ConfigureCosmosDBExtensionOptions()

I found no success with either, or both of these configuration options in the Program.cs

Not to mention they are undocumented. I spent a few hours in the dotnet worker runtime repository.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

var host = new HostBuilder().ConfigureFunctionsWorkerDefaults(options =>
{
    options.Services.AddOptions<CosmosDBExtensionOptions>().Configure<IOptions<WorkerOptions>>((cosmos, worker) =>
    {
        cosmos.ClientOptions.SerializerOptions.PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase;
    });

    options.ConfigureCosmosDBExtensionOptions(options =>
    {
        options.ClientOptions.SerializerOptions.PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase;
    });

}).Build();

await host.RunAsync();

Error details

[2025-01-06T00:12:14.733Z] Executing 'Functions.CreatePascalCase' (Reason='This function was programmatically called via the host APIs.', Id=e3502c80-8f5d-45f8-8060-3f79526d959a)
[2025-01-06T00:12:14.835Z] C# HTTP trigger function processed a request.
[2025-01-06T00:12:15.542Z] Host lock lease acquired by instance ID '000000000000000000000000451B1ABA'.
[2025-01-06T00:12:22.899Z] Executed 'Functions.CreatePascalCase' (Failed, Id=e3502c80-8f5d-45f8-8060-3f79526d959a, Duration=8170ms)
[2025-01-06T00:12:22.900Z] System.Private.CoreLib: Exception while executing function: Functions.CreatePascalCase. Microsoft.Azure.Cosmos.Client: Response status code does not indicate success: BadRequest (400); Substatus: 0; ActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4; Reason: ({"code":"BadRequest","message":"Message: {\"Errors\":[\"One of the specified inputs is invalid\"]}\r\nActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4, Request URI: /apps/DocDbApp/services/DocDbServer1/partitions/a4cb494d-38c8-11e6-8106-8cdcd42c33be/replicas/1p/, RequestStats: \r\nRequestStartTime: 2025-01-06T00:12:22.8309413Z, RequestEndTime: 2025-01-06T00:12:22.8326386Z,  Number of regions attempted:1\r\n{\"systemHistory\":[{\"dateUtc\":\"2025-01-06T00:11:28.7225106Z\",\"cpu\":0.400,\"memory\":7185016.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1436,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:38.7228784Z\",\"cpu\":0.303,\"memory\":7172964.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1101,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:48.7368240Z\",\"cpu\":0.361,\"memory\":7234704.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0365,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:11:58.7376400Z\",\"cpu\":0.557,\"memory\":7209968.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.1008,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:12:08.7520453Z\",\"cpu\":4.037,\"memory\":6609924.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0644,\"availableThreads\":32766,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1},{\"dateUtc\":\"2025-01-06T00:12:18.7587838Z\",\"cpu\":3.106,\"memory\":6173820.000,\"threadInfo\":{\"isThreadStarving\":\"False\",\"threadWaitIntervalInMs\":0.0562,\"availableThreads\":32765,\"minThreads\":16,\"maxThreads\":32767},\"numberOfOpenTcpConnection\":1}]}\r\nRequestStart: 2025-01-06T00:12:22.8311613Z; ResponseTime: 2025-01-06T00:12:22.8326386Z; StoreResult: StorePhysicalAddress: rntbd://127.0.0.1:10253/apps/DocDbApp/services/DocDbServer1/partitions/a4cb494d-38c8-11e6-8106-8cdcd42c33be/replicas/1p/, LSN: 30, GlobalCommittedLsn: -1, PartitionKeyRangeId: 0, IsValid: True, StatusCode: 400, SubStatusCode: 0, RequestCharge: 0, ItemLSN: -1, SessionToken: -1#30, UsingLocalLSN: False, TransportException: null, BELatencyMs: 0.297, ActivityId: 8ba46106-33ab-444c-9706-234f1c626bd4, RetryAfterInMs: , ReplicaHealthStatuses: [(port: 10253 | status: Connected | lkt: 1/4/2025 6:59:48 PM)], TransportRequestTimeline: {\"requestTimeline\":[{\"event\": \"Created\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311628Z\", \"durationInMs\": 0.0208},{\"event\": \"ChannelAcquisitionStarted\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311836Z\", \"durationInMs\": 0.0031},{\"event\": \"Pipelined\", \"startTimeUtc\": \"2025-01-06T00:12:22.8311867Z\", \"durationInMs\": 0.1507},{\"event\": \"Transit Time\", \"startTimeUtc\": \"2025-01-06T00:12:22.8313374Z\", \"durationInMs\": 0.7515},{\"event\": \"Received\", \"startTimeUtc\": \"2025-01-06T00:12:22.8320889Z\", \"durationInMs\": 0.0735},{\"event\": \"Completed\", \"startTimeUtc\": \"2025-01-06T00:12:22.8321624Z\", \"durationInMs\": 0}],\"serviceEndpointStats\":{\"inflightRequests\":1,\"openConnections\":1},\"connectionStats\":{\"waitforConnectionInit\":\"False\",\"callsPendingReceive\":0,\"lastSendAttempt\":\"2025-01-06T00:12:22.7832281Z\",\"lastSend\":\"2025-01-06T00:12:22.7832850Z\",\"lastReceive\":\"2025-01-06T00:12:22.7847877Z\"},\"requestSizeInBytes\":545,\"requestBodySizeInBytes\":40,\"responseMetadataSizeInBytes\":183,\"responseBodySizeInBytes\":53};\r\n ResourceType: Document, OperationType: Upsert\r\n, SDK: Microsoft.Azure.Documents.Common/2.14.0"}

Reproducible sample

https://github.com/EntityAdam/FunctionCrudSample/tree/features/serialization-problem-repro

Related, but not relevant to v4 isolated functions

  • Azure Functions v4 serialize to Cosmos DB in camel case
  • Access Json Serializer options to avoid serializing null in Azure Functions v3
Share Improve this question asked yesterday Adam VincentAdam Vincent 3,78216 silver badges41 bronze badges 6
  • Why are you not using direct serilization? – RithwikBojja Commented yesterday
  • What is direct serialization? Why should I use that, instead of what I'm doing now? – Adam Vincent Commented yesterday
  • Use this var rith = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(cho => { cho.Configure<WorkerOptions>(ritcho => { ritcho.Serializer = new JsonObjectSerializer( new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }); }); }) .Build(); – RithwikBojja Commented yesterday
  • Did it work @AdamVincent? – RithwikBojja Commented yesterday
  • sure did, thanks friend. – Adam Vincent Commented yesterday
 |  Show 1 more comment

1 Answer 1

Reset to default 2

I have a document POCO that I would like to use with the CosmosDbOutput binding extension (latest, v4).I prefer to have my POCO property names in PascaLCase, and my Cosmos / Json documents in camelCase respectively. However, I cannot find the correct way to modify the serialization options to get this to work.

Below code worked for me:

Program.cs:

using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Functions.Worker;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Azure.Core.Serialization;

var rith = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(cho =>
    {
        cho.Configure<WorkerOptions>(bo =>
        {
            bo.Serializer = new JsonObjectSerializer(
                new JsonSerializerOptions
                {
                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                    DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
                });
        });
    })
    .Build();

rith.Run();

Function.cs:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;

namespace FunctionApp20
{
    public class Function1
    {
        private readonly ILogger<Function1> ri_lg;

        public Function1(ILogger<Function1> lg)
        {
            ri_lg = lg;
        }

        [Function("Function1")]
        public async Task<RithOut> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
            FunctionContext executionContext
        )
        {
            ri_lg.LogInformation("Hello Bojja");

            var myDocument = new RithPas
            {
                Id = "008",  
                Message = "Rithwik Bojja"
            };

            var response = req.CreateResponse(HttpStatusCode.OK);
            return new RithOut { Response = response, Document = myDocument };
        }
    }

    public class RithOut
    {
        [HttpResult]
        public required HttpResponseData Response { get; set; }

        [CosmosDBOutput("Testdb", "testcon", Connection = "test", CreateIfNotExists = true, PartitionKey = "/id")]
        public RithPas? Document { get; set; }
    }

    public class RithPas
    {
        public string? Id { get; set; }
        public string? Message { get; set; }
    }
}

Output:

本文标签: cHow do you modify the serialization options for Azure Functions v4 isolatedStack Overflow