Troubleshoot an application using the Azure Cosmos DB for NoSQL SDK

Azure Cosmos DB offers an extensive set of response codes, which help us easily troubleshoot issues that could arise with our different operation types. The catch is to make sure we program proper error handling when creating apps for Azure Cosmos DB.

In this lab, we’ll create a menu driven program that will allow us to insert or delete one of two documents. The main purpose of this lab is to introduce us to how to use some of the most common response codes and how to use them in our app’s error handling code. While we’ll code error handling for multiple response codes, we’ll only trigger two different types of conditions. Additionally the error handling won’t do anything complex, depending on the response code, it will either display a message to the screen or wait 10 seconds and retry the operation one more time.

Prepare your development environment

If you haven’t already cloned the lab code repository for DP-420 to the environment where you’re working on this lab, follow these steps to do so. Otherwise, open the previously cloned folder in Visual Studio Code.

  1. Start Visual Studio Code.

    📝 If you are not already familiar with the Visual Studio Code interface, review the Get Started guide for Visual Studio Code

  2. Open the command palette and run Git: Clone to clone the https://github.com/microsoftlearning/dp-420-cosmos-db-dev GitHub repository in a local folder of your choice.

    💡 You can use the CTRL+SHIFT+P keyboard shortcut to open the command palette.

  3. Once the repository has been cloned, open the local folder you selected in Visual Studio Code.

Create an Azure Cosmos DB for NoSQL account

Azure Cosmos DB is a cloud-based NoSQL database service that supports multiple APIs. When provisioning an Azure Cosmos DB account for the first time, you’ll select which of the APIs you want the account to support (for example, Mongo API or NoSQL API). Once the Azure Cosmos DB for NoSQL account is done provisioning, you can retrieve the endpoint and key. Use the endpoint and key to connect to the Azure Cosmos DB for NoSQL account programmatically. Use the endpoint and key on the connection strings of the Azure SDK for .NET or any other SDK.

  1. In a new web browser window or tab, navigate to the Azure portal (portal.azure.com).

  2. Sign into the portal using the Microsoft credentials associated with your subscription.

  3. Select + Create a resource, search for Cosmos DB, and then create a new Azure Cosmos DB for NoSQL account resource with the following settings, leaving all remaining settings to their default values:

    Setting Value
    Subscription Your existing Azure subscription
    Resource group Select an existing or create a new resource group
    Account Name Enter a globally unique name
    Location Choose any available region
    Capacity mode Provisioned throughput
    Apply Free Tier Discount Do Not Apply

    📝 Your lab environments may have restrictions preventing you from creating a new resource group. If that is the case, use the existing pre-created resource group.

  4. Wait for the deployment task to complete before continuing with this task.

  5. Go to the newly created Azure Cosmos DB account resource and navigate to the Keys pane.

  6. This pane contains the connection details and credentials necessary to connect to the account from the SDK. Specifically:

    1. Notice the URI field. You’ll use this endpoint value later in this exercise.

    2. Notice the PRIMARY KEY field. You’ll use this key value later in this exercise.

  7. Minimize, but don’t close your browser window. We’ll come back to the Azure portal a few minutes after we start a background workload in the next steps.

Import the Microsoft.Azure.Cosmos library into a .NET script

The .NET CLI includes an add package command to import packages from a pre-configured package feed. A .NET installation uses NuGet as its default package feed.

  1. In Visual Studio Code, in the Explorer pane, browse to the 26-sdk-troubleshoot folder.

  2. Open the context menu for the 26-sdk-troubleshoot folder and then select Open in Integrated Terminal to open a new terminal instance.

    📝 This command will open the terminal with the starting directory already set to the 26-sdk-troubleshoot folder.

  3. Add the Microsoft.Azure.Cosmos package from NuGet using the following command:

     dotnet add package Microsoft.Azure.Cosmos --version 3.22.1
    

Run a script to create menu-driven options to insert and delete documents.

Before we can run our application, we need to connect it to our Azure Cosmos DB account.

  1. In Visual Studio Code, in the Explorer pane, browse to the 26-sdk-troubleshoot folder.

  2. Open the Program.cs code file.

  3. Update the existing variable named endpoint with its value set to the endpoint of the Azure Cosmos DB account you created earlier.

     private static readonly string endpoint = "<cosmos-endpoint>";
    

    📝 For example, if your endpoint is: https­://dp420.documents.azure.com:443/, then the C# statement would be: private static readonly string endpoint = “https­://dp420.documents.azure.com:443/”;.

  4. Update the existing variable named key with its value set to the key of the Azure Cosmos DB account you created earlier.

     private static readonly string key = "<cosmos-key>";
    

    📝 For example, if your key is: fDR2ci9QgkdkvERTQ==, then the C# statement would be: private static readonly string key = “fDR2ci9QgkdkvERTQ==”;.

  5. Save the file.

  6. Build and run the project using the dotnet run command:

     dotnet run
    

    📝 This is a very simple program. It will display a menu with five options as show below. Two options to insert a predefined document, two to delete a predefined document, and an option to exit the program.

    1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'
    2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'
    3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'
    4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'
    5) Exit
    Select an option:
    

Time to insert and delete documents.

  1. Select 1 and ENTER to insert the first document. The program will insert the first document and return the following message.

     Insert Successful.
     Document for customer with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828' Inserted.
     Press [ENTER] to continue
    
  2. Again, select 1 and ENTER to insert the first document. This time the program will crash with an exception. Looking through the error stack, we can find the reason for the program failure. As we can tell from the message extracted from the error stack, we come across an unhandled exception “Conflict (409)”

     Unhandled exception. Microsoft.Azure.Cosmos.CosmosException : Response status code does not indicate success: Conflict (409);
    
  3. Since we’re inserting a document, we’ll need to review the list of common create document status codes returned when a document is created. The description of this code is, the ID provided for the new document has been taken by an existing document. This is obvious, since we just ran the menu option to create the same document a few moments ago.

  4. Digging further into the stack, we can see that this exception was called from line 100, and that in turn was called from line 64.

     at Program.CreateDocument1(Container Customer) in C:\Git\dp-420-cosmos-db-dev\26-sdk-troubleshoot\Program.cs:line 100   
    at Program.CompleteTaskOnCosmosDB(String consoleinputcharacter, Container container) in C:\Git\dp-420-cosmos-db-dev\26-sdk-troubleshoot\Program.cs:line 64
    
  5. Reviewing line 100, as expected, the error was caused by the CreateItemAsync operation.

         ItemResponse<customerInfo> response = await Customer.CreateItemAsync<customerInfo>(customer, new PartitionKey(customerID));
    
  6. Furthermore, by reviewing lines 100 to 103, it’s obvious that this code has no error handling. We’ll need to fix that.

         ItemResponse<customerInfo> response = await Customer.CreateItemAsync<customerInfo>(customer, new PartitionKey(customerID));
         Console.WriteLine("Insert Successful.");
         Console.WriteLine("Document for customer with id = '" + customerID + "' Inserted.");
    
  7. We’ll need to decide what our error-handling code should do. Reviewing the create document status codes, we could choose to create error-handling code for every possible status code for this operation. In this lab, we will only consider from this list, status Code 403 a 409. All other status codes returned will just display the system error message.

    📝 Note that while we will code a error-hadling task for a 403 exceptions, in this lab we will not generate a 403 exception.

  8. Let’s add error handling for the function named CompleteTaskOnCosmosDB. Locate the while loop in the function Main on line 45 and wrap up the calls of CompleteTaskOnCosmosDB with and error-handling code. We will replace the CompleteTaskOnCosmosDB statement on line 47 for the code below. The first thing to notice in this new code, is that on the catch we’re capturing an exception of type CosmosException class. This class includes the property StatusCode, which returns the request completion status code from the Azure Cosmos DB service. The StatusCode property is of type System.Net.HttpStatusCode, we can use this value and compare it against the field names from the .NET HTTP Status Code.

         try
         {
             await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
         }
         catch (CosmosException e)
         {
                     switch (e.StatusCode.ToString())
                     {
                         case ("Conflict"):
                             Console.WriteLine("Insert Failed. Response Code (409).");
                             Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                             break;
                         case ("Forbidden"):
                             Console.WriteLine("Response Code (403).");
                             Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                             Console.WriteLine("Firewall blocking requests.");
                             Console.WriteLine("Partition key exceeding storage.");
                             Console.WriteLine("Non-data operations are not allowed.");
                             break;
                         default:
                             Console.WriteLine(e.Message);
                             break;
                     }
    
         }
    
    
  9. Save the file and since we crashed, we need to run our Menu program again, so run the command:

     dotnet run
    
  10. Again, select 1 and ENTER to insert the first document. This time we don’t crash but get a more user-friendly message of what happened.

     Insert Failed. 
     Response Code (409).
     Can not insert a duplicate partition key, customer with the same ID already exists.
    
  11. This code added the error-handling for 403 and 409 exceptions, let’s now additionally add code for some common communication types of exceptions. There are three common communication type of exceptions: 429, 503, and 408 or too many request, service unavailable, and request time out respectively. Around line 66 there should now be a default statement, so add the code below right after the previous break; statement and right before the default statement. The code will verify if we find any of these communication exceptions, and if so, wait 10 seconds, and then try to insert the document one more time. Lets’ add beyond the code:

                         case ("TooManyRequests"):
                         case ("ServiceUnavailable"):
                         case ("RequestTimeout"):
                             // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                             await Task.Delay(10000); // Wait 10 seconds
                             try
                             {
                                 Console.WriteLine("Try one more time...");
                                 await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                             }
                             catch (CosmosException e2)
                             {
                                 Console.WriteLine("Insert Failed. " + e2.Message);
                                 Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                 break;
                             }
                             break;
    

    📝 Note that while we will code a task of what to do if we encounter a 429, 503 or 408 exception, in this lab we will not generate an error with that type of exception.

  12. Our Main function should now look something like this:

         public static async Task Main(string[] args)
         {
    
             CosmosClient client = new CosmosClient(connectionString,new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests = 50, MaxRetryWaitTimeOnRateLimitedRequests = new TimeSpan(0,1,30)});
    
             Console.WriteLine("Creating Azure Cosmos DB Databases and containers");
    
             Database CustomersDB = await client.CreateDatabaseIfNotExistsAsync("CustomersDB");
             Container CustomersDB_Customer_container = await CustomersDB.CreateContainerIfNotExistsAsync(id: "Customer", partitionKeyPath: "/id", throughput: 400);
    
             Console.Clear();
             Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("5) Exit");
             Console.Write("\r\nSelect an option: ");
        
             string consoleinputcharacter;
            
             while((consoleinputcharacter = Console.ReadLine()) != "5") 
             {
                  try
                  {
                      await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                  }
                  catch (CosmosException e)
                  {
                      switch (e.StatusCode.ToString())
                      {
                         case ("Conflict"):
                             Console.WriteLine("Insert Failed. Response Code (409).");
                             Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                             break;
                         case ("Forbidden"):
                             Console.WriteLine("Response Code (403).");
                             Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                             Console.WriteLine("Firewall blocking requests.");
                             Console.WriteLine("Partition key exceeding storage.");
                             Console.WriteLine("Non-data operations are not allowed.");
                             break;
                         case ("TooManyRequests"):
                         case ("ServiceUnavailable"):
                         case ("RequestTimeout"):
                             // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                             await Task.Delay(10000); // Wait 10 seconds
                             try
                             {
                                 Console.WriteLine("Try one more time...");
                                 await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                             }
                             catch (CosmosException e2)
                             {
                                 Console.WriteLine("Insert Failed. " + e2.Message);
                                 Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                 break;
                             }
                             break;
                         default:
                             Console.WriteLine(e.Message);
                             break;
                      }
                 }
                    
    
                 Console.WriteLine("Choose an action:");
                 Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("5) Exit");
                 Console.Write("\r\nSelect an option: ");
             }
         }
    
  13. Note that CreateDocument2 function will also be fixed by the changes above.

  14. Finally the functions DeleteDocument1 and DeleteDocument2 also need the following code to be replaced for the proper error-handling code similar to the CreateDocument1 function. The only difference with these functions besides using DeleteItemAsync instead of CreateItemAsync is that deletes status codes are different than the insert status codes. For the deletes, we only care about a 404 status code, which represents document not found. Lets update error handling of CompleteTaskOnCosmosDB function call with additional case. On the Main function the following code needs to be added above default case:

                     case ("NotFound"):
                         Console.WriteLine("Delete Failed. Response Code (404).");
                         Console.WriteLine("Can not delete customer, customer not found.");
                         break;         
    
  15. Save the file.

  16. Once you’re done fixing all functions, test all the menu options several times to make sure you that your app is returning a message when encountering an exception and not crashing. If your app crashes, fix the errors and just rerun the command:

     dotnet run
    
  17. Don’t peek, but once you’re done, your Main codes should look something like this.

         public static async Task Main(string[] args)
         {
             CosmosClient client = new CosmosClient(connectionString,new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests = 50, MaxRetryWaitTimeOnRateLimitedRequests = new TimeSpan(0,1,30)});
    
             Console.WriteLine("Creating Azure Cosmos DB Databases and containers");
    
             Database CustomersDB = await client.CreateDatabaseIfNotExistsAsync("CustomersDB");
             Container CustomersDB_Customer_container = await CustomersDB.CreateContainerIfNotExistsAsync(id: "Customer", partitionKeyPath: "/id", throughput: 400);
    
             Console.Clear();
             Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
             Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
             Console.WriteLine("5) Exit");
             Console.Write("\r\nSelect an option: ");
        
             string consoleinputcharacter;
            
             while((consoleinputcharacter = Console.ReadLine()) != "5") 
             {
                     try
                     {
                         await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                     }
                     catch (CosmosException e)
                     {
                         switch (e.StatusCode.ToString())
                         {
                             case ("Conflict"):
                                 Console.WriteLine("Insert Failed. Response Code (409).");
                                 Console.WriteLine("Can not insert a duplicate partition key, customer with the same ID already exists."); 
                                 break;
                             case ("Forbidden"):
                                 Console.WriteLine("Response Code (403).");
                                 Console.WriteLine("The request was forbidden to complete. Some possible reasons for this exception are:");
                                 Console.WriteLine("Firewall blocking requests.");
                                 Console.WriteLine("Partition key exceeding storage.");
                                 Console.WriteLine("Non-data operations are not allowed.");
                                 break;
                             case ("TooManyRequests"):
                             case ("ServiceUnavailable"):
                             case ("RequestTimeout"):
                                 // Check if the issues are related to connectivity and if so, wait 10 seconds to retry.
                                 await Task.Delay(10000); // Wait 10 seconds
                                 try
                                 {
                                     Console.WriteLine("Try one more time...");
                                     await CompleteTaskOnCosmosDB(consoleinputcharacter, CustomersDB_Customer_container);
                                 }
                                 catch (CosmosException e2)
                                 {
                                     Console.WriteLine("Insert Failed. " + e2.Message);
                                     Console.WriteLine("Can not insert a duplicate partition key, Connectivity issues encountered.");
                                     break;
                                 }
                                 break;    
                             case ("NotFound"):
                                 Console.WriteLine("Delete Failed. Response Code (404).");
                                 Console.WriteLine("Can not delete customer, customer not found.");
                                 break; 
                             default:
                                 Console.WriteLine(e.Message);
                                 break;
                         }
    
                     }
    
                 Console.WriteLine("Choose an action:");
                 Console.WriteLine("1) Add Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("2) Add Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("3) Delete Document 1 with id = '0C297972-BE1B-4A34-8AE1-F39E6AA3D828'");
                 Console.WriteLine("4) Delete Document 2 with id = 'AAFF2225-A5DD-4318-A6EC-B056F96B94B7'");
                 Console.WriteLine("5) Exit");
                 Console.Write("\r\nSelect an option: ");
             }
         }
    

Conclusion

Even the most junior developers knows that proper error handling must be added to all code. While the error handling in this code is simple, it should have given you the basics about the Azure Cosmos DB exception components that will let you create robust error-handling solutions in your code.