Alex
Alex

Reputation: 38499

Store arbitrary json in CosmosDB posted into api endpoint

I'm trying to save semi arbitrary json to CosmosDb

I say semi because the start of the json will have some known properties. For example:

{
    "id": "88e37e40-fd7e-478e-9ac5-140601fcddad",
    "type": "http",
    "name": "request1",
    "body": {
        "property1" : 1,
        "otherProperty": "foo"
    }
}

As you can see, body is the arbitrary part.

id and type and name are for demo purposes, but we could need to validate these properties.

Here's what I'm trying

app.MapPost
(
    "/", async (HttpRequest httpRequest, Container db) =>
    {
        var postedRequest = await httpRequest
            .ReadFromJsonAsync<Request>();

        var id = postedRequest.id;
        var type = postedRequest.type;

        var savedItem = await db.CreateItemAsync(postedRequest, new PartitionKey(type));

        return Results.Created($"/requests/{type}/{id}", savedItem.Resource);
    }
);

public record Request(string id, string type, string name, dynamic body);

My problem is when I POST the example json at the top of this question, it's being persisted like so:

{
    "id": "88e37e40-fd7e-478e-9ac5-140601fcddad",
    "type": "http",
    "name": "request1"
    "body": {
        "ValueKind": 1
    },

    "_rid": "WxgaAN+DhzoBAAAAAAAAAA==",
    "_self": "dbs/WxgaAA==/colls/WxgaAN+Dhzo=/docs/WxgaAN+DhzoBAAAAAAAAAA==/",
    "_etag": "\"00000000-0000-0000-96ae-5ccf928801d8\"",
    "_attachments": "attachments/",
    "_ts": 1657712863
}

It's using ValueKind in place of the arbitrary json. I believe this is because of how the framework is deserializing it, perhaps due to my usage of dynamic

Is there another type is should use? Or perhaps deserialize HttpRequest body to a stream first, then probe the properties I am interested in validating them? Using Utf8JsonReader? I'm not sure where to go from here.


Here's my full code

using Microsoft.Azure.Cosmos;
using CosmosClient client = new("<connectionstring>");
Database database = await client.CreateDatabaseIfNotExistsAsync(
    id: "testing"
);
Container container = await database.CreateContainerIfNotExistsAsync(
    id: "requests",
    partitionKeyPath: "/type",
    throughput: 400
);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<Container>(container);

var app = builder.Build();

app.MapGet("/", () => "Started");

app.MapPost
(
    "/", async (HttpRequest httpRequest, Container db) =>
    {
        var postedRequest = await httpRequest
            .ReadFromJsonAsync<Request>();

        var id = postedRequest.id;
        var type = postedRequest.type;

        var savedItem = await db.CreateItemAsync(postedRequest, new PartitionKey(type));

        return Results.Created($"/requests/{type}/{id}", savedItem.Resource);
    }
);

app.MapGet
(
    "/requests/{type}/{id}", (Container db, string type, string id) =>
    {
        var dbResult = db.ReadItemAsync<Request>(id, new PartitionKey(type));

        return Results.Ok(dbResult);
    }
);

app.Run();

public record Request(string id, string type, string name, dynamic body);

Upvotes: 2

Views: 573

Answers (1)

Matias Quaranta
Matias Quaranta

Reputation: 15583

If you can know the Partition Key (it comes in the url for example), then it would be simpler to use the Stream APIs. Example: https://github.com/ealsur/ondotnet-cosmosdb/tree/master/src/episode1/streams#streams

app.MapPost
(
    "/{type}/{id}", async (string type, string id, HttpRequest httpRequest, Container db) =>
    {
        var savedItem = await db.CreateItemStreamAsync(httpRequest.Body, new PartitionKey(type));

        return Results.Stream(savedItem.Content, "application/json");
    }
);

Upvotes: 1

Related Questions