United Kingdom: +44 (0)208 088 8978

Configuration with FsConfig

Ever wanted to be able to read configuration from environment variables by convention? With FsConfig, you can!

We're hiring Software Developers

Click here to find out more

Often you want the behaviour of your application to be configurable. Maybe you want to have the same executable in different environments interacting with different services, or to make a parameter tunable by the ops team without needing to redeploy your app. ASP.NET Core makes a variety of different configuration sources available, exposing them via the IConfiguration interface.

At Compositional IT, we often add type extensions to IConfiguration, to provide type-safe access to configuration settings. For example:

type Microsoft.Extensions.Configuration.IConfiguration with
    member this.AdDomain = this.["AdDomain"]

Writing that mapping by hand is pretty easy. But if you'd rather mapping was done for you by convention, the interesting library FsConfig can help you out!

Getting Started

FsConfig is available as a NuGet package. If you're using SAFE Stack, you can add it via Paket:

dotnet paket add -p Server FsConfig

Demo

I've made a small sample repo demonstrating how to use FsConfig starting from the SAFE Template. The key changes are below.

First we open the namespace, which allows us to annotate a record type declaration with the Convention attribute. FsConfig will combine this with the names of the record's fields when mapping environment variables to the fields.

open FsConfig

[<Convention("APP_NAME")>]
type Config = {
    TodoPrefix: string
    TodoSuffix: string
}

Next, we use EnvConfig.Get to read values from environment variables into a record of the new Config type.

    let config =
        match EnvConfig.Get<Config>() with
        | Ok config -> config
        | Error error ->
            match error with
            | NotFound envVarName -> failwithf "Environment variable %s not found" envVarName
            | BadValue(envVarName, value) -> failwithf "Environment variable %s has invalid value %s" envVarName value
            | NotSupported msg -> failwith msg

Because the binding is done up-front, you'll discover any invalid configuration immediately on app launch. Then, after passing the config through the various functions, we use it to add a prefix and suffix to the todos that we read from storage.

                Storage.todos
                |> Seq.map (fun t -> {
                    t with
                        Description = $"%s{config.TodoPrefix}%s{t.Description}%s{config.TodoSuffix}"
                })

All that's left to do is set the environment variables and run the app:

export APP_NAME_TODO_PREFIX='TODO: '; export APP_NAME_TODO_SUFFIX=' 💪'; dotnet run

The SAFE template app with todos augmented with a common prefix and suffix

Conclusion

That was a very quick introduction to FsConfig! We only scratched the surface, but already hints of its utility are apparent. Some other useful features that I've not shown here include:

  • Parsing strings into other types such as ints and Guids.
  • Supporting optional configuration via the option type.
  • Supporting discriminated unions and lists!
  • Setting default values for when a variable isn't present in the environment.
  • Reading from variables that don't follow the convention you've specified.
  • Reading from appSettings files.
  • And more!

Check out the README for more details.

Thanks to demystifyfp and the other contributors for a great library!