Merging custom configuration sections at runtime in .NET
There are actually 3 levels of configuration inheritance by default: Machine, Exe and User (which can be Roaming or Local). If you're loading the configuration files yourself you can use the ExeConfigurationFileMap class in combination with ConfigurationManager.OpenMappedExeConfiguration to load your own custom configuration hierarchy.
I don't think you can change where the default paths are for the ConfigurationManager class with this, but you will get a Configuration element which can be used to get whatever sections from the loaded config hierarchy.
If you check out the answer to How to read configSections it includes some notes on determining at what level sections were declared in the hierarchy (using SectionInformation)
var localSections = cfg.Sections.Cast<ConfigurationSection>()
.Where(s => s.SectionInformation.IsDeclared);
The .NET configuration system is not super flexible
That comment nails it and explains why you've been looking for a long time and haven't found anything yet. Not all the .NET Framework parts are "good", System.Configuration deserves the spot on the far bottom. It is ridiculously over-engineered for something that's ultimately a simple task but at the same time turned into something exceedingly inflexible. Hard to reverse-engineer how this happened, I think it got paralyzed by security concerns. Somewhat understandable perhaps, commandeering a program with data is always a considerable risk.
The only extension point that I know about is writing your own SettingsProvider. The framework has only one for general usage, the LocalFileSettingProvider class. Also exceedingly inflexible, there isn't any way to alter its behavior. There is a decent example available for a custom setting provider, the RegistrySettingsProvider sample demonstrates a provider that stores settings in the registry. It can be a good starting point for writing your own.
Not exactly what you have in mind perhaps, do scratch the idea that you can break into the layering inside System.Configuration.
As silver has pointed out, a well configured ExeConfigurationFileMap
could do the job, at a certain cost.
I have taken his example and made a working version of it.
Here are the two config files I have merged for my test purposes:
custom.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="custom" type="..." />
</configSections>
<custom>
<singleProperty id="main" value="BaseValue" />
<propertyCollection>
<property id="1" value="One" />
<property id="4" value="Four" />
</propertyCollection>
</custom>
</configuration>
app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="custom" type="..."/>
</configSections>
<custom>
<singleProperty id="main" value="OverriddenValue" />
<propertyCollection>
<property id="1" value="OverridenOne" />
<property id="2" value="Two" />
<property id="3" value="Three" />
</propertyCollection>
</custom>
</configuration>
And I used the following code to test the merged setup:
var map = new ExeConfigurationFileMap();
map.MachineConfigFilename = PathToCustomConfig;
map.ExeConfigFilename = PathToAppConfig;
var configuration = ConfigurationManager.OpenMappedExeConfiguration(
map,
ConfigurationUserLevel.None);
var section = configuration.GetSection("custom") as CustomConfigSection;
Assert.IsNotNull(section);
Assert.AreEqual(section.SingleProperty.Value, "OverriddenValue");
Assert.AreEqual(section.PropertyCollection.Count, 4);
// Needed to map the properties as dictionary, not to rely on the property order
var values = section.PropertyCollection
.Cast<SimpleConfigElement>()
.ToDictionary(x => x.ID, x => x.Value);
Assert.AreEqual(values["1"], "OverridenOne");
Assert.AreEqual(values["2"], "Two");
Assert.AreEqual(values["3"], "Three");
Assert.AreEqual(values["4"], "Four");
pros of this approach
- I get the built-in merging logic to work
- Works for older versions of .NET (tested on 3.5)
- No need for reflection or other black magic stuff to trigger the behavior.
cons
- Not very sure, but by setting
map.MachineConfigFilename = PathToCustomConfig;
I assume I am removing any values that are set up by the realmachine.config
file. This could be error-prone and should be avoided for web applications, as most of them rely on what's in the realmachine.config
- One needs to pass the location of the application configuration file, as it is no longer determined automatically. Thus one needs to figure out how the
app.config
will be named when the code is compiled (usually AssemblyName.exe.config) - You can merge the contents of two only files that way. If one needs a larger hierarchy, then this will not work well.
I am still in the process of refining the technique, thus I will return to update this post once done.
have a look on the following code that knows to load your configurations or the exe configuration. once the following code will be clear to you you can customize the loading and the merging as you wish (loading it twice and overriding one with the other).
private static void InitConfiguration()
{
var map = new ExeConfigurationFileMap();
var AssemblyConfigFile = "";
if (File.Exists(AssemblyConfigFile))
map.ExeConfigFilename = AssemblyConfigFile;
else
map.ExeConfigFilename = Path.Combine(Environment.CurrentDirectory, Environment.GetCommandLineArgs()[0]+".config");
var Configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
var serviceModelSectionGroup = ServiceModelSectionGroup.GetSectionGroup(Configuration);
}