How YOU can build a Serverless C# .Net Core API in no time, with bindings and a Database

Chris Noring - Sep 13 '19 - - Dev Community

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles are published every day — that's right, every day — from community members and cloud advocates in the month of September.

Serverless is great in many ways.

Your first impression of Serverless might be that it's saving you money. You've realized that you have some code that can run seldom and you've moved that and made Azure functions out of it. Great, you're already happy at this point, who doesn't like more resources? Then you discover something else that's equally great or even greater. Because it lives in the Cloud it can take to all your services in the Cloud with almost no configuration. How come it's this easy? It's made easy cause we are using something called bindings. So let's talk about Serverless and why and how bindings are part of the success story that is Serverless

In this article we will cover:

  • Serverless, what is it, why would we want to use it?
  • Core concepts, let's cover bindings and trigger
  • Demo, let's build something using Bindings and Databases and show how easy it is

Resources

Serverless

Serverless is on everyone's lips right now. Serverless this Serverless that. Why is that?

Part of the reason is that Cloud nowadays has become the default hosting platform, no more server rooms almost. The Cloud is really someone else server room but the bottom line is that you don't have to care. There are so many things the Cloud helps with, security, reliability, backup, scalability and most of all the ability for other things that live in the Cloud to talk to each other.

So why is that great?

Most companies have a ton of services that together makes out the IT landscape they call a business. Getting all or most of these services to talk to each other is an all-consuming task or at least it requires a lot of resources. Most Clouds today are really good at connecting these services within the Cloud and even cross Cloud.

So where does Serverless fit in?

Serverless has arrived after many steps of cloud evolution in which we first decided to NOT manage the hardware anymore. Then we decided that we didn't want to deal with app servers. So we arrived at the point where we just wanted to focus on code, code, and nothing but the code. That meant a whole new platform was born or FaaS, function as a service. With the arrival of Serverless, we realized something else. We started to realize more and more we only wanted to pay for actual code execution and that's just what Serverless offers - fully managed and pay for what you use.

That's not why we read this article though?

Correct, we are here to learn about how to integrate Serverless functions with other things using something called bindings.

Core Concepts

There are three major concepts we need to understand when it comes to integration and Serverless, those are

  • Triggers, this is how our function can be invoked. What causes an invocation could be everything from an HTTP call, new database entry to a new message on a queue and even more other things. The Point is, triggers are what starts our function.
  • Input binding, this a connection to some kind of data source. The point is to read data from this data source. This could be everything from an Excel document, Database or a Queue
  • Output binding, this is us creating a connection to a database with the explicit intent of wanting to change data. Typically we get access to a record that we can populate and this record goes into the data source to typically update or create new data

There, now we know the most important concepts that we need to know to get started.

Demo

Ok so what are we building? Well, to showcase how great Serverless let's try to connect with at least two things. That means we will need to build/provision:

  • Database, we will provision a CosmosDB Database
  • Azure Function App, of course, we will need to build an Azure Function App that connects with our database

Database

This step is pretty straight forward. What we need to do is log in to the Azure Portal. You do have an account right? If not, create a free one here Free Azure account

Here are all the steps we need to take:

  1. Provision our Database
  2. Set up the database and add sample data

Provision our Database

Ok, so we have logged in to the portal. Now let's provision a database like so:

Thereafter we need to enter some information about our database. Below we have indicated all the mandatory fields we need to fill in. Ensure you select API as Core(SQL) and also ensures you have picked a Location that's close to you for best response times.

Lastly, hit Review + Create at the bottom. This should trigger provisioning. A few minutes later your resource/database should be ready for use.

Set up the database

Now our resource has been provisioned and it's ready for use to configure it. The first thing we are going to do is to add a Container.

What is a container?

Glad you asked :) A container is what actually will hold our data or entities. The container itself contains something called documents.

To create the container we select Data explorer from the left menu and then click New Container, like so:

Next step is to fill in all the details needed to create a container.

It will ask you to fill in the following fields:

  • Database id, you can give it whatever name you want, I choose to name it database-chris
  • Throughput, give it the value 1000
  • Container id, this is the name of the collection. A collection holds a list of documents. Every document is like a row in a table, with table and collection being roughly the same thing. Give it the name Bookmarks
  • Partition key, The partition key specifies how the documents in Azure Cosmos DB collections are distributed across logical data partitions. What does that even mean? It means this database scales in the Cloud. We tell it how to scale and how to use a technique used sharding to do so. Give the value /id

The form will look like this:

Add sample data

Ok now the database is set up and we can start filling it with some data. So the next step is to click our Collection bookmarks and then Items. Thereafter click Net Item to insert a record in our collection, like so:

As you can see to the right it has opened up a textarea that we can use to insert data. Let's change that to the following:

{
    "id": "docs",
    "url": "https://docs.microsoft.com/azure"
}
Enter fullscreen mode Exit fullscreen mode

and click Save. Add a few more records like so:

{
    "id": "portal",
    "url": "https://portal.azure.com"
}
Enter fullscreen mode Exit fullscreen mode

and a last one:

{
    "id": "learn",
    "url": "https://docs.microsoft.com/learn"
}
Enter fullscreen mode Exit fullscreen mode

Azure Function app

Ok, then we have a database, a data source. Now it's time for us to build an Azure Function app and an Azure Function and really demonstrate how easy it is to connect our data source. We will do the following:

  1. Scaffold an Azure Function app and an Azure Function
  2. Set up a connection to the database
  3. Read from our database and thereby learn about input bindings
  4. Write to our database and thereby learn output bindings

Prerequisites

To be able to scaffold our Azure Function app we need a few things installed namely

Ok, all setup?

Good, let's continue! :)

Scaffold an Azure Function App

Open up VS Code. Select View/Command Palette (Ctrl + Shift + P on Windows, CMD + Shift + P on Mac).

Start typing for Azure Function: Create new Project and select it.

  • Select the current folder
  • Select C# as language
  • Select HttpTrigger for our first function
  • Give it the name Bookmarks
  • You name the namespace anything you want but let's go with Company.
  • Choose the authorization type Function

A window will popup to restore dependencies. Click that or if you miss it head to the terminal and type dotnet restore. This is so it downloads all the dependent library that it has specified.

Ok then. We have gotten ourselves a function called Bookmarks, it resides in the Bookmarks.cs, like so:

Let's open up Bookmarks.cs shall we?

// Bookmarks.cs
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 System.Collections.Generic;

namespace Company
{
    public class Bookmark 
    {
        public string Id { get; set; }
        public string Url { get; set; }
    }
    public static class Bookmarks
    {
        [FunctionName("Bookmarks")]
        public static async Task<IActionResult> Run(
          [HttpTrigger(
            AuthorizationLevel.Function, 
            "get", 
            "post", 
            Route = null)
          ] HttpRequest req,
            ILogger log
            )
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

What we are most interested in is the top of our function, let's zoom in on that:

[FunctionName("Bookmarks")]
public static async Task<IActionResult> Run(
  [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
  ILogger log
) { /* function body */ }
Enter fullscreen mode Exit fullscreen mode

We can see that the attribute FunctionName is used to determine that we are dealing with a function and that it's called Bookmarks. Then we can see that we have an attribute HttpTrigger that says it can be triggered by an HTTP call.

Adding CosmosDB input binding

Ok, so how do we add CosmosDB to this? Well, there are two ways:

  1. Add the CosmosDB attribute to our Bookmarks.cs file and then install the correct NuGet package from the terminal to ensure everything compiles

  2. Add a function with a CosmosDB trigger, this will install the needed dependencies and will also trigger a storage dialog that makes you choose a storage account, the name of your database and your collection

Let's try out the first option. First, let's open up a terminal and type:

dotnet add package Microsoft.Azure.WebJobs.Extensions.CosmosDB
Enter fullscreen mode Exit fullscreen mode

Now we are ready for the next step which is to add an input binding to our Bookmarks.cs file.

To do that we will use an attribute class called CosmosDB. We need to give it some values namely

  • database name, this is what we named our database so that's database-chris
  • collection name, we named our collection Bookmarks
  • connection string, as for connection string, this is something we can find in our Azure Portal. Go to your resource click Keys in the menu and then copy the value displayed in PRIMARY CONNECTION STRING

Now, database name and collection name are things we can add in the code, like so:

// excerpt from Bookmarks.cs

[FunctionName("Bookmarks")]
public static async Task<IActionResult> Run(
  [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
  [CosmosDB("database-chris", "Bookmarks", ConnectionStringSetting ="CosmosDB")]IEnumerable<Bookmark> bookmarks
  ILogger log
) { /* function body */ }
Enter fullscreen mode Exit fullscreen mode

So above we have added the attribute class CosmosDB and decorated the parameter bookmarks. We can see that bookmarks is of type IEnumerable<Bookmarks>

where does the type Bookmark come from?

That's a type we need to create, either in Bookmarks.cs or it's own file, but it needs to have the following shape:

public class Bookmark 
{
  public string Id { get; set; }
  public string Url { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

It has the properties Id and Url to match the shape it has in the database.

Looking further at the code, we give ConnectionStringSetting the value CosmosDB

where does it come from?

It comes from a file local.settings.json and its Values property, like so:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "CosmosDB": "value of primary connection string"
  }
}
Enter fullscreen mode Exit fullscreen mode

Trying it out

Let's first set a debugger on the first row of our function in Bookmarks.cs.

In the menu of VS Code select Debug/ Start Debugging.

Go to the browser and input http://localhost:7071/Bookmarks and your breakpoint should be hit, like so:

As you can see our bookmarks variable is populated with data, we are talking to the database and it is giving us data, success :)

Limiting our response

Now the above is great, we get data back from the Database. However, we might have millions of records in there, how do we limit our response?

That's easy to do, we need to add two parameters to our CosmosDB attribute, namely:

  • Id
  • PartitionKey

It's worth learning about partition keys. So have a read here Partition keys for CosmosDB

Ok, so what we will do is to add a new function that will be able to take a query parameter from our trigger and use that to query CosmosDB. So add the following function:

[FunctionName("GetBookmark")]
public static IActionResult GetBookmark(
  [HttpTrigger(AuthorizationLevel.Function, "get", Route=null)] HttpRequest req,
  [CosmosDB(
  "database-chris",
  "Bookmarks",
  ConnectionStringSetting = "CosmosDB", 
  Id = "id",
  PartitionKey = "{Query.id}"
)]Bookmark bookmark, 
  ILogger log
)
{
  return (ActionResult) new OkObjectResult(bookmark);
}
Enter fullscreen mode Exit fullscreen mode

Now start up our application and run the following URL in the browser http://localhost:7071/api/GetBookmark?id=portal. This will ensure that our CosmosDB is queried for id with the value portal. The result should be this:

Output bindings

We used input bindings to read from a database. Output bindings are used to write to a database. Let's look at two different scenarios.

  • Create, we will take query parameters from our trigger and use those to create an entry in our database
  • Update, We will take an existing entry and update that

Create

Ok then, what's needed to create an output binding? Well, it's almost nothing. The only thing we need is a new parameter in a function of type out, like so:

out dynamic bookmark
Enter fullscreen mode Exit fullscreen mode

Let's create a new dedicated function though and call it CreateBookmark and define it like so:

[FunctionName("CreateBookmark")]
 public static IActionResult CreateBookmark(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route=null)] HttpRequest req,
        [CosmosDB("database-chris","Bookmarks",
            ConnectionStringSetting = "CosmosDB")]out dynamic bookmark,
            ILogger log
    ) {
        string id = req.Query["id"];
        string url = req.Query["url"];

        bookmark = new { id = id, url = url };

        return (ActionResult)new OkObjectResult("created");
    }
Enter fullscreen mode Exit fullscreen mode

Now fire up the program and enter a URL like so http://localhost:7071/api/CreateBookmark?id=devto&url=dev.to%2Fsoftchris in the browser. That's it, this should create new entry. Don't believe me? Have a look at your database in the portal:

Update

Well, because this is CosmosDB. This means that if there is a pre-existing record with that id value if will simply be replaced. However, that might not be what you had in mind. If you only wanted to update certain properties I recommend the following approach:

  1. Use an input binding to retrieve an existing record
  2. Copy the values from that input record to the output binding
  3. Copy whatever values you get from a trigger to the output binding

That's it, that's the two update scenarios we have.

Summary

We've learned about input bindings as well as output bindings. We learned that input bindings allowed us to read from the database and output bindings allowed us to create or update data. Additionally, we've learned how to provision a CosmosDB database and connect to it using an attribute class called CosmosDB. It's definitely possible to have CosmosDB as a trigger as well but that will be another article :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .