Serverless with OpenFaaS and .NET

By Goncalo Oliveira

Goncalo Oliveira
Image for post

Today, it’s very likely that we have, at least, heard about Serverless computing. However, the most popular options in the market are closely coupled to the platform, which can be an issue if we want to work with a different provider or multiple providers.

OpenFaaS changes that, in a sense that in can run almost anywhere; Kubernetes, Docker Swarm or even a single machine. That means you can deploy your functions with Azure, Google Cloud, Digital Ocean, bare metal or Raspberry Pi.

OpenFaaS is also very flexible when it comes to the programming language you want to use; Go, Python, Java, C# are just a few. You can even use a Dockerfile. Today we will be focusing on .NET and C#, but we’ll touch on F# support in the end.

Setting up the environment

There are a multitude of options to install OpenFaaS, including managed kubernetes clusters with cloud providers such as Azure, Google or AWS. For the sake of simplicity, today we’ll be using minikube.

Arkade

Arkade is a great tool developed by the OpenFaaS community that greatly simplifies how we install helm charts into a kubernetes cluster. We can also use it to download a variety of CLI tools, such as kubectl, helm and even minikube. We’re going to use it here.

OpenFaaS

At this point, I will assume kubectl is pointing to the minikube cluster. So, let’s install OpenFaaS with Arkade.

$ arkade install openfaas

To verify that OpenFaaS has started, we use the following command, as suggested by the installation script.

$ kubectl -n openfaas get deployments -l “release=openfaas, app=openfaas”

Next, we need to install faas-cli. We can do that with Arkade as well.

$ arkade get faas-cli

And then, as suggested by the OpenFaaS installation script, we forward the gateway. This requires the gateway to be deployed, so we can check that first.

$ kubectl rollout status -n openfaas deploy/gateway
deployment “gateway” successfully rolled out
$ kubectl port-forward -n openfaas svc/gateway 8080:8080 &
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

Next, we login with faas-cli into the OpenFaaS gateway. Again, as suggested by the installation script.

$ PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath=”{.data.basic-auth-password}” | base64 — decode; echo)$ echo -n $PASSWORD | faas-cli login — username admin — password-stdin
Calling the OpenFaaS server to validate the credentials…
Handling connection for 8080
WARNING! Communication is not secure, please consider using HTTPS. Letsencrypt.org offers free SSL/TLS certificates.
credentials saved for admin http://127.0.0.1:8080

At this point, our OpenFaaS cluster is ready to use.

Hello World

Our testing environment is ready, so… let’s do our Hello World in C#. We’ll start with the official template provided by the community.

$ faas-cli new — lang csharp hello

This creates an hello.yml YAML file and an hello folder. A quick look at the YAML file…

version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
hello:
lang: csharp
handler: ./hello
image: hello:latest

And because we need a Docker registry to publish our image, let’s change the image tag and add the required prefix. This could be a private or a public registry. In this case, I’m just adding my Docker Hub username as prefix.

functions:
hello:
lang: csharp
handler: ./hello
image: goncalooliveira/hello:latest

And a quick look at the FunctionHandler.cs inside the hello older…

using System;
using System.Text;
namespace Function
{
public class FunctionHandler
{
public string Handle(string input) {
return $"Hi there - your input was: {input}\n";
}
}
}

Now let’s publish this the quick way… The up command will build, push and deploy.

$ faas-cli up -f hello.yml
...
Deployed. 202 Accepted.
URL: http://127.0.0.1:8080/function/hello.openfaas-fn

If all goes well, we see a deployed message and the URL for the function. So, let’s try out our function.

$ curl -d 'hello' http://127.0.0.1:8080/function/hello.openfaas-fn
Handling connection for 8080
Hi there - your input was: hello

That’s it! Our first function in C#.

ASP.NET Functions

Even though the csharp template provided by the store works, it doesn’t offer much functionality or control and it uses the Classic version of the Watchdog. For higher throughput cases, the new generation Watchdog should be used. There are two more C# templates in the official template: csharp-kestrel and csharp-httprequest. The first is no longer maintained and the last hasn’t been updated in a while and even though it adds an HttpRequest for more control, it uses an awkward tuple <int,string> response.

There’s another template that isn’t yet part of the store. However, it was submitted in late 2020, so it might in the future. ASPNET Functions uses the latest version of the Watchdog, it’s built with .NET 5 and ASP.NET and offers a more recognizable environment to those used to working with ASP.NET. Furthermore, it supports Dependency Injection, Configuration, Route templates, Authentication and Authorization and… debugging.

Installation

Even though the template isn’t part of the store, it is installed faas-cli just the same.

$ faas-cli template pull https://github.com/goncalo-oliveira/faas-aspnet-template

Hello ASPNET World

Now the template is installed, we do the same as before and create a new function, this time, with the aspnet template.

$ faas-cli new --lang aspnet hello-aspnet

Looking at the created hello-aspnet.yml YAML file, it’s pretty much the same.

version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
hello-aspnet:
lang: aspnet
handler: ./hello-aspnet
image: hello-aspnet:latest

We do the same as before and change the image name, to match our public or private registry.

functions:
hello-aspnet:
lang: aspnet
handler: ./hello-aspnet
image: goncalooliveira/hello-aspnet:latest

And looking at the hello-aspnet folder, we now have two files, Function.cs and Startup.cs. We start by looking at our function code.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OpenFaaS;
using System;
using System.Threading.Tasks;
namespace OpenFaaS
{
public class Function : HttpFunction
{
[HttpGet]
[HttpPost]
public override Task<IActionResult> HandleAsync( HttpRequest request )
{
var result = new
{
Message = "Hello!"
};
return Task.FromResult<IActionResult>( Ok( result ) );
}
}
}

Straight away we see three things we recognize. The HttpRequest input, that offers full control of the request, the IActionResult response and the HTTP method attributes (GET and POST by default).

Now let’s quickly look at the Startup.cs.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace OpenFaaS
{
public class Startup
{
public Startup( IConfiguration configuration )
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services )
{
services.AddHttpFunction<Function>();
// add your services here.
}
}
}

Again, we something we recognize. An IConfiguration instance and DI setup for an IServiceCollection instance.

Let’s do the same as the first example then and publish the function.

$ faas-cli up -f hello-aspnet.yml
...
Deployed. 202 Accepted.
URL: http://127.0.0.1:8080/function/hello-aspnet.openfaas-fn

Yet again, if all goes well, we see a deployed message and the URL for the function. Let’s try it out.

$ curl http://127.0.0.1:8080/function/hello-aspnet.openfaas-fn
Handling connection for 8080
{"message":"Hello!"}

And that’s it. Our function is working.

Why should I use ASPNET Functions?

So what is it exactly that we gain with this template?

It’s updated

The template uses the latest Watchdog version and is better suited for high throughput scenarios. It also uses the latest version of .NET and ASPNET.

It’s recognizable

Since it uses ASPNET under the hood, many things are recognizable to those familiar with it. The HTTP method attributes, the IActionResult and the Ok(), NoContent() or BadRequest() response methods…

Dependency Injection

Most of us using ASPNET use Dependency Injection. The Startup.cs file allows us to configure the DI container very easily, just the same as we would with an ASPNET app.

Configuration

The ASPNET configuration model is also present. The IConfiguration instance in the Startup.cs is populated with environment variables and OpenFaaS Secrets. When running locally, it also reads a config.json file, if it exists.

Authentication and Authorization

Since it uses ASPNET under the hood, it’s possible to configure Authentication in the Startup.cs. The function class can also be decorated with the Authorize attribute, so that only authenticated requests can execute it.

namespace OpenFaaS
{
[Authorize]
public class Function : HttpFunction
{
...
}

It runs locally

The function can be executed locally with FaaS Runner. This can be useful to test, debug (with attach to process) or even to run the function on an environment without Kubernetes, Docker Swarm or faasd.

$ dotnet build hello-aspnet/
...
$ faas-run hello-aspnet/bin/Debug/netstandard2.0/function.dll
OpenFaaS ASPNET Function Loader
To debug attach to process id 95587.Loaded function assembly hello-aspnet/bin/Debug/netstandard2.0/function.dll.Running...

We can debug with VS Code

Using FaaS Runner, we can debug by attaching to the running function process. The process id is displayed on top.

We can also create a configuration in the launch.json file to do this automatically. Here’s an example retrieved from the template README.

{
"name": ".NET Core Launch (faas run)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "faas-run",
"args": ["bin/Debug/netstandard2.0/function.dll", "--no-auth"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
},

Hello F# World

We’ve done everything with C# so far, but there’s also an F# template available with ASPNET Functions. Under the hood it’s exactly the same and the functionality just the same.

So let’s do the same as before and create a new function, this time, with the aspnet-fsharp template.

$ faas-cli new --lang aspnet-fsharp hello-fsharp

Looking at the created hello-fsharp.yml YAML file, it’s pretty much the same.

version: 1.0
provider:
name: openfaas
gateway: http://127.0.0.1:8080
functions:
hello-fsharp:
lang: aspnet-fsharp
handler: ./hello-fsharp
image: hello-fsharp:latest

We do the same as before and change the image name, to match our public or private registry.

functions:
hello-fsharp:
lang: aspnet-fsharp
handler: ./hello-fsharp
image: goncalooliveira/hello-fsharp:latest

So now we look at the hello-fsharp folder. We can see that the structure is pretty much the same. Our function is in the Library.fs file and there’s also a Startup.fs file. Let’s have a look at our function then.

namespace OpenFaaSopen Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Mvc
open OpenFaaS
open System
open System.Threading.Tasks
type Function() =
inherit HttpFunction()
[<HttpGet>]
[<HttpPost>]
override this.HandleAsync( request : HttpRequest ) =
let result = {| Message = "Hello" |}
Task.FromResult( this.Ok( result ) :> IActionResult )

It’s pretty much the same, but with F#. Let’s publish it.

$ faas-cli up -f hello-fsharp.yml
...
Deployed. 202 Accepted.
URL: http://127.0.0.1:8080/function/hello-fsharp.openfaas-fn

Again, if all goes well, we see a deployed message and the URL for the function. Let’s try it out.

$ curl http://127.0.0.1:8080/function/hello-fsharp.openfaas-fn
Handling connection for 8080
{"message":"Hello!"}

And that’s it. Our F# function is working.

Final words

OpenFaaS is a great framework for serverless functions and an excellent alternative to Azure Functions or AWS Lambda. With the ASPNET Functions, we can unleash the power of .NET, C# and F# with it.

This article only touched the surface, therefore I strongly advise to have a look at the projects pages, both the OpenFaaS framework and the ASPNET functions. If you like them, give them a star.