MSDN Tutorials

Learn to build powerful applications with Microsoft technologies.

Creating Azure Functions with Cosmos DB using C#

This tutorial guides you through the process of creating serverless applications using Azure Functions and integrating them with Azure Cosmos DB for data persistence. We'll use C# as our programming language.

Prerequisites

Before you begin, ensure you have the following installed:

Step 1: Set up Azure Cosmos DB

First, we need to create an Azure Cosmos DB account and a database within it.

  1. Navigate to the Azure portal.
  2. Click Create a resource.
  3. Search for Azure Cosmos DB and select it.
  4. Click Create.
  5. Choose an API (e.g., Core (SQL) API).
  6. Fill in the required details: Subscription, Resource group, Account Name, Location.
  7. For Capacity mode, select Serverless for cost-effectiveness in development/testing.
  8. Click Review + create, then Create.
Make a note of your Cosmos DB account's URI and Primary Key from the "Keys" section. You'll need these later.

Step 2: Create an Azure Functions Project

Now, let's create a new Azure Functions project locally using the Azure Functions Core Tools.

  1. Open your terminal or command prompt.
  2. Create a new directory for your project:
    mkdir CosmosDbFunctionApp
    cd CosmosDbFunctionApp
  3. Initialize a new Azure Functions project:
    func init --worker-runtime dotnet
  4. Create a new HTTP trigger function:
    func new --name HttpTriggerCosmosDb --template "HTTP trigger" --authlevel "anonymous"

Step 3: Add Cosmos DB NuGet Packages

To interact with Cosmos DB, we need to add the necessary NuGet packages to our project.

  1. Install the Cosmos DB SDK:
    dotnet add package Microsoft.Azure.Cosmos --version 3.20.0
  2. Install the Azure Functions extension for Cosmos DB:
    dotnet add package Microsoft.Azure.WebJobs.Extensions.CosmosDB --version 3.0.10

Step 4: Configure Cosmos DB Connection

We'll use local.settings.json to store our Cosmos DB connection string.

Open local.settings.json and add the following:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "CosmosDbConnectionString": "AccountEndpoint=YOUR_COSMOS_DB_ENDPOINT;AccountKey=YOUR_COSMOS_DB_PRIMARY_KEY;"
  }
}
Replace YOUR_COSMOS_DB_ENDPOINT and YOUR_COSMOS_DB_PRIMARY_KEY with your actual Cosmos DB credentials obtained in Step 1.

Step 5: Implement the Azure Function

Now, let's modify our HttpTriggerCosmosDb.cs file to perform CRUD operations with Cosmos DB.

HttpTriggerCosmosDb.cs

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.Cosmos;
using System.Collections.Generic;
using System.Linq;

namespace CosmosDbFunctionApp
{
    public static class HttpTriggerCosmosDb
    {
        private static CosmosClient cosmosClient;
        private static Container container;
        private const string DatabaseId = "MyDatabase"; // Replace with your database ID
        private const string ContainerId = "MyContainer"; // Replace with your container ID

        static HttpTriggerCosmosDb()
        {
            // Initialize CosmosClient once
            string connectionString = Environment.GetEnvironmentVariable("CosmosDbConnectionString");
            if (!string.IsNullOrEmpty(connectionString))
            {
                cosmosClient = new CosmosClient(connectionString);
                var database = cosmosClient.GetDatabase(DatabaseId);
                container = database.GetContainer(ContainerId);
            }
            else
            {
                throw new InvalidOperationException("CosmosDbConnectionString is not set.");
            }
        }

        [FunctionName("CreateItem")]
        public static async Task<IActionResult> CreateItem(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "items")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request to create an item.");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var newItem = JsonConvert.DeserializeObject<Item>(requestBody);

            if (newItem == null)
            {
                return new BadRequestObjectResult("Please pass an item in the request body");
            }

            try
            {
                Item createdItem = await container.CreateItemAsync(newItem, new PartitionKey(newItem.Id));
                return new OkObjectResult(createdItem);
            }
            catch (CosmosException ex)
            {
                log.LogError($"Error creating item: {ex.Message}");
                return new StatusCodeResult(ex.StatusCode);
            }
        }

        [FunctionName("GetItems")]
        public static async Task<IActionResult> GetItems(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "items")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request to get all items.");

            try
            {
                var query = new QueryDefinition("SELECT * FROM c");
                FeedIterator<Item> feedIterator = container.GetItemQueryIterator<Item>(query);
                List<Item> items = new List<Item>();

                while (feedIterator.HasMoreResults)
                {
                    FeedResponse<Item> response = await feedIterator.ReadNextAsync();
                    items.AddRange(response);
                }

                return new OkObjectResult(items);
            }
            catch (CosmosException ex)
            {
                log.LogError($"Error getting items: {ex.Message}");
                return new StatusCodeResult(ex.StatusCode);
            }
        }

        [FunctionName("GetItemById")]
        public static async Task<IActionResult> GetItemById(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "items/{id}")] HttpRequest req,
            string id,
            ILogger log)
        {
            log.LogInformation($"C# HTTP trigger function processed a request to get item by ID: {id}");

            try
            {
                Item item = await container.ReadItemAsync<Item>(id, new PartitionKey(id));
                return new OkObjectResult(item);
            }
            catch (CosmosException ex)
            {
                log.LogError($"Error getting item by ID {id}: {ex.Message}");
                return new StatusCodeResult(ex.StatusCode);
            }
        }

        [FunctionName("UpdateItem")]
        public static async Task<IActionResult> UpdateItem(
            [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "items/{id}")] HttpRequest req,
            string id,
            ILogger log)
        {
            log.LogInformation($"C# HTTP trigger function processed a request to update item with ID: {id}");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            var updatedItemData = JsonConvert.DeserializeObject<Item>(requestBody);

            if (updatedItemData == null || updatedItemData.Id != id)
            {
                return new BadRequestObjectResult("Please pass the item ID in the route and ensure the ID in the body matches.");
            }

            try
            {
                Item updatedItem = await container.UpsertItemAsync(updatedItemData, new PartitionKey(updatedItemData.Id));
                return new OkObjectResult(updatedItem);
            }
            catch (CosmosException ex)
            {
                log.LogError($"Error updating item with ID {id}: {ex.Message}");
                return new StatusCodeResult(ex.StatusCode);
            }
        }

        [FunctionName("DeleteItem")]
        public static async Task<IActionResult> DeleteItem(
            [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "items/{id}")] HttpRequest req,
            string id,
            ILogger log)
        {
            log.LogInformation($"C# HTTP trigger function processed a request to delete item with ID: {id}");

            try
            {
                await container.DeleteItemAsync<Item>(id, new PartitionKey(id));
                return new OkResult(); // Return 200 OK for successful deletion
            }
            catch (CosmosException ex)
            {
                log.LogError($"Error deleting item with ID {id}: {ex.Message}");
                return new StatusCodeResult(ex.StatusCode);
            }
        }
    }

    // Define a simple model class for your items
    public class Item
    {
        [JsonProperty("id")]
        public string Id { get; set; } = Guid.NewGuid().ToString(); // Auto-generate ID if not provided

        public string Name { get; set; }
        public string Description { get; set; }
    }
}
Remember to replace "MyDatabase" and "MyContainer" with the actual names of your database and container in Cosmos DB. You might need to create the container if it doesn't exist.

Step 6: Run and Test the Function

You can now run your Azure Functions project locally.

  1. In your terminal, run:
    func start
  2. The output will show the URLs for your HTTP trigger functions. For example:
    HttpTriggerCosmosDb: [POST,GET,PUT,DELETE] http://localhost:7071/api/items
    HttpTriggerCosmosDb: [GET] http://localhost:7071/api/items/{id}
  3. Use tools like Postman or curl to test your endpoints:
    • POST /api/items: Create a new item. Send a JSON payload like:
      {
                                          "name": "Sample Item",
                                          "description": "This is a test item."
                                      }
    • GET /api/items: Retrieve all items.
    • GET /api/items/{id}: Retrieve a specific item by its ID.
    • PUT /api/items/{id}: Update an existing item.
    • DELETE /api/items/{id}: Delete an item.

Step 7: Deploy to Azure

Once you're satisfied with the local testing, you can deploy your function app to Azure.

  1. Create an Azure Function App resource in the Azure portal.
  2. Use the Azure Functions extension in VS Code or the Azure CLI to deploy your project.
When deploying, ensure you configure the Cosmos DB connection string in your Function App's application settings in Azure.

Congratulations! You've successfully created and integrated Azure Functions with Azure Cosmos DB using C#. This pattern is highly scalable and cost-effective for many modern applications.

Explore Azure Cosmos DB Documentation Explore Azure Functions Documentation