DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Luis Beltran
Luis Beltran

Posted on

C# Azure Functions to access CosmosDB

This article is part of The Fifth Annual C# Advent Calendar initiative by Matthew D. Groves. You'll find other helpful articles and tutorials published daily by community members and experts there, so make sure to check it out every day.

Interacting with CosmosDB from a serverless application can be achieved with very few lines of code using C#.

Pre-requisites:

  • A CosmosDB resource
  • An Azure Functions resource
  • An input binding (the CosmosDB resource) is required for read operations, while an output binding (the CosmosDB resource) is needed for the write operations. For each operation, I share the specific function.json required.

Considerations:

  • In each code there is a model (class) of your data. In our example, we are using a Student class.
  • Each operation is an HTTP trigger.

Let's go!

Operation 1. Retrieving data:
function.json is available here.

using System.Net;
using Microsoft.AspNetCore.Mvc;

public static async Task<IActionResult> Run(HttpRequest req, IEnumerable<Student> inputDocument, ILogger log)
{
  var students = (List<Student>) inputDocument;
  return new OkObjectResult(students);
}
Enter fullscreen mode Exit fullscreen mode

Comments:
No "special" library/SDK is required to read from CosmosDB since this is handled by the binding in function.json!

Testing:
Simply send a GET request to the URL provided by Azure Functions and you'll get your data:

Retrieving data

Operation 2. Adding data:
function.json is available here

#r "Newtonsoft.Json"

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

public static IActionResult Run(HttpRequest req, out object outputDocument, ILogger log)
{
  var body = new StreamReader(req.Body).ReadToEnd();
  dynamic data = JsonConvert.DeserializeObject(body);

  outputDocument = new
  {
    id = data.id,
    faculty = data.faculty,
    Name = data.Name,
    Semester = data.Semester
  };

  return (ActionResult)new OkResult();
}
Enter fullscreen mode Exit fullscreen mode

Comments:
The return statement writes new data in CosmosDB since we set that resource as an output binding (see the associated function.json).

Testing:
Send a POST request to the URL provided by Azure Functions including the new data (Student object) in the Body.

Adding data

Result is 200 OK =)

Result of adding data

Operation 3. Updating data:
function.json is available here

#r "Newtonsoft.Json"
#r "Microsoft.Azure.DocumentDB.Core"
#r "Microsoft.Azure.WebJobs.Extensions.CosmosDB"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Microsoft.Azure.Documents.Client;

public static async Task<IActionResult> Run(
  HttpRequest req, 
  [CosmosDB(ConnectionStringSetting = "universitylb7_DOCUMENTDB")] DocumentClient outputDocument, 
  ILogger log, string id, string faculty)
{
  string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
  var updated = JsonConvert.DeserializeObject<Student>(requestBody);

  var option = new FeedOptions { EnableCrossPartitionQuery = true };
  var collectionUri = UriFactory.CreateDocumentCollectionUri("studentsdb", "students");

  var document = outputDocument.CreateDocumentQuery(collectionUri, option).Where(t => t.Id == id)
        .AsEnumerable().FirstOrDefault();

  if (document == null)
  {
    return new NotFoundResult();
  }

  document.SetPropertyValue("Name", updated.Name);
  document.SetPropertyValue("Semester", updated.Semester);

  await outputDocument.ReplaceDocumentAsync(document);

  return new OkResult();
}
Enter fullscreen mode Exit fullscreen mode

Comments:
To update (and delete) we need the libraries ( Microsoft.Azure.DocumentDB.Core and Microsoft.Azure.WebJobs.Extensions.CosmosDB) since we need to search and then replace the document with the latest content.

Moreover, in the function.json you will see that a route is specified: it means that the URL must include the id and partition key of the document we want to update (the combination of both elements must be unique in a collection). And of course, you will include the new data in the Body request.

Updating data with query parameters

Once again, the result is a nice 200 OK.

200 OK

Operation 4. Deleting data:
function.json is available here

#r "Newtonsoft.Json"
#r "Microsoft.Azure.DocumentDB.Core"
#r "Microsoft.Azure.WebJobs.Extensions.CosmosDB"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Documents.Client;

public static async Task<IActionResult> Run(
  HttpRequest req, 
  [CosmosDB(ConnectionStringSetting = "universitylb7_DOCUMENTDB")] DocumentClient outputDocument, 
  ILogger log, string id, string faculty)
{
    var option = new FeedOptions { EnableCrossPartitionQuery = true };
    var collectionUri = UriFactory.CreateDocumentCollectionUri("studentsdb", "students");

    var document = outputDocument.CreateDocumentQuery(collectionUri, option).Where(t => t.Id == id).AsEnumerable().FirstOrDefault();

    if (document == null)
    {
        return new NotFoundResult();
    }

    await outputDocument.DeleteDocumentAsync(document.SelfLink, new RequestOptions
    {
    PartitionKey = new Microsoft.Azure.Documents.PartitionKey(faculty)
    });

    return new OkResult();
}
Enter fullscreen mode Exit fullscreen mode

Comments:
The code to delete is quite similar to the update one, except we don't replace here (but delete) the document.

Testing:
Just include the id and faculty in the URL query parameters.

Deleting data

Bye-bye data!

Begone!


And with that, we have set a serverless API that can interact with CosmosDB.

I hope that this blog post was interesting and useful for you. I invite you to visit my blog for more technical posts about Xamarin, Azure, and the .NET ecosystem. I write in Spanish language =)

Thanks for your time, and enjoy the rest of the C# Advent Calendar publications!

See you next time,
Luis

Top comments (1)

Collapse
stimms profile image
Simon Timms

I was surprised that the update scenario took so much code and that the bindings weren't better for it. I went and looked at the input binding docs and found

When the function exits successfully, any changes made to the input document via named input parameters are automatically persisted.
Enter fullscreen mode Exit fullscreen mode

So I think maybe that example could be simplified into a variant of the loading a doc version.

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.