1

I am working on an ASP.Net Core 3.0 API with an Azure Cosmos DB as the persistence store. This is my first attempt at working with Cosmos DB. When I try to create a new item (document), I am getting back an error in Postman that says...

"Response status code does not indicate success: 400 Substatus: 0 
Reason: (Message: {\"Errors\":[\"The input name '{' is invalid. 
Ensure to provide a unique non-empty string less than '1024' characters."

I cannot figure out what is causing this problem.

I am using the Microsoft.Azure.Cosmos v3.4.0 nuget in my project

Here is the method in my repository for adding the new Account document.

public async Task AddAccountAsync(Account account)
{
    await _container.CreateItemAsync(account, new PartitionKey(account.Id));
}

Here is a pic of the property values when I hover over the "Account account" object while in debug mode.

enter image description here

My container in Cosmos DB is set up with /id as the partition key.

Here is my request body in Postman;

{
  "id": "00000000-0000-0000-0000-000000000000",
  "accountName": "Test Company 1",
  "accountType": 1,
  "ownerId": "00000000-0000-0000-0000-000000000000",
  "isTaxExempt": false,
  "mailJobProxyId": "00000000-0000-0000-0000-000000000000",
  "salesPersonId": "00000000-0000-0000-0000-000000000000"

}

Here is the Account class;

public class Account
{

    // Aggregate state properties
    [JsonProperty(PropertyName = "id")]
    public AccountId Id { get; set; }

    [JsonProperty(PropertyName = "accountName")]
    public AccountName AccountName { get; set; }

    [JsonProperty(PropertyName = "accountType")]
    public AccountTypes AccountType { get; set; }

    [JsonProperty(PropertyName = "ownerId")]
    public OwnerId OwnerId { get; set; }

    [JsonProperty(PropertyName = "isTaxExempt")]
    public bool IsTaxExempt { get; set; }

    [JsonProperty(PropertyName = "mailJobProxyId")]
    public MailJobProxyId MailJobProxyId { get; set; }

    [JsonProperty(PropertyName = "salesPersonId")]
    public SalesPersonId SalesPersonId { get; set; }

    [JsonProperty(PropertyName = "addresses")]
    public List<Address.Address> Addresses { get; set; }

    [JsonProperty(PropertyName = "contacts")]
    public List<Contact.Contact> Contacts { get; set; }

    [JsonProperty(PropertyName = "postagePaymentMethods")]
    public List<PostagePaymentMethod.PostagePaymentMethod> PostagePaymentMethods { get; set; }

    public Account(string id, string accountName, AccountTypes accountType, string ownerId, Guid mailJobProxyId, Guid salesPersonId, bool isTaxExempt)
    {
        Id = AccountId.FromString(id);
        AccountName = AccountName.FromString(accountName);
        AccountType = accountType;
        OwnerId = OwnerId.FromString(ownerId);
        MailJobProxyId = new MailJobProxyId(mailJobProxyId);
        SalesPersonId = new SalesPersonId(salesPersonId);
        IsTaxExempt = isTaxExempt;
        Addresses = new List<Address.Address>();
        Contacts = new List<Contact.Contact>();
        PostagePaymentMethods = new List<PostagePaymentMethod.PostagePaymentMethod>();
        Status = Status.Active;

    }
}

Please let me know if you need other code examples.

UPDATE 11/6/19 at 6:43p EST

Here is the AccountId value object

   public class AccountId : Value<AccountId>
    {
        public string Value { get; internal set; }

        // Parameterless constructor for serialization requirements
        protected AccountId() { }

        internal AccountId(string value) => Value = value;

        // Factory pattern
        public static AccountId FromString(string accountId)
        {
            CheckValidity(accountId);
            return new AccountId(accountId);
        }

        public static implicit operator string(AccountId accountId) => accountId.Value;

        private static void CheckValidity(string value)
        {
            if (!Guid.TryParse(value, out _))
            {
                throw new ArgumentException(nameof(value), "Account Id is not a GUID.");
            }
        }
    }

And here is the initialization class in Startup.cs that sets up the database and container.

private static async Task<AccountsRepository> InitializeCosmosClientAccountInstanceAsync(IConfigurationSection configurationSection)
{
    var databaseName = configurationSection.GetSection("DatabaseName").Value;
    string uri = configurationSection.GetSection("Uri").Value;
    string key = configurationSection.GetSection("Key").Value;
    CosmosClientBuilder clientBuilder = new CosmosClientBuilder(uri, key);
    CosmosClient client = clientBuilder
                        .WithConnectionModeDirect()
                        .Build();
    DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);

    string containerName = configurationSection.GetSection("AccountsContainerName").Value;
    await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
    AccountsRepository cosmosDbService = new AccountsRepository(client, databaseName, containerName);

    return cosmosDbService;
} 

Here is the stack trace from when the error occurs;

stackTrace": "   at Microsoft.Azure.Cosmos.ResponseMessage.EnsureSuccessStatusCode()\r\n
at Microsoft.Azure.Cosmos.CosmosResponseFactory.ToObjectInternal[T]
(ResponseMessage cosmosResponseMessage, CosmosSerializer jsonSerializer)\r\n
at Microsoft.Azure.Cosmos.CosmosResponseFactory. 
<CreateItemResponseAsync>b__6_0[T](ResponseMessage cosmosResponseMessage)\r\n
at Microsoft.Azure.Cosmos.CosmosResponseFactory.ProcessMessageAsync[T]
(Task`1 cosmosResponseTask, Func`2 createResponse)\r\n   at
Delivery.Api.Infrastructure.AccountsRepository.AddAccountAsync(Account
account) in 
C:\\AzureDevOps\\Delivery\\Delivery.Api\\Accounts\\AccountsRepository.cs:line 20\r\n 
at Delivery.Api.Accounts.AccountsApplicationService.HandleCreate(Create cmd) 
in C:\\AzureDevOps\\Delivery\\Delivery.Api\\Accounts\\AccountsApplicationService.cs:line 43\r\n 
at Delivery.Api.Infrastructure.RequestHandler.HandleCommand[T](T request, Func`2 handler, ILogger log) 
in C:\\AzureDevOps\\Delivery\\Delivery.Api\\Infrastructure\\RequestHandler.cs:line 16
6
  • What does AccountId look like? It looks like your obsession with avoiding primitive obsession is causing problems. Unless you have a custom JsonConverter for your AccountId type... Commented Nov 6, 2019 at 21:21
  • Does the stack trace point to the line where CreateItemAsync happens or maybe some initialization code is trying to create the Container and failing? Commented Nov 6, 2019 at 21:55
  • @KirkLarkin I updated my post with the AccoutId value object class and the initialization code for the database and container. We are doing Domain Driven Design on this project, which is why we try to use value objects instead of primitives, as that is one of the objectives of DDD as per Eric Evans, Vaughn Vernon, Alexey Zimarev, etc. Commented Nov 6, 2019 at 23:51
  • 1
    I expect that the JSON serialisation process for your AccountId class turns it into { "Value": "00000000-0000-0000-0000-000000000000" } instead of "00000000-0000-0000-0000-000000000000", which would explain the error message. Andrew Lock wrote about this stuff recently. Commented Nov 6, 2019 at 23:56
  • @MatiasQuaranta I added the initialization code for the container from my startup.cs in an update to my post. The stack trace is starting from the await _container.CreateItemAsync(account, new PartitionKey(account.Id)); call. Commented Nov 6, 2019 at 23:58

1 Answer 1

3

You may need to create your custom converter for your AccountId, OwnerId and so on.

Here is my test:

class AccountIdConverter

    class AccountIdConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(AccountId));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return AccountId.FromString(JToken.Load(reader).ToString());
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JToken.FromObject(value.ToString()).WriteTo(writer);
        }
    }

class AccountId

Add toString method, and set to use custom converter

    [JsonConverter(typeof(AccountIdConverter))]
    public class AccountId
    {
        public string Value { get; internal set; }
        protected AccountId() { }

        internal AccountId(string value) => Value = value;

        public static AccountId FromString(string accountId)
        {
            CheckValidity(accountId);
            return new AccountId(accountId);
        }

        public static implicit operator string(AccountId accountId) => accountId.Value;

        public override string ToString()
        {
            return Value;
        }
        private static void CheckValidity(string value)
        {
            if (!Guid.TryParse(value, out _))
            {
                throw new ArgumentException(nameof(value), "Account Id is not a GUID.");
            }
        }
    }

class Account

    class Account
    {
        [JsonProperty(PropertyName = "id")]
        public AccountId Id { get; set; }

        public Account(string id)
        {
            Id = AccountId.FromString(id);
        }
    }

Test

    static void Main(string[] args)
    {
        // Test toString 
        AccountId accountId = AccountId.FromString(Guid.NewGuid().ToString());
        Console.WriteLine(accountId.ToString());

        // Test AccountIdConverter
        Console.WriteLine(JsonConvert.SerializeObject(accountId));

        // Test for serializing Account
        Account account = new Account(Guid.NewGuid().ToString());
        string accountJson = JsonConvert.SerializeObject(account);
        Console.WriteLine(accountJson);

        // Test for deserializing Account
        Account accountDeserialized = JsonConvert.DeserializeObject<Account>(accountJson);
        Console.WriteLine(accountDeserialized.Id);

        Console.ReadLine();
    }

Result

enter image description here

You can see that the Account object which contains an AccountId object can be serialized and deserialized correctly as expected.

Sign up to request clarification or add additional context in comments.

1 Comment

To add to Jack's answer, the reason is that Cosmos DB's Ids need to be strings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.