Connect an app to Azure Cosmos DB in C#
In this exercise, you connect an SDK application to the local Azure Cosmos DB emulator, add logging and error handling, and then connect the same application to a cloud account with Microsoft Entra authentication.
Before you start
To complete this exercise, you need an Azure subscription with permission to create resources and assign roles.
If your lab environment isn’t set up yet, follow Set up your lab environment to install Visual Studio Code, Git, the Azure CLI, and PowerShell 7.
You also need the .NET 10 SDK or later installed.
Install the Azure Cosmos DB emulator using the local development guide. Use the Windows emulator or legacy Linux container image on an x64 host for this exercise.
Set up your Azure Cosmos DB resources
Most of this exercise runs against the local emulator. The final task uses the verified core account described below.
The core exercises reuse an account prepared with the core profile, not the two-item account from the first portal exercise. Before skipping setup, open Allfiles/Labs/Shared in PowerShell, sign in with az login, and set $resourceGroup, $location, and $accountName to your recorded values. Run ./verify.ps1 -ResourceGroup $resourceGroup -AccountName $accountName -LabProfile core and continue only when it succeeds. In Data Explorer, confirm 295 items in cosmicworks/product and 237 in cosmicworks/productMeta with SELECT VALUE COUNT(1) FROM c.
If you have no verified core account, follow the setup steps below. To add missing resources to an existing lab account, pass its explicit -AccountName to setup rather than using a different module’s name prefix. Reseeding restores canonical items but doesn’t remove extra items or reset container policies. Resolve mismatches before continuing; don’t reset a shared account automatically.
-
Start Visual Studio Code.
-
If you don’t have the lab code yet, clone the repository for DP-420: open the command palette with Ctrl+Shift+P, run Git: Clone, and enter the following URL. Choose a local folder when prompted. Otherwise, open the folder from your previous clone.
https://github.com/microsoftlearning/dp-420-cosmos-db-dev -
Once the repository is cloned, open that local folder in Visual Studio Code.
-
In the Explorer pane, browse to the Allfiles/Labs/Shared folder.
-
Open the context menu for the folder and select Open in Integrated Terminal. If the terminal isn’t PowerShell, select the dropdown beside the + in the terminal toolbar and choose PowerShell.
-
Sign in to the Azure CLI. A browser window opens so you can sign in to Azure.
az login -
Set the resource group and region variables. If your lab environment provides a resource group, replace
ResourceGroup1with its exact name and use a permitted region. Don’t create a different resource group. Otherwise, use a resource group and region in your own subscription.$resourceGroup = "ResourceGroup1" $location = "westus2" -
Run the setup script.
./setup.ps1 -ResourceGroup $resourceGroup -Location $location -NamePrefix dp420lab03 -LabProfile coreAzure Cosmos DB account names have to be globally unique, so the script builds one for you by adding six random characters to the prefix, giving a name like
dp420lab03a7f3k9. -
Wait for the script to finish. The whole script takes 5-10 minutes to run.
-
Record the Account name and Account endpoint values the script prints. You need the endpoint in the final task, and it looks like
https://<your-account-name>.documents.azure.com:443/.
The core profile prepares a shared account for this exercise and later exercises. It creates the following resources. This exercise uses the product container in the cloud account.
| Resource | Configuration |
|---|---|
| Azure Cosmos DB account | API for NoSQL, with key-based authentication disabled |
cosmicworks database |
Holds the five containers in the core profile |
product container |
Partitioned on /categoryId, autoscale up to 1,000 RU/s, loaded with 295 CosmicWorks products |
productMeta container |
Partitioned on /type, autoscale up to 1,000 RU/s, loaded with 237 metadata items |
operations container |
Partitioned on /categoryId, autoscale up to 1,000 RU/s, initially empty |
bulkload container |
Partitioned on /categoryId, autoscale up to 1,000 RU/s, initially empty |
leases container |
Partitioned on /id, manual throughput of 400 RU/s, initially empty |
| Role assignment | Cosmos DB Built-in Data Contributor, granted to your signed-in identity |
The four autoscale containers each have their own 1,000 RU/s maximum. The leases container has separate manual throughput. Provisioned throughput incurs charges even when the containers are empty.
Use the public emulator key only for local development. The cloud account disables key-based authentication and uses your Azure CLI identity through AzureCliCredential.
📝 A new role assignment takes a few minutes to propagate. If a later step fails with a 403 error, wait a moment and try again.
Task 1: Set up the project and install the SDK
-
Open a terminal and create a new console project:
dotnet new console -n cosmos-connect-exercise cd cosmos-connect-exercise -
Add the Azure Cosmos DB SDK, the Newtonsoft.Json dependency it requires, and the Azure Identity library:
dotnet add package Microsoft.Azure.Cosmos dotnet add package Newtonsoft.Json --version 13.0.4 dotnet add package Azure.Identity -
Open Program.cs and replace its contents with the following using directives:
using Azure.Identity; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Fluent;
❗ Build a single file across Tasks 2 through 6. Keep the client markers shown in the code; later tasks use them to identify which block to replace.
Task 2: Start the emulator and read account properties
The emulator provides a local endpoint at https://localhost:8081/ and uses a documented public key. It doesn’t require an Azure connection.
-
Start the emulator.
On Windows, select Azure Cosmos DB Emulator from the Start menu, or run the executable directly:
& "$env:ProgramFiles\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe"If Windows displays a User Account Control prompt, verify that the publisher is Microsoft Corporation before approving. Ask your lab administrator if credentials are required; don’t disable User Account Control.
If you installed the container image instead, start it with:
docker run --publish 8081:8081 --publish 10250-10255:10250-10255 --name cosmos-emulator --detach mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest -
Wait a minute or two for the emulator to finish starting.
-
Confirm that the emulator is running by opening Data Explorer at
https://localhost:8081/_explorer/index.html.Keep the emulator running for Tasks 2 through 5.
-
In Program.cs, below the using directives, add the emulator connection constants and client initialization:
string endpoint = "https://localhost:8081/"; string key = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; // ==== CLIENT ==== Task 4 replaces this section. Task 6 replaces it again. // Emulator-only certificate bypass; use Gateway mode for this local client. CosmosClientOptions options = new() { HttpClientFactory = () => { var handler = new System.Net.Http.HttpClientHandler { ServerCertificateCustomValidationCallback = System.Net.Http.HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; return new System.Net.Http.HttpClient(handler); }, ConnectionMode = ConnectionMode.Gateway }; CosmosClient client = new(endpoint, key, options); // ==== END CLIENT ====⚠
DangerousAcceptAnyServerCertificateValidatordisables SSL certificate validation. Use this setting only for the local emulator; never use it in production code. Alternatively, import the emulator certificate as described in Import the emulator’s TLS/SSL certificate and omit the customHttpClientFactory. -
To verify the connection, read account properties. These lines stay in the file for the whole exercise:
// Read account metadata from the service, not an item in a container. AccountProperties account = await client.ReadAccountAsync(); Console.WriteLine($"Account name: {account.Id}"); Console.WriteLine($"Primary region: {account.WritableRegions.FirstOrDefault()?.Name}"); Console.WriteLine($"Consistency: {account.Consistency.DefaultConsistencyLevel}"); -
Run the project:
dotnet run -
Verify that the output shows the emulator account name (typically localhost), the primary region, and the consistency level.
Task 3: Create the database and container, then reference them in code
The emulator starts empty, so create the cosmicworks database and product container in its Data Explorer. The setup script already created the same pair in your cloud account.
-
Open the emulator’s Data Explorer at
https://localhost:8081/_explorer/index.html. -
Select New Container and enter the following, then select OK:
Setting Value Database ID Create new, cosmicworksContainer ID productPartition key /categoryIdContainer throughput Manual, 400 -
Below the account output, add the following code to reference the database and container:
// These calls build references; ReadContainerAsync makes the service request. Database database = client.GetDatabase("cosmicworks"); Container container = client.GetContainer("cosmicworks", "product"); ContainerProperties properties = await container.ReadContainerAsync(); Console.WriteLine($"Database: {database.Id}"); Console.WriteLine($"Container: {properties.Id}");The container read confirms that the resource exists and that the application can access it.
-
Run the project again and confirm both names appear in the output.
Task 4: Add a logging handler
Add a custom SDK request handler that logs request methods, URIs, status codes, and RU charges.
-
At the bottom of Program.cs, after all top-level statements, add the following class definition:
public class LogHandler : RequestHandler { public override async Task<ResponseMessage> SendAsync( RequestMessage request, CancellationToken cancellationToken) { Console.WriteLine($" [REQ] {request.Method.Method} {request.RequestUri}"); // Forward the request through the SDK pipeline. ResponseMessage response = await base.SendAsync(request, cancellationToken); Console.WriteLine($" [RES] {(int)response.StatusCode} {response.StatusCode}" + $" RU: {response.Headers.RequestCharge:0.00}"); return response; } } -
Replace the marked client section with the following code:
// ==== CLIENT ==== Task 6 replaces this section. // Keep the emulator-only transport settings while adding the logging handler. CosmosClient client = new CosmosClientBuilder(endpoint, key) .WithConnectionModeGateway() .WithHttpClientFactory(() => { var handler = new System.Net.Http.HttpClientHandler { ServerCertificateCustomValidationCallback = System.Net.Http.HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; return new System.Net.Http.HttpClient(handler); }) .AddCustomHandlers(new LogHandler()) .Build(); // ==== END CLIENT ====Leave the
endpointandkeylines above the marker alone. -
Run the project again and inspect the method, URI, status code, and RU charge logged by the handler.
Task 5: Handle a missing-item error
Read a nonexistent item and inspect the SDK exception and status code.
-
Below the two
Console.WriteLinelines that print the database and container names, add atry/catchblock that attempts to read a nonexistent item:try { // A nonexistent ID and partition key deliberately exercise error handling. var response = await container.ReadItemAsync<dynamic>( id: "nonexistent-id", partitionKey: new PartitionKey("nonexistent-category")); } catch (CosmosException ex) { // Inspect the exception's status code. Console.WriteLine($"\nCaught CosmosException:"); Console.WriteLine($" Status code: {(int)ex.StatusCode} ({ex.StatusCode})"); Console.WriteLine($" Is retryable: " + $"{ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests || ex.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable}"); } -
Run the project. Confirm that the output shows 404 Not Found and that
Is retryableisFalse.
Task 6: Point the app at the cloud account
Connect the application to the cloud account using AzureCliCredential and your Azure CLI sign-in. Keep the existing account read, container access, logging, and error handling.
-
Point
endpointat the account the setup script printed, and delete thekeyline. Key-based authentication is disabled for this account:string endpoint = "<cosmos-endpoint>"; -
Replace everything between the
// ==== CLIENT ====markers with the following code:// ==== CLIENT ==== // Explicitly use az login and normal certificate validation for the cloud account. // Retain the logging handler while removing the emulator's transport overrides. CosmosClient client = new CosmosClientBuilder(endpoint, new AzureCliCredential()) .AddCustomHandlers(new LogHandler()) .Build(); // ==== END CLIENT ====The cloud client uses normal certificate validation and the default Direct connection mode. Ensure the required outbound ports are allowed. The logging handler remains enabled.
-
Run the project:
dotnet run -
Confirm that the output identifies your cloud account and the
productcontainer. The intentional 404 from Task 5 should still appear.
Clean up resources
When you finish the course, delete the resource group only if you created it and every resource in it can be removed. If your lab provided ResourceGroup1, skip this command and delete only the exercise resources you no longer need:
az group delete --name $resourceGroup --yes --no-wait
If your lab environment provided the resource group, delete only the Azure Cosmos DB account instead:
az cosmosdb delete --name <your-account-name> --resource-group $resourceGroup --yes