|
| 1 | +--- |
| 2 | +title: Introducing a new OpenFaaS template for C# and .NET 8 |
| 3 | +description: |
| 4 | +date: 2024-04-19 |
| 5 | +categories: |
| 6 | +- dotnet |
| 7 | +- functions |
| 8 | +- templates |
| 9 | +- postgres |
| 10 | +dark_background: true |
| 11 | +image: "/images/2024-04-dotnet8-csharp/background.png" |
| 12 | +author_staff_member: han |
| 13 | +hide_header_image: true |
| 14 | +--- |
| 15 | + |
| 16 | +We created a new template for C# and .NET 8.0. The template is based on the ASP.NET Core [Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-8.0). |
| 17 | + |
| 18 | +In the past we had an official csharp that used the original forking mode of the OpenFaaS watchdog, which created one process per request, and was less efficient than newer templates where one process would handle many requests concurrently. There were also a number of unofficial templates adopted by the community, which were not necessarily kept up to date, or something that we could support directly. |
| 19 | + |
| 20 | +The new template is called: `dotnet8-csharp` and has the following benefits: |
| 21 | + |
| 22 | +- Adding NuGet packages to a function for additional dependencies. |
| 23 | +- Register services for dependency injection |
| 24 | +- Using ASP.NET Core middleware |
| 25 | + |
| 26 | +In the next section we will walk through an example that show you how to develop and deploy an OpenFaaS function with C# and the new template. |
| 27 | + |
| 28 | +## Prerequisites |
| 29 | + |
| 30 | +We won't go into detail on how to deploy OpenFaaS and assume you are already running OpenFaaS on Kubernetes or on a VM with [faasd](https://github.com/openfaas/faasd). Check out the [deployment guide](https://docs.openfaas.com/deployment/) for more information. |
| 31 | +Make sure you have the [faas-cli](https://github.com/openfaas/faas-cli) and docker installed to build and deploy functions. |
| 32 | + |
| 33 | +## Tutorial: Query a Postgres database |
| 34 | + |
| 35 | +In this section we will walk through an example showing how to create a function that queries a Postgres database. |
| 36 | + |
| 37 | +We will assume you are already running a Postgres database somewhere. You can use one of the many DBaaS services available, run a postgres with docker or use [arkade](https://github.com/alexellis/arkade) to quickly deploy a database in your cluster. If you are running faasd, the official guide [Serverless For Everyone Else](https://openfaas.gumroad.com/l/serverless-for-everyone-else) has a chapter that shows how to deploy PostgreSQL as an additional service. |
| 38 | + |
| 39 | +To quickly deploy PostgreSQL in your Kubernetes cluster run `arkade install postgresql`. After the installation it will print out all the instructions to get the password and connect to the database. |
| 40 | + |
| 41 | +We will create a table and insert some records that can be queried by our function: |
| 42 | + |
| 43 | +```sql |
| 44 | +CREATE TABLE IF NOT EXISTS employee |
| 45 | +( |
| 46 | + id INT PRIMARY KEY NOT NULL, |
| 47 | + name TEXT NOT NULL, |
| 48 | + email TEXT NOT NULL |
| 49 | +); |
| 50 | + |
| 51 | +INSERT INTO employee (id,name,email) VALUES |
| 52 | +(1,'Alice','alice@example.com'), |
| 53 | +(2,'Bob','bob@example.com'); |
| 54 | +``` |
| 55 | + |
| 56 | +Create a new OpenFaaS function using the `dotnet8-csharp` template. This template is available in the OpenFaaS template store. |
| 57 | + |
| 58 | +```bash |
| 59 | +faas-cli template store pull dotnet8-csharp |
| 60 | +faas-cli new --lang dotnet8-csharp \ |
| 61 | + employee-api |
| 62 | +mv employee-api.yml stack.yml |
| 63 | +``` |
| 64 | + |
| 65 | +```c# |
| 66 | +using Microsoft.AspNetCore.Builder; |
| 67 | +using Microsoft.AspNetCore.Http; |
| 68 | +using Microsoft.Extensions.DependencyInjection; |
| 69 | +using Npgsql; |
| 70 | + |
| 71 | +namespace function; |
| 72 | + |
| 73 | +public static class Handler |
| 74 | +{ |
| 75 | + // MapEndpoints is used to register WebApplication |
| 76 | + // HTTP handlers for various paths and HTTP methods. |
| 77 | + public static void MapEndpoints(WebApplication app) |
| 78 | + { |
| 79 | + var connectionString = File.ReadAllText("/var/openfaas/secrets/pg-connection"); |
| 80 | + var dataSource = NpgsqlDataSource.Create(connectionString); |
| 81 | + |
| 82 | + app.MapGet("/employees", async () => |
| 83 | + { |
| 84 | + var employees = new List<Employee>(); |
| 85 | + await using (var cmd = dataSource.CreateCommand("SELECT id, name, email FROM employee")) |
| 86 | + await using (var reader = await cmd.ExecuteReaderAsync()) |
| 87 | + { |
| 88 | + while (await reader.ReadAsync()) |
| 89 | + { |
| 90 | + employees.Add(new Employee{ |
| 91 | + Id = (int)reader["id"], |
| 92 | + Name = reader.GetString(1), |
| 93 | + Email = reader.GetString(2) |
| 94 | + }); |
| 95 | + } |
| 96 | + } |
| 97 | + return Results.Ok(employees); |
| 98 | + }); |
| 99 | + } |
| 100 | + |
| 101 | + // MapServices can be used to configure additional |
| 102 | + // WebApplication services |
| 103 | + public static void MapServices(IServiceCollection services) |
| 104 | + { |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +public class Employee { |
| 109 | + public int Id { get; set; } |
| 110 | + public string? Name { get; set; } |
| 111 | + public string? Email { get; set; } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +In this example we use [Npgsql](https://www.npgsql.org/) to query a PostgreSQL database. The NuGet package reference for Npgsql should be added the `function.csproj` file for the `employee-api` function. You can use the dotnet CLI for this: |
| 116 | + |
| 117 | +```bash |
| 118 | +dotnet add employee-api package Npgsql --version 8.0.2 |
| 119 | +``` |
| 120 | + |
| 121 | +In this example we use Npgsql directly but you could also use [Dapper](https://github.com/DapperLib/Dapper) for less manual object mapping or even [Entity Framework Core](https://learn.microsoft.com/en-us/ef/). The `MapServices` method can be used to register a database context. |
| 122 | + |
| 123 | +The OpenFaaS philosophy is that environment variables should be used for non-confidential configuration values only, and not to inject secrets. That's why we encourage users to use the secrets functionality built into OpenFaaS. See [OpenFaaS secrets](https://docs.openfaas.com/reference/secrets/) for more information. |
| 124 | + |
| 125 | +Save your database connection string in a file `pg-connection` in the `.secrets` directory. By storing secrets in this directory they can be picked up by `faas-cli local-run` which is can be used to run and test functions locally. |
| 126 | + |
| 127 | +> See: [The faster way to iterate on your OpenFaaS functions](https://www.openfaas.com/blog/develop-functions-locally/) |
| 128 | +
|
| 129 | +The connection string for Postgres should be formatted like this: |
| 130 | + |
| 131 | +``` |
| 132 | +Host=postgresql;Username=postgres;Password=mysecretpassword;Database=postgres |
| 133 | +``` |
| 134 | + |
| 135 | +Before you deploy the function to OpenFaaS make sure the secret exists. This can be done with the faas-cli: |
| 136 | + |
| 137 | +```bash |
| 138 | +faas-cli secret create pg-connection \ |
| 139 | + --from-file .secrets/pg-connection |
| 140 | +``` |
| 141 | + |
| 142 | +Update the `stack.yml` file: |
| 143 | + |
| 144 | +```yaml |
| 145 | +version: 1.0 |
| 146 | +provider: |
| 147 | + name: openfaas |
| 148 | + gateway: http://127.0.0.1:8080 |
| 149 | +functions: |
| 150 | + employee-api: |
| 151 | + lang: dotnet8-csharp |
| 152 | + handler: ./employee-api |
| 153 | + image: ttl.sh/employee-api:latest |
| 154 | + secrets: |
| 155 | + - pg-connection |
| 156 | +``` |
| 157 | +
|
| 158 | +Now we can deploy to Kubernetes using OpenFaaS and faas-cli. |
| 159 | +
|
| 160 | +Before you deploy the function make sure the secret exists in your OpenFaaS cluster. This can be done with the faas-cli: |
| 161 | +
|
| 162 | +```bash |
| 163 | +faas-cli secret create pg-connection \ |
| 164 | + --from-file .secrets/pg-connection |
| 165 | +``` |
| 166 | + |
| 167 | +Next use `faas-cli up` to build and deploy the function. |
| 168 | + |
| 169 | +``` |
| 170 | +export OPENFAAS_URL="" # Set a remote cluster if you have one available |
| 171 | +
|
| 172 | +faas-cli up |
| 173 | +``` |
| 174 | + |
| 175 | +Invoking the function should return a json response that looks like this: |
| 176 | + |
| 177 | +``` |
| 178 | +$ curl -i $OPENFAAS_URL/function/employee-api/employees |
| 179 | +
|
| 180 | +HTTP/1.1 200 OK |
| 181 | +Content-Length: 101 |
| 182 | +Content-Type: application/json |
| 183 | +Date: Wed, 17 Apr 2024 10:30:57 GMT |
| 184 | +Server: Kestrel |
| 185 | +X-Call-Id: 49e750b6-d813-4139-9d4e-96adcf79d596 |
| 186 | +X-Duration-Seconds: 0.173345 |
| 187 | +X-Start-Time: 1669895320281999527 |
| 188 | +
|
| 189 | +[ |
| 190 | + { |
| 191 | + "id": 1, |
| 192 | + "name": Slice", |
| 193 | + "email": "alice@example.com" |
| 194 | + }, |
| 195 | + { |
| 196 | + "id": 2, |
| 197 | + "name": "Bob", |
| 198 | + "email": "bob@example.com" |
| 199 | + } |
| 200 | +] |
| 201 | +``` |
| 202 | + |
| 203 | +## Dependency Injection |
| 204 | + |
| 205 | +The `MapServices` method in the `Handler` class can be used to register additional services to the dependency injection container. |
| 206 | + |
| 207 | +In this code snippet we register a new database context to query employees using Entity Framework: |
| 208 | + |
| 209 | +```c# |
| 210 | +public static class Handler |
| 211 | +{ |
| 212 | + // MapEndpoints is used to register WebApplication |
| 213 | + // HTTP handlers for various paths and HTTP methods. |
| 214 | + public static void MapEndpoints(WebApplication app) |
| 215 | + { |
| 216 | + app.MapGet("/employees", async (EmployeeDb db) => |
| 217 | + await db.Employees.ToListAsync()); |
| 218 | + } |
| 219 | + |
| 220 | + // MapServices can be used to configure additional |
| 221 | + // WebApplication services |
| 222 | + public static void MapServices(IServiceCollection services) |
| 223 | + { |
| 224 | + var connectionString = File.ReadAllText("/var/openfaas/secrets/pg-connection"); |
| 225 | + |
| 226 | + services.AddDbContext<EmployeeDb>( |
| 227 | + optionsBuilder => optionsBuilder.UseNpgsql(connectionString) |
| 228 | + ); |
| 229 | + } |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +## Add Middleware |
| 234 | + |
| 235 | +The MapEndpoints methods gives you access the the `WebApplication` class. This makes it possible to add any existing ASP.NET Core middleware from the function. |
| 236 | + |
| 237 | +```c# |
| 238 | +public static class Handler |
| 239 | +{ |
| 240 | + // MapEndpoints is used to register WebApplication |
| 241 | + // HTTP handlers for various paths and HTTP methods. |
| 242 | + public static void MapEndpoints(WebApplication app) |
| 243 | + { |
| 244 | + // Setup the file server to serve static files. |
| 245 | + app.UseFileServer(); |
| 246 | + |
| 247 | + app.MapGet("/", () => "Hello from OpenFaaS."); |
| 248 | + } |
| 249 | + |
| 250 | + // MapServices can be used to configure additional |
| 251 | + // WebApplication services |
| 252 | + public static void MapServices(IServiceCollection services) |
| 253 | + { |
| 254 | + } |
| 255 | +} |
| 256 | + |
| 257 | +``` |
| 258 | + |
| 259 | +For more information, see: [ASP.NET Core Middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0) |
| 260 | + |
| 261 | +## Conclusion |
| 262 | + |
| 263 | +We walked through a short example to show you how to develop and deploy OpenFaaS function with C#. The new official `dotnet8-csharp` template allows you to quickly develop simple functions or a full featured API. It is based on the APS.NET Core [Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-8.0) style and has full support for dependency injection and other ASP.NET core middleware. |
| 264 | + |
| 265 | +If you already have an existing MVC API or microservice there is no need to rewrite your entire app. You can easily deploy an existing .NET app to OpenFaaS and benefit from all the OpenFaaS abstractions as long as it conforms to the [OpenFaaS workload](https://docs.openfaas.com/reference/workloads/). OpenFaaS makes deploying your apps a much simpler task than it would have been if you tried to program directly against Kubernetes. For a full overview see our blog post: [Build ASP.NET Core APIs with Kubernetes and OpenFaaS](https://www.openfaas.com/blog/asp-net-core/) |
0 commit comments