Additive merge of different config sources?

Topics: Enterprise Library Core, Logging Application Block
Apr 13, 2011 at 4:06 PM

Hi,

I have a problem with the following scenario: I have a basic "static" configuration for the logging block inside my app.config file (logs to some file). And a "dynamic" part that needs to be configured from code (that logs to a different file at a location that must be determined at runtime).

I used the fluent api to create my dynamic logging configsource and then looked for a way to merge those settings with the ones from app.config.

UpdateWithReplace seems to replace (hence the name) the static part of the config but what I need is an "additive merge" that keeps both the static and the dynamic logging config.

Any hints how I can do that?

Thanks!

Apr 14, 2011 at 3:12 AM

I'm thinking this could be accomplished by creating a custom configuration source and making use of the HierarchicalConfigurationSourceHandler class.  You can check from the entlib source code how this was used to merge configurations.  If you also need information how to create a custom configuration source, you can check out EntLib 5.0's Extensibility Labs.

 

Sarah Urmeneta
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Apr 14, 2011 at 10:54 AM
Edited Apr 14, 2011 at 11:58 AM

Is there any documentation for the HierarchicalConfigurationSourceHandler other than "its in the code"? Its a tiny bit more complicated than HelloWorld....

Looks like the replace behavior is by design. At least this is what I got from the unit tests of the HierarchicalConfigurationSourceHandler. If a section exists in both, parent and local, local wins and replaces parent.

I can't directly manipulate the LoggingSettings I get from the SystemConfigurationSource (by copying TraceListeners, Filters and stuff from dynamic configuration) as they are readonly.

I tried merging at the xml level but that turned out to be even more "not so easy".

Any more recommendations?

Apr 15, 2011 at 9:04 AM

The replace behavior of the FluentConfiguration API is yes, by design.  It's not a behavior inherent to the HierarchicalConfigurationSourceHandler. 

I still think that this class will help you with the additive merge behavior that you want.    There's no documentation for this class so what you can do is to manually debug through this code.

 

Sarah Urmeneta
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Apr 18, 2011 at 10:41 AM

Do things right and all of a sudden they work as expected... The HierarchicalConfigurationSourceHandler does the merge as expected, I just used it the wrong way. Thanks for your advice!

May 29, 2011 at 9:35 PM

Hi Damien,

Would you mind sharing the code you finally used? I'm running into the exact same issue.

Thanks.

May 30, 2011 at 9:13 AM

Hi rudi,

the attached code is a proof of concept that would need some fine tuning.

HIH

   /// <summary>
    /// Takes two <see cref="IConfigurationSource"/>s and tries to merge settings from both
    /// </summary>
    public class MergeConfigurationSource : IConfigurationSource
    {
        #region Fields

        private readonly HierarchicalConfigurationSourceHandler hierarchicalConfigurationSourceHandler;
        private readonly CompositeConfigurationSourceHandler compositeConfigurationSourceHandler;
        private readonly IConfigurationSource parentSource;
        private readonly IConfigurationSource localSource;

        private readonly object eventHandlersLock;  // lock used to protect the event handlers list
        private readonly EventHandlerList eventHandlers;

        #endregion Fields

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MergeConfigurationSource"/> class.
        /// </summary>
        /// <param name="localSource">The local source.</param>
        /// <param name="parentSource">The parent source.</param>
        /// <remarks>Naming of the parameters uses Enterprise Library conventions 
        /// (<seealso cref="hierarchicalConfigurationSourceHandler"/>)</remarks>
        public MergeConfigurationSource(IConfigurationSource localSource, IConfigurationSource parentSource)
        {
            if (localSource == null) throw new ArgumentNullException("localSource");
            if (parentSource == null) throw new ArgumentNullException("parentSource");

            this.localSource = localSource;
            this.parentSource = parentSource;

            this.localSource.SourceChanged += OnSourceChanged;
            this.parentSource.SourceChanged += OnSourceChanged;

            hierarchicalConfigurationSourceHandler = new HierarchicalConfigurationSourceHandler(this.localSource, this.parentSource);
            compositeConfigurationSourceHandler = new CompositeConfigurationSourceHandler(this.parentSource);

            eventHandlersLock = new object();
            eventHandlers = new EventHandlerList();
        }

        #endregion Constructor

        /// <inheritdoc/>
        public event EventHandler<ConfigurationSourceChangedEventArgs> SourceChanged = delegate { };

        /// <inheritdoc/>
        public void Dispose()
        {
            hierarchicalConfigurationSourceHandler.Dispose();
            compositeConfigurationSourceHandler.Dispose();

            parentSource.Dispose();
            localSource.Dispose();
        }

        /// <summary>
        /// Gets a section identified by its <paramref name="sectionName"/>. If the section exists in local and parent source it
        /// will be merged.
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns>The configuration section with the given <paramref name="sectionName"/>; <c>null</c> if no such section can be found.</returns>
        public ConfigurationSection GetSection(string sectionName)
        {
            if (sectionName == null) throw new ArgumentNullException("sectionName");

            ConfigurationSection configurationSection = localSource.GetSection(sectionName);

            configurationSection = compositeConfigurationSourceHandler.CheckGetSection(sectionName, configurationSection);

            return hierarchicalConfigurationSourceHandler.CheckGetSection(sectionName, configurationSection);
        }

        /// <summary>
        /// Adds the specified section to the local configuration source
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <param name="configurationSection">The configuration section.</param>
        public void Add(string sectionName, ConfigurationSection configurationSection)
        {
            if (sectionName == null) throw new ArgumentNullException("sectionName");
            if (configurationSection == null) throw new ArgumentNullException("configurationSection");

            if (!compositeConfigurationSourceHandler.CheckAddSection(sectionName, configurationSection))
            {
                try
                {
                    localSource.Add(sectionName, configurationSection);
                }
                catch (Exception innerException)
                {
                    var exception = new AddSectionFailedException(sectionName, innerException);

                    throw exception;
                }
            }
        }

        /// <summary>
        /// Removes the specified section from parent and local configuration source
        /// </summary>
        /// <param name="sectionName">Name of the section to remove</param>
        public void Remove(string sectionName)
        {
            if (sectionName == null) throw new ArgumentNullException("sectionName");

            if (!compositeConfigurationSourceHandler.CheckRemoveSection(sectionName))
            {
                localSource.Remove(sectionName);
                parentSource.Remove(sectionName);
            }
        }

        /// <inheritdoc/>
        public void AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
        {
            if (sectionName == null) throw new ArgumentNullException("sectionName");
            if (handler == null) throw new ArgumentNullException("handler");

            lock (eventHandlersLock)
            {
                eventHandlers.AddHandler(sectionName, handler);
            }
        }

        /// <inheritdoc/>
        public void RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
        {
            if (sectionName == null) throw new ArgumentNullException("sectionName");
            if (handler == null) throw new ArgumentNullException("handler");

            lock (eventHandlersLock)
            {
                eventHandlers.RemoveHandler(sectionName, handler);
            }
        }

        private void OnSourceChanged(object sender, ConfigurationSourceChangedEventArgs e)
        {
            SourceChanged(this, e);
        }
    }

Jan 11, 2012 at 7:14 PM

this post and code just made me extremely happy!!!!!!!!!!!!!!!! Thank you Damien and Avanade. Frustrating a whole day over this very issue was not for nothing thanks to you.

Jan 11, 2012 at 8:07 PM

i just tried to do an additional merge with an already merged configuration source, but it yielded only configsource2 and configsource3 as functional - i.e. configsource1 did not work any more...

how could i modify this client code or how could Damien's MergeConfigurationSource class be modified to allow infinite programmatic merging of configuration sources?

 

var builder1 = new ConfigurationSourceBuilder();
// builder1 configuration here ...
var configSource1 = new DictionaryConfigurationSource();
builder1.UpdateConfigurationWithReplace(configSource1);

var builder2 = new ConfigurationSourceBuilder();
// builder2 configuration here ...
var configSource2 = new DictionaryConfigurationSource();
builder2.UpdateConfigurationWithReplace(configSource2);

var merge1 = new MergeConfigurationSource(configSource2, configSource1);

var builder3 = new ConfigurationSourceBuilder();
// builder3 configuration here ...
var configSource3 = new DictionaryConfigurationSource();
builder3.UpdateConfigurationWithReplace(configSource3);

var merge2 = new MergeConfigurationSource(configSource3, merge1);

EnterpriseLibraryContainer.ConfigureContainer(configurator, merge2);


Jan 11, 2012 at 9:08 PM

You could try to replace parent/localSource with a list of config sources and change the rest of the implementation accordingly.

Jan 12, 2012 at 11:58 AM
Edited Jan 12, 2012 at 12:05 PM

Thanks weberse!

Below is something that works for me, based on Damien's code:

You can instantiate the MultiConfigurationSource with a List<IConfigurationSource>, which can contain as many configurations as you wish

Overlapping configuration sections in those configurations should be all merged

Use AddSource / RemoveSource to add/remove configurations from the instance post-construction

Or you could instantiate with e.g. SortedSet<IConfigurationSource> to conserve specific dominance order of the configuration sources (given overlap, early configurations in collection should be overriden by late configurations in collection)

Note the class is not tested much.

p.s. do say if you see any obvious problems with the code?

 


    /// <summary>
    /// Takes multiple <see cref="IConfigurationSource"/>s and tries to merge settings from all
    /// Based on Damien's MergeConfigurationSource http://entlib.codeplex.com/discussions/253733
    /// </summary>
    public class MultiConfigurationSource<TCollection> : IConfigurationSource
        where TCollection : class, ICollection<IConfigurationSource>, new()
    {
        #region Fields

        private readonly TCollection _sources;
        private readonly IConfigurationSource _extraSource = new DictionaryConfigurationSource();
        private readonly object _eventHandlersLock;  // lock used to protect the event handlers list
        private readonly EventHandlerList _eventHandlers;
        /// <inheritdoc/>
        public event EventHandler<ConfigurationSourceChangedEventArgs> SourceChanged = delegate { };

        #endregion Fields

        public TCollection Sources
        {
            get { return _sources; }
        }

        #region Constructor

        public MultiConfigurationSource(TCollection sources)
        {
            if (sources == null)
                throw new ArgumentNullException("sources");
            _sources = sources;
            _sources.Add(_extraSource);
            foreach (IConfigurationSource s in _sources)
                s.SourceChanged += OnSourceChanged;
            _eventHandlersLock = new object();
            _eventHandlers = new EventHandlerList();
        }

        #endregion Constructor

        /// <summary>
        /// Gets a section identified by its <paramref name="sectionName"/>. If the section exists in any of the configuration sources it
        /// will be merged.
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <returns>The configuration section with the given <paramref name="sectionName"/>; <c>null</c> if no such section can be found.</returns>
        public ConfigurationSection GetSection(string sectionName)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");

            ConfigurationSection mergedSection = null;
            IConfigurationSource mergedSource = null;

            foreach (IConfigurationSource s in _sources)
            {
                var localSection = s.GetSection(sectionName);
                if (localSection == null)
                    continue;
                mergedSection = (mergedSection == null)
                                    ? localSection
                                    : Merge(sectionName, s, mergedSource);
                if (mergedSection != null)
                {
                    mergedSource = new DictionaryConfigurationSource();
                    mergedSource.Add(sectionName, mergedSection);
                }
            }
            return mergedSection;
        }

        private ConfigurationSection Merge(string sectionName, IConfigurationSource localSource, IConfigurationSource parentSource)
        {
            var configurationSection = localSource.GetSection(sectionName);
            var compositeConfigurationSourceHandler = new CompositeConfigurationSourceHandler(parentSource);
            configurationSection = compositeConfigurationSourceHandler.CheckGetSection(sectionName, configurationSection);
            var hierarchicalConfigurationSourceHandler = new HierarchicalConfigurationSourceHandler(localSource, parentSource);
            return hierarchicalConfigurationSourceHandler.CheckGetSection(sectionName, configurationSection);
        }

        public void AddSource(IConfigurationSource newSource)
        {
            if (newSource == null)
                throw new ArgumentNullException("newSource");
            _sources.Add(newSource);
        }

        public void RemoveSource(IConfigurationSource existingSource)
        {
            if (existingSource == null)
                throw new ArgumentNullException("existingSource");
            _sources.Remove(existingSource);
        }

        /// <summary>
        /// Adds the specified section to the extra configuration source
        /// </summary>
        /// <param name="sectionName">Name of the section.</param>
        /// <param name="configurationSection">The configuration section.</param>
        public void Add(string sectionName, ConfigurationSection configurationSection)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (configurationSection == null)
                throw new ArgumentNullException("configurationSection");
            _extraSource.Add(sectionName, configurationSection);
        }

        /// <summary>
        /// Removes the specified section from all sources
        /// </summary>
        /// <param name="sectionName">Name of the section to remove</param>
        public void Remove(string sectionName)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            _extraSource.Remove(sectionName);
            foreach (IConfigurationSource s in _sources)
                s.Remove(sectionName);
        }

        /// <inheritdoc/>
        public void AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (handler == null)
                throw new ArgumentNullException("handler");

            lock (_eventHandlersLock)
            {
                _eventHandlers.AddHandler(sectionName, handler);
            }
        }

        /// <inheritdoc/>
        public void RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (handler == null)
                throw new ArgumentNullException("handler");

            lock (_eventHandlersLock)
            {
                _eventHandlers.RemoveHandler(sectionName, handler);
            }
        }

        private void OnSourceChanged(object sender, ConfigurationSourceChangedEventArgs e)
        {
            SourceChanged(this, e);
        }

        /// <inheritdoc/>
        public void Dispose()
        {            
            foreach (IConfigurationSource s in _sources)
                s.Dispose();
        }
    }