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:
- .NET Core SDK (version 3.1 or later)
- Visual Studio Code with the Azure Functions extension
- An Azure subscription (a free trial is available)
Step 1: Set up Azure Cosmos DB
First, we need to create an Azure Cosmos DB account and a database within it.
- Navigate to the Azure portal.
- Click Create a resource.
- Search for Azure Cosmos DB and select it.
- Click Create.
- Choose an API (e.g., Core (SQL) API).
- Fill in the required details: Subscription, Resource group, Account Name, Location.
- For Capacity mode, select Serverless for cost-effectiveness in development/testing.
- Click Review + create, then Create.
Step 2: Create an Azure Functions Project
Now, let's create a new Azure Functions project locally using the Azure Functions Core Tools.
- Open your terminal or command prompt.
- Create a new directory for your project:
mkdir CosmosDbFunctionApp cd CosmosDbFunctionApp
- Initialize a new Azure Functions project:
func init --worker-runtime dotnet
- 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.
- Install the Cosmos DB SDK:
dotnet add package Microsoft.Azure.Cosmos --version 3.20.0
- 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;"
}
}
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; }
}
}
"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.
- In your terminal, run:
func start
- 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}
- 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.
- POST /api/items: Create a new item. Send a JSON payload like:
Step 7: Deploy to Azure
Once you're satisfied with the local testing, you can deploy your function app to Azure.
- Create an Azure Function App resource in the Azure portal.
- Use the Azure Functions extension in VS Code or the Azure CLI to deploy your project.
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