App.config和F# Interactive无法工作

18

在我打磨我的小项目时,我试图将所有的常量字符串(如Keys、XpathExpressions等)存储在我的app.config文件中。当我运行已编译的exe文件时,这非常有效。但是在交互式Shell中不是这样。

我尝试将.config文件从我的bin/Release目录复制到obj/Debug和obj/Release目录,但是对ConfigurationManager.AppSettings.Item("key")的调用总是返回null。

有什么建议可以解决这个问题吗?

致以最好的问候

6个回答

13

F# Interactive可以使用依赖于app.config文件的可执行文件。

实现方法是在项目中有一个.fs文件,该文件根据COMPILED定义条件加载.config文件:

let GetMyConfig() =
  let config  = 
    #if COMPILED 
      ConfigurationManager.GetSection("MyConfig") :?> MyConfig
    #else                        
      let path = __SOURCE_DIRECTORY__ + "/app.config"
      let fileMap = ConfigurationFileMap(path) 
      let config = ConfigurationManager.OpenMappedMachineConfiguration(fileMap) 
      config.GetSection("MyConfig") :?> MyConfig
    #endif

接着在你的脚本文件中引用可执行文件并 #load .fs 文件如下:

#I "../Build/Path

#r "ConfiguredApp.exe"

#load "MyConfig.fs"

执行这三行代码后,你会在 FSI 窗口中看到类似下面的消息:

[Loading C:\Svn\trunk\Source\ConfiguredApp\MyConfig.fs]

Binding session to 'C:\Svn\Qar\trunk\Build\Path\ConfiguredApp.exe'...

请注意,在 FSI 中实际上是引用了 app.config (而不是生成的 .exe.config)。

祝你好运...


我知道现在已经过去了10年以上,但是我认为这个在.NET5上(使用dotnet fsi运行)不起作用,对吗? - knocte

12

尽管FSI动态生成您的输入代码,但使用fsi.exe.config也可以正常工作。

我创建了这个文件:

<configuration>
    <appSettings>
        <add key="test" value="bar"/>
    </appSettings>
</configuration>

我将其保存为"fsi.exe.config"(程序文件\fsharp-version\bin)。

然后启动FSI:

> #r "System.configuration";;
--> Referenced 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.configuration.dll'

> System.Configuration.ConfigurationManager.AppSettings.["test"];;
val it : string = "bar"

它在Visual Studio中也可以工作。 (但请注意,您需要重置会话以获取更改。)


这对我有用。我想为WCF客户端添加“<system.serviceModel><bindings>..</bindings><client>..</client></system.ServiceModel>”。我将其添加到现有的“fsi.exe.config”中,重新启动FSI,然后我的“let client = new ServiceClient”就可以工作了 :) - Stephen Hosking
对于VS 2015,F#配置文件位于C:\Program Files (x86)\Microsoft SDKs\F#\4.0\Framework\v4.0下。 - Răzvan Flavius Panda
这种方法的真正缺点是必须将配置文件放在那个位置(对于fsx脚本,最好的解决方案是将配置文件放在与脚本相同的文件夹中,正如https://dev59.com/pGrWa4cB1Zd3GeqP_4ak中所要求的那样)。 - knocte
谢谢。对于那些试图从fsx中使用基于EntityFramework的库的人来说,只需在您的解决方案中引用该库,然后在fsi.exe.config中包含相关的连接字符串即可。 - David Miller

1
问题在于FSI是一个不同的exe,在后台运行,并使用即时编译和二进制生成进行一些疯狂的技巧。检查一下FSI认为正在运行的程序集。你可能会惊讶于你所发现的:)
它会抛出一个错误:

System.NotSupportedException: The invoked member is not supported in a dynamic assembly. at System.Reflection.Emit.AssemblyBuilder.get_Location()

你需要了解如何将app.config设置传递到动态程序集中。这可能会很麻烦,而且可能不值得。如果它作为已编译的二进制文件工作,我会测试那些依赖于配置设置的东西,而不是在FSI外部。
祝你好运。

0

也许你可以通过使用ConfigurationManager上的OpenMappedExeConfiguration方法手动将FSI指向app.config文件。

另外,您可以尝试在单独的AppDomain中加载程序集-您可以使用AppDomainSetup类创建自己的AppDomain,并将任何文件作为配置文件提供给它。

事实仍然是,FSI不适合这种情况...


0

我认为我可以提供上面两个最受欢迎的答案的最佳结合(特别是对于编写fsx脚本的人):

假设有这个app.config文件:

<configuration>
    <appSettings>
        <add key="foo" value="bar"/>
    </appSettings>
</configuration>

以这种方式阅读foo

let appConfigPath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "app.config")
let fileMap = ExeConfigurationFileMap()
fileMap.ExeConfigFilename <- appConfigPath
let config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None)
let foo = config.AppSettings.Settings.["foo"].Value
Console.WriteLine(foo)

“ExeConfigurationFileMap” 是从哪里来的? - ryanwebjackson
1
https://learn.microsoft.com/en-us/dotnet/api/system.configuration.execonfigurationfilemap?view=netframework-4.8 - knocte
很好,但这仍然不能使ConfigurationManager.AppSettings或ConfigurationManager.GetSection工作 - 任何您尝试插入的现有库仍将需要重构才能正常工作。 - Lamarth

-1

设置APP_CONFIG_FILE可以完成任务“如果足够早的话”。它似乎不能在.fsx文件中足够早地执行。因此,需要进行重置,如本帖所述: 在运行时更改默认的app.config

在F#中,它看起来像这样:

open System
open System.Configuration
open System.IO
open System.Reflection

let appConfigPath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "App.config")
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", appConfigPath)
typeof<ConfigurationManager>
    .GetField("s_initState", BindingFlags.NonPublic ||| BindingFlags.Static).SetValue(null, 0)
typeof<ConfigurationManager>
    .GetField("s_configSystem", BindingFlags.NonPublic ||| BindingFlags.Static).SetValue(null, null)
(typeof<ConfigurationManager>.Assembly.GetTypes()
    |> Array.find (fun x -> x.FullName = "System.Configuration.ClientConfigPaths"))
    .GetField("s_current", BindingFlags.NonPublic ||| BindingFlags.Static).SetValue(null, null);;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接