问题
I'm trying to store connection string and credentials data in a .config
file. I can't push the config with the connection/credentials to a repo; the config will be in a secure, synced folder that isn't the home directory.
I can store the connection/credentials in the app.config
file in the home directory and access it with the FSharp.Configuration
library:
type connection = AppSettings<"app.config">
but if I try to access a config in a different directory
open System.IO
open FSharp.Configuration
let baseDirectory = __SOURCE_DIRECTORY__
let baseDirectory' = Directory.GetParent(baseDirectory)
let configPath = "Tresor\app.config"
let fullConfigPath = Path.Combine(baseDirectory'.FullName, configPath)
type Settings = AppSettings<fullConfigPath>
the fullConfigPath
errors out with
This is not a valid constant expression or custom attribute value.
Even if I try to use the yaml type provider
let yamlPath = "Tresor\Config.yaml"
let fullYamlPath = Path.Combine(baseDirectory'.FullName, yamlPath)
type Config = YamlConfig<FilePath = fullYamlPath>
I get a similar error for the fullYamlPath
.
Is there a reason I can't access files outside of the home directory? Am I constructing the file path correctly?
回答1:
Short answer: sorry, you're probably screwed, although there is a workaround using SelectExecutableFile
that might work for you.
Long answer:
This is not how type providers work.
When you use a type provider to provide a type for you, the providing of the type happens at compile time (otherwise, what would be the point?). This means that all inputs that the type provider takes also need to be known at compile time. But in your code, the value of fullConfigPath
or fullYamlPath
isn't known until Path.Combine
is executed, which will only happen at runtime.
The way it's supposed to work is, the type provider would take some "template" file (or database, or URL, or whatever it takes), which it can analyze and generate you the type from its contents. And then, later, at runtime, you would specify where to get the actual data from.
To reiterate, this all happens in two stages:
- Data shape (aka "structure" aka "schema") at compile time.
- Actual data at runtime.
This is how database providers usually work:
// Pseudocode. I don't have actual libraries handy.
type Db = SqlProvider<"Server=localhost;Database=my_development_db;Integrated Security=true">
let dbConnection = Db.OpenConnection Config.ProductionConnectionString
Theoretically, both AppSettings
and YamlConfig
providers would work somewhat similar:
type Config = AppSettings<"app.config">
let config = Config.OpenConfigFile "MyProgram.exe.config"
let someSetting = config.SomeSetting;
Unfortunately, this is not the case (for some reason).
YamlConfig
provider doesn't have any way to load an alternate config file (it will always look for the one specified at compile time). But the AppSettings
provider does give you some control via the SelectExecutableFile
method. This is a static method that you can call in order to select the source of the data once and for all. And it doesn't take the config file path either, but only the exe
file path, which it then passes to ConfigurationManager.OpenExeConfiguration:
type Config = AppSettings<"app.config">
Config.SelectExecutableFile "MyProgram.exe"
let someSetting = Config.SomeSetting;
Which makes me unsure of how it would work with a web app.
I suppose this could give a workaround: call SelectExecutableFile
and pass in the path to your config file sans the .config
extension, that should work. But you also need to create a dummy file with the same name, but without the .config
extension (which would stand in for the exe
file), because the library checks for its presence.
The bottom line is, there is no support for what you're trying to do, and it's a shame, and I suggest you file an issue about it.
来源:https://stackoverflow.com/questions/40362631/do-config-files-im-referencing-need-to-be-in-a-projects-home-directory