19
votes

My question is about working with standard .NET configuration objects and custom configuration elements as well, (which one can define by extending the System.Configuration.ConfigurationSection class). We usually obtain these from the System.Configuration.ConfigurationManager class's methods, GetSection(...) being an example.

The loaded configuration object appears to be a merged configuration object containing the setup present in the application config file (the the app.config or web.config file which a developer may have created) and what is defined in the machine.config file (the latter coming with the .NET Framework installation).

So, we can assume that the configuration is loaded in hierarchical manner with the machine.config first, and any user-defined configuration overlaying that default setup, and could be looked at like this:

  • machine.config
    • app.config (overrides / merges with respective elements found in machine.config )

My goal is to create multiple layers of configuration, so that there might be other config files between (and if possible, after) the machine.config and the app.config file:

  • machine.config
    • custom.config (a custom config interfering between the machine.config and the app.config file)
      • app.config - now app.config is merged with both machine.config and custom.config

Update

The point with that is - if I have defined the configuration section in both custom.config and app.config, I need to get a merged version of both configs when I call ConfigurationManager.GetSection("MyCustomSection"). Ideally, it will be great if I am able to perform merging as describe in this MSDN article.


Normally, I would write my own configuration manager class and will try to get as far as I could to get the desired result, but assuming the .NET framework has this working for machine.config and app.config, I thought I might benefit from the built-in functionality of the framework. In addition, I do not know how to manually trigger such merging, if I am indeed supposed to resort to a config manager implementation of my own.

So, is it possible to leverage the built-in mechanisms of configuration section/element merging with custom configuration files? I am especially interested in developing and supporting this for custom configuration sections. The System.Configuration namespace contains base objects to build configuration section and elements, and those allow some settings related to the merging (like setting the appropriate ConfigurationElementCollectionType for example). Are these related to only merging with machine.config (or multiple layers of web.config files in a web application), or is it possible to manually trigger the merging of pre-loaded config files? I am trying to avoid having to support custom merging in any of my custom configuration objects and probably forget about supporting the existing settings from the System.Configuration...


Update

There is an important clarification to I'd like to make, in response to the existing answers and comments. I am capable of loading the ConfigurationSection objects from the current application setup (app.config / web.config) and from a physical file that is my custom.config. I need to know if there is a chance to merge these objects without resorting to reflection and property-by-property comparison, by some built-in means in the framework.


Note: I would appreciate better a solution that would work on .NET 3.0+. Please add a note if your answer targets a higher version of the framework.

5
I am looking forward to seeing answers to this question. But I fear that what you're asking for is not possible. The .NET configuration system is not super flexible when it comes to configuration file splitting / modularization.stakx - no longer contributing
@stakx, maybe you're correct. Still, since the .NET framework itself exposes merging capabilities (and does this merging internally), it must be possible to at least understand how it all happens. I have managed to do a lot for easing the configuration in most of my projects, yet still this merging capability is the missing piece of the puzzle I am looking for from a long time.Ivaylo Slavov
This is not an exact answer to the question, but you could use external satellite config files with the configSource attribute: msdn.microsoft.com/library/…Simon Mourier
@SimonMourier, thank you for the hint. However, this seems to help in splitting the config file into more convenient locations (unless I am missing something). I am more interested in layering the settings between different config files with similar structure - for instance if both app.config and custom.config defined element 'custom element', I need to obtain a merged version of it from both files. when I use ConfigurationManager.GetSection(...). I hope this clarifies my requirement a little more.Ivaylo Slavov

5 Answers

8
votes

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.

5
votes

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 real machine.config file. This could be error-prone and should be avoided for web applications, as most of them rely on what's in the real machine.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.

3
votes

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

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

}
0
votes

we may need to use the classes ConfigurationSection and ConfigurationProperty

Please refer below link for the details on the implementation https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config/