Build web service using F# and ASP.NET Core

The aim of following article is to build web service using F# and ASP.NET Core using the Giraffe library. Therefore, we’ll build some JSON APIs which takes data from a data source, and exposes them through HTTP.

Before start, I invite you to read this article about Web development in F#.

You can find the following demo on github: https://github.com/samueleresca/Blog.FSharpOnWeb

Setup the project

Setup a Giraffe project is very simple. First of all, let’s start by adding giraffe projects template:

dotnet new -i "giraffe-template::*"

The previous command install some project templates in order to build Giraffe web applications. Afterwards you can create a new Giraffe application by running dotnet new giraffe.

The command will create a project with some files inside it. The most important is Program.fs file, which contains the setup of the host, the routing engine and services:

module Blog.FSharpWebAPI.App
open System
open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Cors.Infrastructure
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open Giraffe
// ---------------------------------
// Models
// ---------------------------------
type Message =
{
Text : string
}
// ---------------------------------
// Views
// ---------------------------------
module Views =
open GiraffeViewEngine
let layout (content: XmlNode list) =
html [] [
head [] [
title [] [ encodedText "Blog.FSharpWebAPI" ]
link [ _rel "stylesheet"
_type "text/css"
_href "/main.css" ]
]
body [] content
]
let partial () =
h1 [] [ encodedText "Blog.FSharpWebAPI" ]
let index (model : Message) =
[
partial()
p [] [ encodedText model.Text ]
] |> layout
// ---------------------------------
// Web app
// ---------------------------------
let indexHandler (name : string) =
let greetings = sprintf "Hello %s, from Giraffe!" name
let model = { Text = greetings }
let view = Views.index model
htmlView view
let webApp =
choose [
GET >=>
choose [
route "/" >=> indexHandler "world"
routef "/hello/%s" indexHandler
route "/hello-json" >=> json [| "Hello,"; "use"; "JSON!" |]
]
setStatusCode 404 >=> text "Not Found" ]
// ---------------------------------
// Error handler
// ---------------------------------
let errorHandler (ex : Exception) (logger : ILogger) =
logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
clearResponse >=> setStatusCode 500 >=> text ex.Message
// ---------------------------------
// Config and Main
// ---------------------------------
let configureCors (builder : CorsPolicyBuilder) =
builder.WithOrigins("http://localhost:8080")
.AllowAnyMethod()
.AllowAnyHeader()
|> ignore
let configureApp (app : IApplicationBuilder) =
let env = app.ApplicationServices.GetService<IHostingEnvironment>()
(match env.IsDevelopment() with
| true -> app.UseDeveloperExceptionPage()
| false -> app.UseGiraffeErrorHandler errorHandler)
.UseCors(configureCors)
.UseStaticFiles()
.UseGiraffe(webApp)
let configureServices (services : IServiceCollection) =
services.AddCors() |> ignore
services.AddGiraffe() |> ignore
let configureLogging (builder : ILoggingBuilder) =
let filter (l : LogLevel) = l.Equals LogLevel.Error
builder.AddFilter(filter).AddConsole().AddDebug() |> ignore
[<EntryPoint>]
let main _ =
let contentRoot = Directory.GetCurrentDirectory()
let webRoot = Path.Combine(contentRoot, "WebRoot")
WebHostBuilder()
.UseKestrel()
.UseContentRoot(contentRoot)
.UseIISIntegration()
.UseWebRoot(webRoot)
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
.Build()
.Run()
0
view raw Program.fs hosted with ❤ by GitHub

Project structure

As ASP.NET MVC developer, I am used to follow an naming-convention structure in order to keep separated all the components of my application: Views, Models, Startup, Routing. Giraffe simply keeps all these implementations inside the Program.fs file.

Let’s create a more structured project. In that case, I will proceed with the following structure:

  • Model.fs contains all the model which will reflect the data source structure;
  • DataAccess.fs defines the db context and it implements all abstractions over data manipulation in order to access to our data source;
  • RequestModels.fs implements types which define the DTOs models;
  • Handlers.fs handler accepts the HttpHandler type and retrieve informations from DataAccess.fs functions;
  • Program.fs it is very similar to Startup.cs file into an ASP.NET Core project: it defines services, web host and routing;

Access data through Entity framework core

EF Core is the “official” ORM shipped with ASP.NET Core. I have already talk about EF Core in the following articles: Implementing SOLID REST API using ASP.NET Core,  Developing token authentication using ASP.NET Core. Obviously, EF Core is just an option, you may replace it with your favourite implementation or ORM.

EF Core APIs are the same between C# and F#, therefore it will force you to fit your F# code to C# APIs.

In order to proceed, we need a model which will be contained into Model.fs and a data context, which will be implemented into DataAccess.fs. Let’s start by defining the model:

module Blog.FSharpWebAPI.Models
// ---------------------------------
// Models
// ---------------------------------
[<CLIMutable>]
type Label =
{
Id : int
Code: string
IsoCode: string
Content: string
Inactive: bool
}

All the models needs to expose the [<CLIMutable>] attribute, which allows EF Core to threat them as mutable object only @ compiler level.

The DataContext.fs file will contain the LabelsContext. It will extend the base DbContext and it will define all the DbSet used by repository module:

module Blog.FSharpWebAPI.DataAccess
open Blog.FSharpWebAPI.Models
open Microsoft.EntityFrameworkCore
type LabelsContext(options: DbContextOptions<LabelsContext>) =
inherit DbContext(options)
override __.OnModelCreating modelBuilder =
let expr = modelBuilder.Entity<Label>().HasKey(fun label -> (label.Id) :> obj)
modelBuilder.Entity<Label>().Property(fun label -> label.Id).ValueGeneratedOnAdd() |> ignore
[<DefaultValue>]
val mutable labels:DbSet<Label>
member x.Labels
with get() = x.labels
and set v = x.labels <- v
module LabelsRepository =
let getAll (context : LabelsContext) = context.Labels
let getLabel (context : LabelsContext) id = context.Labels |> Seq.tryFind (fun f -> f.Id = id)
let addLabelAsync (context : LabelsContext) (entity : Label) =
async {
context.Labels.AddAsync(entity)
|> Async.AwaitTask
|> ignore
let! result = context.SaveChangesAsync true |> Async.AwaitTask
let result = if result >= 1 then Some(entity) else None
return result
}
let updateLabel (context : LabelsContext) (entity : Label) (id : int) =
let current = context.Labels.Find(id)
let updated = { entity with Id = id }
context.Entry(current).CurrentValues.SetValues(updated)
if context.SaveChanges true >= 1 then Some(updated) else None
let deleteLabel (context : LabelsContext) (id : int) =
let current = context.Labels.Find(id)
let deleted = { current with Inactive = true }
updateLabel context deleted id
let getAll = LabelsRepository.getAll
let getLabel = LabelsRepository.getLabel
let addLabelAsync = LabelsRepository.addLabelAsync
let updateLabel = LabelsRepository.updateLabel
let deleteLabel = LabelsRepository.deleteLabel

Finally, we will define the LabelRepository module in order to perform data manipulation. Each method will return an Option<'T> type in order to handle data source exception case.

Getting started with HttpHandler

Let’s start with some fundamentals of Giraffe framework. The main building block in Giraffe is a so called HttpHandler:

type HttpFuncResult = Task<HttpContext option>
type HttpFunc = HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpContext -> HttpFuncResult
view raw HttpHandler.fs hosted with ❤ by GitHub

HttpHandler is a function which takes two  arguments:  HttpFunc,   HttpContext, and it returns a HttpContext (wrapped in an option and Task workflow) when finished. On a high level a HttpHandler function receives and returns an ASP.NET Core HttpContext object.

HttpHandler can process an incoming HttpRequest before passing it further down the Giraffe pipeline by invoking the next HttpFunc or short circuit the execution by returning an option of Some HttpContext.

Each handler is mapped to an URL through the route configuration of Giraffe.

Let’s see a concrete example inside our implementation. The following routing objects shows a mapping between some urls: /label, /label/{id} and some handlers: labelsHandler, labelHandler:

let webApp =
choose [
GET >=>
choose [
route "/" >=> indexHandler "world"
route "/label" >=> labelsHandler
routef "/label/%i" labelHandler
]
POST >=> route "/label" >=> labelAddHandler
setStatusCode 404 >=> text "Not Found" ]

The /label url will retrieve all the labels from database, and the /label/%i will retrieve a label by id. Handler.fs file contains implementations of handlers:

module Blog.FSharpWebAPI.Handlers
open Giraffe;
open Microsoft.AspNetCore.Http
open Blog.FSharpWebAPI.Models
open CompostionRoot
let labelsHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
getAll |> ctx.WriteJsonAsync
let labelHandler(id: int) =
fun (next : HttpFunc) (ctx : HttpContext) ->
getLabel id
|> function
| Some l -> ctx.WriteJsonAsync l
| None -> (setStatusCode 400 >=> json "label not found") next ctx

Manage CRUD operations using HttpHandler

Handler functions are the bridge between the http client and our application. As seen before, the Handlers.fs file implements the labelsHandler methods, which retrieve informations from our datasource.

Let’s continue by implementing all CRUD operations in our Handlers.fs:

module Blog.FSharpWebAPI.Handlers
open Blog.FSharpWebAPI.DataAccess
open Blog.FSharpWebAPI.RequestModels
open Giraffe
open Microsoft.AspNetCore.Http
let labelsHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
getAll context |> ctx.WriteJsonAsync
let labelHandler (id : int) =
fun (next : HttpFunc) (ctx : HttpContext) ->
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
getLabel context id |> function
| Some l -> ctx.WriteJsonAsync l
| None -> (setStatusCode 404 >=> json "Label not found") next ctx
let labelAddHandler : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
let! label = ctx.BindJsonAsync<CreateUpdateLabelRequest>()
match label.HasErrors with
| Some msg -> return! (setStatusCode 400 >=> json msg) next ctx
| None ->
return! addLabelAsync context label.GetLabel
|> Async.RunSynchronously
|> function
| Some l -> Successful.CREATED l next ctx
| None -> (setStatusCode 400 >=> json "Label not added") next ctx
}
let labelUpdateHandler (id : int) =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
let! label = ctx.BindJsonAsync<CreateUpdateLabelRequest>()
match label.HasErrors with
| Some msg -> return! (setStatusCode 400 >=> json msg) next ctx
| None ->
return! updateLabel context label.GetLabel id |> function
| Some l -> ctx.WriteJsonAsync l
| None -> (setStatusCode 400 >=> json "Label not updated") next ctx
}
let labelDeleteHandler (id : int) =
fun (next : HttpFunc) (ctx : HttpContext) ->
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
deleteLabel context id |> function
| Some l -> ctx.WriteJsonAsync l
| None -> (setStatusCode 404 >=> json "Label not deleted") next ctx

labelAddHandler and labelUpdateHandler implement respectively the add operation and update operation. Each of them retrieve the LabelContext by using the GetService method.

GetService method is part of the dependency management system of ASP.NET Core, which works out of the box with Giraffe.

The LabelContext services, and in general, all services are defined into the Program.fs file:

let configureServices (services : IServiceCollection) =
services.AddDbContext<LabelsContext>
(fun (options : DbContextOptionsBuilder) ->
options.UseSqlServer
(@"Server=localhost;Database=ContentDataDB2;User Id=<user_id>;Password=<password>;")
|> ignore) |> ignore
services.AddCors() |> ignore
services.AddGiraffe() |> ignore

In that case, we are configuring a new DbContext<LabelsContext> by passing the connection string.

Model validation

Giraffe also implements out-of-box a model validation system. The validation criteria can be defined into models. Indeed, in our example, we add an additional level by defining the RequestModels.fs file.

Here is the implementation of RequestModels.fs:

module Blog.FSharpWebAPI.RequestModels
open Blog.FSharpWebAPI.Models
type CreateUpdateLabelRequest =
{
Code: string
IsoCode: string
Content: string
Inactive: bool
}
member this.HasErrors =
if this.Code = null || this.Code = "" then Some "Code is required"
else if this.Code.Length > 255 then Some "Code is too long"
else if this.IsoCode.Length > 2 then Some "IsoCode is too long"
else None
member this.GetLabel = {
Id= 0;
Code = this.Code;
IsoCode = this.IsoCode;
Content = this.Content;
Inactive= this.Inactive
}

The HasErrors method can be used into handlers in order to check the consistency of the request and provide some feedback to the client:

let labelUpdateHandler (id : int) =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let context = ctx.RequestServices.GetService(typeof<LabelsContext>) :?> LabelsContext
let! label = ctx.BindJsonAsync<CreateUpdateLabelRequest>()
match label.HasErrors with
| Some msg -> return! (setStatusCode 400 >=> json msg) next ctx
| None ->
return! updateLabel context label.GetLabel id |> function
| Some l -> ctx.WriteJsonAsync l
| None -> (setStatusCode 400 >=> json "Label not updated") next ctx
}

Final thought

Finally, we can perform operations over data by using the following calls

GET /label HTTP/1.1
Host: localhost:5000
Content: application/json
POST /label HTTP/1.1
Host: localhost:5000
Content: application/json
{
"code":"test",
"isoCode":"IT",
"content":"Test Content",
"inactive":false
}
PUT /label/1 HTTP/1.1
Host: localhost:5000
Content: application/json
{
"code":"test",
"isoCode":"EN",
"content":"Content Updated",
"inactive":false
}
view raw calls.sh hosted with ❤ by GitHub

This example shows you some how to build build web service using F# and ASP.NET Core, focusing on some features provided by Giraffe framework, and it is a good deep dive into web functional world for all software engineers who comes from an OOP approach.

This article is NOT an invitation to move all your code to FP paradigm. If you think different approaches, like FP and OOP, are mutually exclusive, then I invite you to read this article: OOP vs FP.

The example is available on github.

Cover photo credits: Graz – Mur Island

Cheers 🙂