SaturnFramework / Saturn

Opinionated, web development framework for F# which implements the server-side, functional MVC pattern
https://saturnframework.org
MIT License
708 stars 108 forks source link

Use config within application CE #223

Closed yurotor closed 4 years ago

yurotor commented 4 years ago

Currently, if i add a use_config operation to the application CE, i will be able to access the config through HttpContext in any controller. But what if i need the config data in the application itself? For example, when i use the service_config operation to integrate identity server authentication, i need to load the settings from an appSettings.json file. Am i missing something or is it not implemented?

baronfel commented 4 years ago

I agree that it should be possible to provide the current configuration at application CE usage. This is in line with the various lifecycle hooks that aspnetcore provides currently. They typically allow for DI of the IConfiguration type into the Startup type used, and our analogue would be providing a way to extract out the IConfiguration from the current app CE.

I see a couple options:

yurotor commented 4 years ago

Here is how i finally solved it:

Add a host_config operation to your app CE to read from settings file(s): host_config (fun (webHost:IWebHostBuilder) = webHost.ConfigureAppConfiguration(fun ctx builder -> builder.AddJsonFile(sprintf "appSettings.%s.json" ctx.HostingEnvironment.EnvironmentName) |> ignore ))

Access the configuration from within any function in the app CE like this: fun (services:IServiceCollection) = let config = services .BuildServiceProvider() .GetService<IConfiguration>() .GetSection("MySettingsSection") .Get<MySettings>() //do whatever needed with the config

Where the MySettings type is marked with the CLIMutable attribute. If you need to access the MySettings within HTTP requests, then first register it using the service_config operation in the app CE like this: service_config (fun (services:IServiceCollection) = let cfg = services .BuildServiceProvider() .GetService<IConfiguration>() .GetSection("MySettingsSection") services.Configure<MySettings>(cfg)

Then, add a pipe_through operation to your router CE like this: pipe_through (fun _ : HttpHandler = fun next ctx -> let cfg = ctx.RequestServices.GetRequiredService<IOptions<MySettings>>().Value //do something with the config and return an HttpHandler)

MarneeDear commented 4 years ago

Thank you. I ended up using this in a custom ApplicationBuilder method I wrote to handle CAS authentication. This made it so I didn't have to hardcode the settings. It looks like this:

    [<CustomOperation("use_cas")>]
    member __.UseCasAuthentication(state: ApplicationState, webAuthSettingKey) =
        let middleware (app : IApplicationBuilder) =
            app.UseAuthentication() 

        let service (s : IServiceCollection) =
            let config = s.BuildServiceProvider()
                            .GetService<IConfiguration>()         
                            .GetSection(webAuthSettingKey) 
            let c = s.AddAuthentication(fun cfg ->
                cfg.DefaultScheme <- CookieAuthenticationDefaults.AuthenticationScheme
                cfg.DefaultChallengeScheme <- "CAS"
                )
            c.AddCAS(fun o -> 
                o.CasServerUrlBase <- config.Value //["WebAuth:Url"]
                o.SignInScheme <- CookieAuthenticationDefaults.AuthenticationScheme
                )
            |> ignore
            s

        { state with
            ServicesConfig = service::state.ServicesConfig
            AppConfigs = middleware::state.AppConfigs
        }
Krzysztof-Cieslak commented 4 years ago

Suggestions from @yurotor are correct - in Saturn 0.14 we will add some helpers functions to make this user friendly