Getting Started with MongoDB Atlas and Azure Functions using .NET and C#
Rate this tutorial
So you need to build an application with minimal operating costs that can also scale to meet the growing demand of your business. This is a perfect scenario for a serverless function, like those built with Azure Functions. With serverless functions you can focus more on the application and less on the infrastructure and operations side of things. However, what happens when you need to include a database in the mix?
In this tutorial we'll explore how to create a serverless function with Azure Functions and the .NET runtime to interact with MongoDB Atlas. If you're not familiar with MongoDB, it offers a flexible data model that can be used for a variety of use cases while being integrated into most application development stacks with ease. Scaling your MongoDB database and Azure Functions to meet demand is easy, making them a perfect match.
There are a few requirements that must be met prior to starting this tutorial:
- The Azure Functions Core Tools installed and configured.
- .NET or .NET Core 6.0+
- A [MongoDB Atlas](https://www.mongodb.com/atlas database) deployed and configured with appropriate user rules and network rules.
We'll be using the Azure CLI to configure Azure and we'll be using the Azure Functions Core Tools to create and publish serverless functions to Azure.
Configuring MongoDB Atlas is out of the scope of this tutorial so the assumption is that you've got a database available, a user that can access that database, and proper network access rules so Azure can access your database. If you need help configuring these items, check out the MongoDB Atlas tutorial to set everything up.
We're going to start by creating an Azure Function locally on our computer. We'll be able to test that everything is working prior to uploading it to Azure.
Within a command prompt, execute the following command:
1 func init MongoExample
The above command will start the wizard for creating a new Azure Functions project. When prompted, choose .NET as the runtime since our focus will be C#. It shouldn’t matter if you choose the isolated process or not, but we won’t be using the isolated process for this example.
With your command prompt, navigate into the freshly created project and execute the following command:
1 func new --name GetMovies --template "HTTP trigger"
The above command will create a new "GetMovies" Function within the project using the "HTTP trigger" template which is quite basic. In the "GetMovies" Function, we plan to retrieve one or more movies from our database.
While it wasn't a requirement to use the MongoDB sample database sample_mflix and sample collection movies in this project, it will be referenced throughout. Nothing we do can't be replicated using a custom database or collection.
At this point we can start writing some code!
Since MongoDB will be one of the highlights of this tutorial, we need to install it as a dependency. Within the project, execute the following from the command prompt:
1 dotnet add package MongoDB.Driver
If you're using NuGet there are similar commands you can use, but for the sake of this example we'll stick with the .NET CLI.
Because we created a new Function, we should have a GetMovies.cs file at the root of the project. Open it and replace the existing code with the following C# code:
1 using System; 2 using System.IO; 3 using System.Threading.Tasks; 4 using Microsoft.AspNetCore.Mvc; 5 using Microsoft.Azure.WebJobs; 6 using Microsoft.Azure.WebJobs.Extensions.Http; 7 using Microsoft.AspNetCore.Http; 8 using Microsoft.Extensions.Logging; 9 using Newtonsoft.Json; 10 using MongoDB.Driver; 11 using System.Collections.Generic; 12 using MongoDB.Bson.Serialization.Attributes; 13 using MongoDB.Bson; 14 using System.Text.Json.Serialization; 15 16 namespace MongoExample 17 { 18 19 [ ]20 public class Movie 21 { 22 23 [ ]24 [ ]25 public string? Id { get; set; } 26 27 [ ]28 [ ]29 public string Title { get; set; } = null!; 30 31 [ ]32 [ ]33 public string Plot { get; set; } = null!; 34 35 } 36 37 public static class GetMovies 38 { 39 40 public static Lazy<MongoClient> lazyClient = new Lazy<MongoClient>(InitializeMongoClient); 41 public static MongoClient client = lazyClient.Value; 42 43 public static MongoClient InitializeMongoClient() 44 { 45 return new MongoClient(Environment.GetEnvironmentVariable("MONGODB_ATLAS_URI")); 46 } 47 48 [ ]49 public static async Task<IActionResult> Run( 50 [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, 51 ILogger log) 52 { 53 54 string limit = req.Query["limit"]; 55 IMongoCollection<Movie> moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); 56 57 BsonDocument filter = new BsonDocument{ 58 { 59 "year", new BsonDocument{ 60 { "$gt", 2005 }, 61 { "$lt", 2010 } 62 } 63 } 64 }; 65 66 var moviesToFind = moviesCollection.Find(filter); 67 68 if(limit != null && Int32.Parse(limit) > 0) { 69 moviesToFind.Limit(Int32.Parse(limit)); 70 } 71 72 List<Movie> movies = moviesToFind.ToList(); 73 74 return new OkObjectResult(movies); 75 76 } 77 78 } 79 80 }
There's a lot happening in the above code, but we're going to break it down so it makes sense.
Within the namespace, you'll notice we have a Movie class:
1 [ ]2 public class Movie 3 { 4 5 [ ]6 [ ]7 public string? Id { get; set; } 8 9 [ ]10 [ ]11 public string Title { get; set; } = null!; 12 13 [ ]14 [ ]15 public string Plot { get; set; } = null!; 16 17 }
The above class is meant to map our local C# objects to fields within our documents. If you're using the sample_mflix database and movies collection, these are fields from that collection. The class doesn't represent all the fields, but because the [BsonIgnoreExtraElements] is included, it doesn't matter. In this case only the present class fields will be used.
Next you'll notice some initialization logic for our database:
1 public static Lazy<MongoClient> lazyClient = new Lazy<MongoClient>(InitializeMongoClient); 2 public static MongoClient client = lazyClient.Value; 3 4 public static MongoClient InitializeMongoClient() 5 { 6 7 return new MongoClient(Environment.GetEnvironmentVariable("MONGODB_ATLAS_URI")); 8 9 }
We're using the Lazy class for lazy initialization of our database connection. This is done outside the runnable function of our class because it is not efficient to establish connections on every execution of our Azure Function. Concurrent connections to MongoDB and pretty much every database out there are finite, so if you have a large scale Azure Function, things can go poorly real quick if you're establishing a connection every time. Instead, we establish connections as needed.
Take note of the MONGODB_ATLAS_URI environment variable. We'll obtain that value soon and we'll make sure it gets exported to Azure.
This brings us to the actual logic of our Azure Function:
1 string limit = req.Query["limit"]; 2 3 IMongoCollection<Movie> moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); 4 5 BsonDocument filter = new BsonDocument{ 6 { 7 "year", new BsonDocument{ 8 { "$gt", 2005 }, 9 { "$lt", 2010 } 10 } 11 } 12 }; 13 14 var moviesToFind = moviesCollection.Find(filter); 15 16 if(limit != null && Int32.Parse(limit) > 0) { 17 moviesToFind.Limit(Int32.Parse(limit)); 18 } 19 20 List<Movie> movies = moviesToFind.ToList(); 21 22 return new OkObjectResult(movies);
In the above code we are accepting a limit variable from the client who executes the Function. It is not a requirement and doesn't need to be called limit, but it will make sense for us.
After getting a reference to the database and collection we wish to use, we define the filter for the query we wish to run. In this example we are attempting to return only documents for movies that were released between the year 2005 and 2010. We then use that filter in the Find operation.
Since we want to be able to limit our results, we check to see if limit exists and we make sure it has a value that we can work with. If it does, we use that value as our limit.
Finally we convert our result set to a List and return it. Azure hands the rest for us!
Want to test this Function locally before we deploy it? First make sure you have your Atlas URI string and set it as an environment variable on your local computer. This can be obtained through the MongoDB Atlas Dashboard.
The best place to add your environment variable for the project is within the local.settings.json file like so:
1 { 2 "IsEncrypted": false, 3 "Values": { 4 // OTHER VALUES ... 5 "MONGODB_ATLAS_URI": "mongodb+srv://<USER>:<PASS>@<CLUSTER>.170lwj0.mongodb.net/?retryWrites=true&w=majority" 6 }, 7 "ConnectionStrings": {} 8 }
The local.settings.json file doesn't get sent to Azure, but we'll handle that later.
With the environment variable set, execute the following command:
1 func start
If it ran successfully, you'll receive a URL to test with. Try adding a limit and see the results it returns.
At this point we can prepare the project to be deployed to Azure.
As mentioned previously in the tutorial, you should have the Azure CLI. We're going to use it to do various configurations within Azure.
From a command prompt, execute the following:
1 az group create --name <GROUP_NAME> --location <AZURE_REGION>
The above command will create a group. Make sure to give it a name that makes sense to you as well as a region. The name you choose for the group will be used for the next steps.
With the group created, execute the following command to create a storage account:
1 az storage account create --name <STORAGE_NAME> --location <AZURE_REGION> --resource-group <GROUP_NAME> --sku Standard_LRS
When creating the storage account, use the same group as previous and provide new information such as a name for the storage as well as a region. The storage account will be used when we attempt to deploy the Function to the Azure cloud.
The final thing we need to create is the Function within Azure. Execute the following:
1 az functionapp create --resource-group <GROUP_NAME> --consumption-plan-location <AZURE_REGION> --runtime dotnet --functions-version 4 --name <APP_NAME> --storage-account <STORAGE_NAME>
Use the regions, groups, and storage accounts from the previous commands when creating your function. In the above command we're defining the .NET runtime, one of many possible runtimes that Azure offers. In fact, if you want to see how to work with MongoDB using Node.js, check out this tutorial on the topic.
Most of the Azure cloud is now configured. We'll see the final configuration towards the end of this tutorial when it comes to our environment variable, but for now we're done. However, now we need to link the local project and cloud project in preparation for deployment.
Navigate into your project with a command prompt and execute the following command:
1 func azure functionapp fetch-app-settings <FUNCTION_APP_NAME>
The above command will download settings information from Azure into your local project. Just make sure you've chosen the correct Function name from the previous steps.
We also need to download the storage information.
From the command prompt, execute the following command:
1 func azure storage fetch-connection-string <STORAGE_ACCOUNT_NAME>
After running the above command you'll have the storage information you need from the Azure cloud.
We have a project and that project is linked to Azure. Now we can focus on the final steps for deployment.
The first thing we need to do is handle our environment variable. We can do this through the CLI or the web interface, but for the sake of quickness, let's use the CLI.
From the command prompt, execute the following:
1 az functionapp config appsettings set --name <FUNCTION_APP_NAME> --resource-group <RESOURCE_GROUP_NAME> --settings MONGODB_ATLAS_URI=<MONGODB_ATLAS_URI>
The environment variable we're sending is the MONGODB_ATLAS_URI like we saw earlier. Maybe sure you add the correct value as well as the other related information in the above command. You'd have to do this for every environment variable that you create, but luckily this project only had the one.
Finally we can do the following:
1 func azure functionapp publish <FUNCTION_APP_NAME>
The above command will publish our Azure Function. When it's done it will provide a link that you can access it from.
Don't forget to obtain a "host key" from Azure before you try to access your Function from cURL, the web browser or similar otherwise you'll likely receive an unauthorized error response.
1 curl https://<FUNCTION_APP_NAME>.azurewebsites.net/api/GetMovies?code=<HOST_KEY_HERE>
The above cURL is an example of what you can run, just swap the values to match your own.
You just saw how to create an Azure Function that communicates with MongoDB Atlas using the .NET runtime. This tutorial explored several topics which included various CLI tools, efficient database connections, and the querying of MongoDB data. This tutorial could easily be extended to do more complex tasks within MongoDB such as using aggregation pipelines as well as other basic CRUD operations.
If you're looking for something similar using the Node.js runtime, check out this other tutorial on the subject.
With MongoDB Atlas on Microsoft Azure, developers receive access to the most comprehensive, secure, scalable, and cloud–based developer data platform in the market. Now, with the availability of Atlas on the Azure Marketplace, it’s never been easier for users to start building with Atlas while streamlining procurement and billing processes. Get started today through the Atlas on Azure Marketplace listing.