简体   繁体   中英

App.config and F# Interactive not working

As I'm polishing my little pet project, I'm trying to store all the constant strings in my app.config file (Keys, XpathExpressions etc). When I run the compiled exe this works great. In the Interactive Shell this isn't the case.

I tried to copy the .config file from my bin/Release directory to the obj/Debug & obj/Release dirs, but the Call to ConfigurationManager.AppSettings.Item("key") always returns null.

Any suggestions how to fix this?

With best regards

F# Interactive can work with executables that rely on app.config files.

The way to do this is to have an .fs file in your project that loads your .config conditional on the COMPILED define so:

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

then in your script file reference the executable and #load the .fs file so:

#I "../Build/Path

#r "ConfiguredApp.exe"

#load "MyConfig.fs"

On executing these three lines you will see a message similar to the following in the FSI window:

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

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

Notice that you're actually referencing the app.config when in FSI (rather than the generated .exe.config .)

Best of luck...

While FSI dynamically generates code for your input, using the fsi.exe.config will work just fine.

I created this file:

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

And saved it as "fsi.exe.config" (program files\\fsharp-version\\bin).

Then started 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"

It also worked from Visual Studio. (But note that you'll need to reset the session to pickup changes.)

The problem is that FSI is a different exe running behind the scenes and does some crazy tricks with on-the-fly compilation and generation of binaries. Check to see which assembly FSI thinks is running . You might be surprised what you find :)

It will throw an error:

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

You need to look into how to get app.config settings into dynamic assemblies. This could be a pain, and might not be worth it. If it works as a compiled binary, I'd test those things that rely on config settings outside of FSI.

Good luck.

I think I provide below the best of both worlds from the two most voted answers above (especially for people writing fsx scripts):

Given this app.config file:

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

Read foo this way:

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)

Setting APP_CONFIG_FILE does the job "if it's early enough". It doesn't appear to be able to do it early enough in the .fsx file. So it needs a reset, as per this post: Change default app.config at runtime

In F#, it looks like this:

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);;

Maybe you could point FSI to the app.config file manually by using the OpenMappedExeConfiguration method on ConfigurationManager.

Also you could try loading your assembly in a separate AppDomain - you can give any file as config file to an AppDomain you create yourself using the AppDomainSetup class.

Th fact remains that FSI isn't well suited to this kind of scenario...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM