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
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
Sign up for a free Azure account
To create Serverless Azure Functions you will need a free Azure accountCreate your first Azure Function in VSCode
A great first blog post that will show you function creation as well as deployUsing C# for Azure Functions
A C# reference for Azure FunctionsInstall Azure Functions Core Tools
You will need Azure Functions Core Tools to be able to RUN and DEBUG Azure Functions from VS CodeAzure Functions docs overview
You can also create Azure Functions using Azure CLI and the Portal and moreAzure CLI Install
The Azure CLI is an amazing way to manage your Cloud resources from the terminal, usually much quicker than using the Portal.https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings
A complete listing of all the trigger, input, and output bindings
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:
- Azure Functions Core Tools
- Azure Functions Extension for VS Code, search for
Azure Functions
. It should look like this:
- 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:
- Generate an Azure Function App
- 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 theAuthorizationLevel
toAnonymous
, this means that anyone from the outside can access the function. There are other options onAuthorizationLevel
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
andpost
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.
Top comments (3)
Nice 😄, In
Paas
likeApp Service
, no mater your app is used or not you have to pay for hosting. But In Server less the hosting is free and we pay only for consuming theAzure functions
. So how can cloud providers provide free hosting for server less ? What they will do if all businesses go server less ? Then How can they make profit ?Serverless is a very specific model. Pay only for when code actually need to run. Example, some code needs to run all the time, should be on app service.. Some code can run seldom, for example, a new customer signed up. Serverless is more expensive if the code you make serverless runs all the time.. So Serverless is niche case, you should not turn all your code serverless, just code that can run seldom
Great use case. Now I understand that all code cant be made serverless. Thank you.