Something that I always find confusing why is how .NET
(specially
ASP.NET) seems to be moving most of its API away from a type-safe
design. One of the APIs that works this way is the application
configuration. If you're not using the Option pattern, this is how you
access a configuration value:
ConnectionString = new configuration.GetValue<string>("ConnectionString");
Well, you can probably tell that this is not type-safe at all! In order to do this in a more elegant way in F#, we can leverage the FsConfig package as it has support for the Microsoft.Extensions.Configuration namespace. First, let's add the package to our project:
$ dotnet add package FsConfig
Now, let's define a Configuration
record with our structure and a
module with a helper function called init
that will do the heavy
lifting for us.
open System.IO
open FsConfig
open Microsoft.Extensions.Configuration
type Configuration =
{ SuperSecret: string }
module Configuration =
let init =
let configurationRoot =
ConfigurationBuilder()
.AddEnvironmentVariables()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json")
.Build()
let appConfig = AppConfig(configurationRoot)
appConfig.Get<Configuration>()
However, how useful would be our configuration if we couldn't access
it from within our ASP.NET application? In order to do just that, we
can register our Configuration
record as a Singleton
(check out
the service lifetime documentation) and retrieve it whenever we want
it just like any other dependency. Adding it as a service is pretty
straightforward:
let configureServices (services: IServiceCollection) =
let config =
match Configuration.init with
| Ok config -> config
| Error e -> failwithf "Error reading configuration: %A" e
services.AddSingleton<Configuration>(config) |> ignore
services.AddGiraffe() |> ignore
Consecutively, to retrieve it from, let's say, an HttpHandler, you can do the following:
{{< alert class="danger" >}} Please, never print your secrets! {{< /alert >}}
let handler : HttpHandler =
fun (next: HttpFunc) (ctx: HttpContext) ->
let config = ctx.GetService<Configuration>()
let msg = sprintf "config: %s" config.SuperSecret
text msg next ctx