loading...
Cover image for How YOU can create a Serverless API in C# and .NET
Microsoft Azure

How YOU can create a Serverless API in C# and .NET

softchris profile image Chris Noring Updated on ・13 min read

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

In this article we will go through building a Serverless function using C# and .NET. We will explain the WHY of Serverless but also learn to build, run and debug our first functions in VS Code.

If you are interested in how to author functions in JavaScript, have a look below, otherwise, please read on.

Have a look at this series if you are curious about JavaScript and Serverless

https://dev.to/azure/serverless-how-you-can-learn-serverless-authoring-functions-in-portal-vs-code-write-apis-and-more-1cno

Before we go into building our first functions let's mention what we will cover in this article:

  • Why Serverless, Asking yourself why you do what you do is critical. There are some criteria that make using a Serverless function a good choice.
  • Core concepts, Serverless consists of a set of concepts that you need to know to work with it efficiently and generally understand what's going on. The most important concepts you need to know about is called input/output bindings.
  • Building our first functions, here we will cover how to author our functions by setting up triggers, parse input either from route/query parameters or a posted Body. We will also look at how we can define our output.

Resources

 Why Serverless

Serverless is a cloud-computing execution model.

Ok, that sounds like a handful. What do we mean?

Simple, the function we write will be executed in the Cloud. This means we don't have to care about allocating a virtual machine or even run for example a web server to make the code run, that's managed for us. It also means that resource allocation is managed for us.

Sounds good, what else?

One of the primary reasons for choosing Serverless is the cost or lack thereof. Serverless is cheap. You normally only pay for when your function actually runs.

How come it's cheap?

Your function/s isn't actually there all the time, instances of them will get spun up as you need them. This means you might experience something called a cold start. This means it might take some time the first time an instance is spun up and you can start interacting with it.

I'm not sure that sounds that good, what if I want my customers to reach my function 24-7 or at least have it avoid this cold start, what then?

If you really want to avoid cold starts there is a premium plan

https://docs.microsoft.com/en-us/azure/azure-functions/functions-premium-plan

but before you go for that let's talk about what code to actually run there.

Ok?

Yea all kinds of code and business logic shouldn't run there. Only things that you do seldom should run as Serverless, like a utility function or some kind of computation.

Ok I get, I guess I have some code in my codebase that I could turn into Serverless, that don't need to run so often. I will save money doing this right?

Yes exactly, seldom run code can be made Serverless and thereby you will save a ton of money.

Is that all?

Well, there is more, serverless is usually part of a bigger picture in that it can interact with most of the cloud resources. It can be triggered by everything from Databases, to Queues to HTTP and we can also easily pass the computation result to another service in the Cloud

Wow, that sounds... really great.

 Core concepts

Ok so we mentioned part of the core concepts a bit already but let's make it more clear what those were.

We got the following concepts:

  • Trigger, this is what triggers our function to start. This could be an HTTP call, a raised event or a row entered in a database and much more
  • Input binding, an input binding is a connection to a resource, could be things like SignalR, Table Storage, CosmosDB and so on. The point is we don't have to instantiate a connection, it's pre-created and ready for us to interact with it
  • Output binding, this is simply what you can write to. You would, for example, use an input binding to have a connection to a database and read out a list of product. You would conversely use an ouput binding to create a new product entry given input data to the function

This might all sound a bit exotic bit it will be clearer as we learn to create some functions.

I recommend have a look a this link

https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings

to see the full scope of all supported input/output bindings

Building our first function

Ok, we have come to the exiting part, coding some functions. Before we start we will need to install some extensions to make our authoring experience a bit better.

Set up

We will need the following:

  • Because we will work with C# we're gonna need the extension for it, it should look like so

Let's code

We are ready to code. We will do the following:

  1. Generate an Azure Function App
  2. Create an Azure Function triggered by HTTP

Generate an Azure Function App
Our functions will need to be hosted inside of an application. An application can have several functions in it though. This is quite straight forward, simply bring up the Command Palette in VS Code by select View/Command Palette or hit the key combination COMMAND + SHIFT + P if you are on a Mac. Once you have the menu up type: Azure Functions: Create new project.

This will ask you for:

  • Directory, what directory to store your app in, select your current directory
  • Code language, it will continue to ask you for code language, select C#
  • Trigger, Then it will ask you for your first functions trigger, select HTTP Trigger
  • Function Name, After that it will ask for the function name, type Hello
  • Namespace, Then you will be asked for a namespace, For now, go with Company.Function, you can always change it later
  • Access Rights, When asked about Access Rights, select Anonymous. Security is a big thing but for this exercise we will focus on understanding parts, we can beef up the security later.

It will also ask you for the name of your first function, its language and type of trigger.

This will generate a project for you. However, starting out it will complain all in red letters that there are unresolved dependencies. You can fix this either by clicking restore in the popup that shows up right after the project is done generating or you could enter the following command in the terminal, at the project root:

dotnet restore

This will restore dependencies that was pointed out in the project and you will soon see how the code looks happy again.

The anatomy of a function

Lets have a look at the file Hello.cs that was generated when we created our app:

// Hello.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;

namespace Company.Function
{
    public static class Hello
    {
        [FunctionName("Hello")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "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");
        }
    }
}

Most of the things we need to know about Azure Functions in C# is in that piece of code. Let's go through it, from the top:

  • FunctionName, this is a decorator that takes a string argument. This is what the function will be called when we debug or in some other way try to test out our app. The string argument is Hello so we can expect the final URL for this function to be [someUrl]/Hello.
  • Run, this is an arbitrary function name, it could be called anything
  • HttpTrigger, this is a decorator decorating our HttpRequest parameter req. We start off in the decorator, setting the AuthorizationLevel to Anonymous, this means that anyone from the outside can access the function. There are other options on AuthorizationLevel that we will explore in future articles
  • HTTP Verbs list, Next is a parameter string list consisting of HTTP verbs. We can see we have the values get and post so far, which means the function will respond to those HTTP verbs. We can definitely extend the list to say "get", "post", "put", "delete" but in REST you tend to only support one verb per type of action.
  • Route pattern, Next value is Route. If we change this we can start supporting route parameters, we will show this later.

That's it for the HttpRequest parameter. Let's look at log of type ILogger. By invoking this one we are able to write to the logs. The logs will be visible locally when you debug but also in the portal, once you have deployed your app.

Ok then, let's look at the generated function body:

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");

The first line tells us how to parse out query parameters. We use the Query with a string argument to do so. So for example, if your URL looked like this url/Hello?name='some value' the variable name would hold the value some value.

Next, we read out the Body using a StreamReader, note the use of the keyword await. Note, how this is matched at the beginning of the function head with an async keyword. Next line after that is to call JsonConvert.DeserializeObject and assign it to a dynamic. On the next line, we try to read out name from our Body, providing the Body was non-null. Finally we return a 200 response with OkObjectResult or a 401 using BadRequestObjectResult.

Run the function

Next up we will run the function using VS Code debugging and try out both the GET and the POST. Go to the menu and choose Debug /Start Debugging This should compile your app and start the runtime. You should see the following when it's done:

Go to your browser and enter the indicated URL http://localhost:7071/api/hello:

The code is clearly unhappy with us, as we are not providing a query parameter or posting a body. Let's try it with a query param ?name=chris so we type http://localhost:7071/api/hello?name=chris and it now says:

All good!

Let's put a breakpoint and then reload the browser:

As you can see above the breakpoint is hit and we are able to inspect and debug our code like we are used to, good stuff :)

We clearly get the GET call to work, so what about POST? Bring up your favorite client for doing REST calls, whether that's cURL or Advanced Rest Client, that's up to you. Then create a request like so:

Then see how your breakpoint is being hit, like so:

Build a REST API

We would like to do a little bit more than just a simple function, namely to generate a REST API. We won't use a database for this but rather a static class that will hold state.

Create an entirely new directory for this and create a new Azure Function App. When asked for the name of a method choose ProductsGet and HttpTrigger

Then create the following methods using the command palette:

  • ProductCreate
  • ProductGet

To generate these methods we bring up the command palette and this time we choose Azure Functions: Create Function. Select HttpTrigger and name. At this point your project should look like this:

Now what?

We will go through each of the generated classes in order but lets first create the class that will hold our database Db.cs

Creating the database

This is just a simple class holding our data but in future articles, we will replace this with an actual database.

// Db.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace Company {
  public class Product
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }
  public static class Db {

    private static List<Product> products = new List<Product>(){
        new Product(){ Id = 1, Name= "Avengers End Game" },
        new Product(){ Id = 2, Name= "Wonder Woman" }
    };

    public static IEnumerable<Product> GetProducts() 
    {
      return products.AsEnumerable();
    }

    public static Product GetProductById(int id)
    {
      return products.Find(p => p.Id == id);
    }

    public static Product CreateProduct(string name)
    {
      var newProduct = new Product(){ Id = products.Count + 1, Name = name}; 
      products.Add(newProduct);
      return newProduct;
    }
  } 
}

As you can see above we create the type Product and the methods GetProducts, GetProductById() and CreateProduct().

List of Products

This will return a list of Products and looks pretty simple:

// ProductsGet.cs

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
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;

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

            var json = JsonConvert.SerializeObject(new{
                products = Db.GetProducts()
            });

            return (ActionResult)new OkObjectResult(json);
        }
    }
}

It's worth commenting on how we create the response:

var json = JsonConvert.SerializeObject(new{
    products = Db.GetProducts()
});

What we do here is to create a JSON response looking like this:

{
  "products": [/* list of products*/]
}

Also worth noting is how we limit the allowed HTTP verb to only get.

Get a specific Product

Now, this is about asking for a specific Product. We will do that by creating a route like this url/ProductGet/{productid}. The idea is that we find a particular product by typing for example url/ProductGet/1. Let's have a look at the implementation:

// ProductGet.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;

namespace Company.Function
{
    public static class ProductGet
    {
        [FunctionName("ProductGet")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ProductsGet/{id:int}")] HttpRequest req,
            int id,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var product = Db.GetProductById(id);
            var json = JsonConvert.SerializeObject(new {
                product = product
            });

            return (ActionResult) new OkObjectResult(json);
        }
    }
}

Specifically note how we in our HttpTrigger decorator set the value of Route to ProductsGet/{id:int}. This is a pattern meaning we will respond to request looking like url/ProductsGet/1. There is something else we parse out id from the route by simply typing int id. Then we simply filter out the matching product by the following code below and create our JSON response:

var product = Db.GetProductById(id);
var json = JsonConvert.SerializeObject(new {
    product = product
});

Create a Product

For this one, we will limit the HTTP verb to post. We will also read from the body and parse out name. The code looks like the following:

// ProductCreate.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;

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

            string name = null;

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            log.LogInformation("request body", requestBody);
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            if(name != null) 
            {
                log.LogInformation("name", name);
                var product = Db.CreateProduct(name);
                var json = JsonConvert.SerializeObject(new{
                    product = product
                });
                return (ActionResult)new OkObjectResult(json);
            } 
            else {
                return new BadRequestObjectResult("Missing name in posted Body");
            }
        }
    }
}

The interesting part here is how we parse out the Body information with this code:

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation("request body", requestBody);
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;

Other than that the code is pretty straight forward and checks if name was provided in the Body if so go ahead and create it and finally create a JSON response.

Summary

Ok, we have done some progress. We have understood the reason for choosing serverless, been able to create two different Azure Function apps, one simpler one that showcases concepts such as dealing with query parameters and body and the other example showcasing a REST API where we are close to production code.

This was the first article on Serverless and C# and hopefully, now you grasp the basics. We have only just scratched the surface. The real value lies in the Serverless frameworks ability to set up different triggers and input and output bindings and interact with a lot of other services on Azure.

Posted on by:

softchris profile

Chris Noring

@softchris

https://twitter.com/chris_noring Cloud Developer Advocate at Microsoft, Google Developer Expert

Microsoft Azure

Any language. Any platform.

Discussion

markdown guide