So to clarify: you have a class in a class library, the library references System.Configuration.ConfigurationManager.dll
, and the class looks does something like this:
using System.Configuration;
namespace FooLibrary
{
public class Foo
{
public Foo()
{
var bar = ConfigurationManager.AppSettings("FooLibrary.Foo.Bar");
}
}
}
Now when you call this class from a console application, say, Baz.exe
, there will exist a Baz.exe.config
:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="FooLibrary.Foo.Bar" value="Baz" />
</appSettings>
</configuration>
And all works.
It doesn't work when the library runs in a context without a configuration file. While I think it's possible to give an unmanaged application, say, Qux.exe
a configuration file Qux.exe.config
which .NET will then read for assemblies loaded from that executable, that situation isn't ideal. Even if that'd work (I think, but am not sure, that it's just a file name convention, not something the runtime does for executables on startup). It's possible that the executable running the assembly is not under your control anyway (e.g. somewhere in System32).
While you could let the library load a configuration file relative to its own location, and to answer your question:
But how can the library detect which case 1/2 its in?
You could just test for the AppSettings key anyway, and if not found, assume you've got no configuration file, and open that of the DLL instead:
public class Foo
{
private readonly string _bar;
public Foo()
{
// Read from .exe.config or Web.config
_bar = ConfigurationManager.AppSettings("FooLibrary.Foo.Bar");
if (string.IsNullOrEmpty(_bar))
{
// Assume that if not present, own config must be loaded:
var dllConfigPath = Assembly.GetExecutingAssembly().Location;
// Will just append ".config" despite its name
Configuration config = ConfigurationManager.OpenExeConfiguration(dllConfigPath);
_bar = config.AppSettings.Settings["test"]?.Value;
if (string.IsNullOrEmpty(_bar))
{
// Not found in both configs, now what?
}
}
}
}
And you should refactor it a bit to read multiple variables. And now you want to unit test this code, so you'll have to provide a configuration file there as well.
These problems are solved by not reading the configuration in class libraries, but in application startup, and passing the appropriate values to the constructor of the classes from the library:
public Foo(string bar) { ... }
// And then from your executable:
var foo = new Foo(ConfigurationManager.AppSettings["FooLibrary.Foo.Bar"));
// And your unit test
var foo = new Foo("FromTest");
But you can't pass constructor parameters through COM, which the compiler will tell you as well. So you'll have to provide a parameterless constructor, and something like an InitLib(string bar)
method for COM only.
And if you, despite all the above, still insist on reading the config there, then it would look something like this:
public class Foo
{
private string _bar;
public Foo()
{
// Read from .exe.config or Web.config
_bar = ConfigurationManager.AppSettings["FooLibrary.Foo.Bar"];
// Can't throw here if _bar is empty, maybe we're called from COM
}
public void ReadDllConfig()
{
// Assume that if not present, own config must be loaded:
var dllConfigPath = Assembly.GetExecutingAssembly().Location;
// Will just append ".config" despite its name
Configuration config = ConfigurationManager.OpenExeConfiguration(dllConfigPath);
_bar = config.AppSettings.Settings["test"]?.Value;
// Can throw here if _bar is empty
}
public void FooForCom()
{
// TODO: test _bar again, it can still be null.
}
}