Is there a way to use F# record types to extract the appsettings.json configuration?
Asked Answered
C

1

10

Long story short I can extract the appsettings.json to the Configuration type when I use C#-like classes:

type Connection() =
    member val Host = Unchecked.defaultof<string> with get,set
    member val Port = Unchecked.defaultof<int> with get,set
    member val UserName = Unchecked.defaultof<string> with get,set
    member val Password = Unchecked.defaultof<string> with get,set

type Configuration() =
    member val RabbitMQ = Unchecked.defaultof<Connection> with get,set
    member val PostgreSQL = Unchecked.defaultof<Connection> with get,set

let fetchConfiguration =
        let builder = (new ConfigurationBuilder())
                          .SetBasePath(Directory.GetCurrentDirectory())
                          .AddJsonFile("appsettings.json", true, true)
                          .AddEnvironmentVariables(); 
        let configurationRoot = builder.Build();
        let configuration = new Configuration()
        configurationRoot.Bind(configuration)
        configuration

But when using F# record types:


type Connection = {
    Host: string
    Port: int32
    Username: string
    Password: string
}

type Configuration = {
    RabbitMQ: Connection
    PostgreSQL: Connection
}

let fetchConfiguration =
        let builder = (new ConfigurationBuilder())
                          .SetBasePath(Directory.GetCurrentDirectory())
                          .AddJsonFile("appsettings.json", true, true)
                          .AddEnvironmentVariables(); 
        let configurationRoot = builder.Build();
        let configuration = {
            RabbitMQ = {
                Host = Unchecked.defaultof<string>
                Port = Unchecked.defaultof<int>
                Username = Unchecked.defaultof<string>
                Password = Unchecked.defaultof<string>
            }
            PostgreSQL = {
                Host = Unchecked.defaultof<string>
                Port = Unchecked.defaultof<int>
                Username = Unchecked.defaultof<string>
                Password = Unchecked.defaultof<string>
            }
        }
        configurationRoot.Bind(configuration)
        configuration

I end up with configuration that is no different than the value I gave before calling the 'Bind' method (even though I did'nt any exception), basically it's like nothing happened.

Note: I have the same behaviour when I use the defaults below:

let configuration = {
    RabbitMQ = Unchecked.defaultof<Connection>
    PostgreSQL = Unchecked.defaultof<Connection>
}
Cephalization answered 4/6, 2019 at 11:22 Comment(6)
Using the type name Configuration to your own setting type is a bad idea and can only confuse people that try to read your code. In any case, F# records are immutable. You can't change their properties once you create them. You don't need to use Bind in any case, you can use Get<T> to retrieve a strongly typed settings class.Ream
The [<CLIMutable>] attribute on your Connection and Configuration records may help. That tells the compiler to add property setters on the members implementation so that libraries that do things like deserialization can set the members individually.Garmaise
@Panagiotis Kanavos Configuration was purposfully chosen for this SO post not in actual production code. F# records are immutable but depending on the serializer can leverage the constructor created behind the scene (in Marten it seems to work at least). Get<T> only shows that the deserializer is looking for a parameterless constructor. However that works when settings [<CLIMutable>] attribute on the record types.Cephalization
@Garmaise Kelly that works!Cephalization
@EhouarnPerret that still confused me until I realized that Configuration was a random class. Configuration is the common name given to the IConfiguration variable used in the Startup configuration methods.Ream
@Panagiotis Kanavos well for instance what I am using it's just a console app, no asp, no startupCephalization
C
10

Thanks to the comments above, the solution was simply to set the [<CLIMutable>] attribute to the F# record types:

[<CLIMutable>]
type Connection = {
    Host: string
    Port: int32
    Username: string
    Password: string
}

[<CLIMutable>]
type Configuration = {
    RabbitMQ: Connection
    PostgreSQL: Connection
}

let fetchConfiguration =
        let builder = (new ConfigurationBuilder())
                          .SetBasePath(Directory.GetCurrentDirectory())
                          .AddJsonFile("appsettings.json", true, true)
                          .AddEnvironmentVariables(); 
        let configurationRoot = builder.Build();
        configurationRoot.Get<Configuration>()
Cephalization answered 4/6, 2019 at 12:7 Comment(1)
last row should possibly be ConfigurationBinder.Get<Configuration>(configurationRoot)Jeffersonjeffery

© 2022 - 2024 — McMap. All rights reserved.