none
App Config multiple sections in sectionGroup with same name

    Question

  • I'm looking to create a structure similar to the below excerpt but I'm not sure how to define the configSections and if the settings construct can handle this. Any suggestions on if there's another way to do it would be greatly appreciated.

     

    <configuration>
    <configSections>
    <sectionGroup name="Plugins">
    	<section name="Plugin"
    	 type="Test.Plugin.ConfigurationClassLoader , Test.Plugin.Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </sectionGroup>
    </configSections>
    <Plugins>
    	<Plugin
    		name="AuthenticationSettingsLoader"
    		assembly="Test.Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9d04a1dca2c654ed"
    		class="Test.Plugin.AuthenticationPlugin"
    		/>
    	<Plugin
    		name="UserManager"
    		assembly="Test.Plugin.Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
    		class="Test.Plugin.UserManager"
    		/>
    <!-- etc, there could be an unlimited number of <Plugin /> sections. 
    There could also be other attributes of the Plugin element which help
    tell the ConfigurationClassLoader class how to load the classes -->
    </Plugins>
    </configuration>

     

    Tuesday, March 23, 2010 6:20 PM

Answers

  • Hi Paulo,

    It looks like you almost got it.  The ConfigurationClassLoader needs to look something like this (not guaranteed to work unedited)

        public class ConfigurationClassLoader : ConfigurationSection
        {
            [ConfigurationProperty("Plugins")]
            public PluginsCollection Plugins
            {
                get { return ((PluginsCollection)(base["Plugins"])); }
            }
        }
    
        [ConfigurationCollection(typeof(PluginElement))]
        public class PluginsCollection : ConfigurationElementCollection
        {
            protected override ConfigurationElement CreateNewElement()
            {
                return new PluginElement();
            }
    
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((PluginElement)(element)).PluginType;
            }
    
            public PluginElement this[int idx]
            {
                get { return (PluginElement)BaseGet(idx); }
            }
        }
    
        public class PluginElement : ConfigurationElement
        {
            [ConfigurationProperty("name")]
            public string PluginType
            {
                get { return ((string)(base["name"])); }
                set { base["name"] = value; }
            }
    
            [ConfigurationProperty("assembly")]
            public string Priority
            {
                get { return ((string)(base["assembly"])); }
                set { base["assembly"] = value; }
            }
    
            [ConfigurationProperty("class")]
            public string Priority
            {
                get { return ((string)(base["class"])); }
                set { base["class"] = value; }
            }
        }

     


    ---
    Happy Coding!
    Morten Wennevik [C# MVP]
    Wednesday, March 24, 2010 7:35 AM

All replies

  • Hi Paulo,

    It looks like you almost got it.  The ConfigurationClassLoader needs to look something like this (not guaranteed to work unedited)

        public class ConfigurationClassLoader : ConfigurationSection
        {
            [ConfigurationProperty("Plugins")]
            public PluginsCollection Plugins
            {
                get { return ((PluginsCollection)(base["Plugins"])); }
            }
        }
    
        [ConfigurationCollection(typeof(PluginElement))]
        public class PluginsCollection : ConfigurationElementCollection
        {
            protected override ConfigurationElement CreateNewElement()
            {
                return new PluginElement();
            }
    
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((PluginElement)(element)).PluginType;
            }
    
            public PluginElement this[int idx]
            {
                get { return (PluginElement)BaseGet(idx); }
            }
        }
    
        public class PluginElement : ConfigurationElement
        {
            [ConfigurationProperty("name")]
            public string PluginType
            {
                get { return ((string)(base["name"])); }
                set { base["name"] = value; }
            }
    
            [ConfigurationProperty("assembly")]
            public string Priority
            {
                get { return ((string)(base["assembly"])); }
                set { base["assembly"] = value; }
            }
    
            [ConfigurationProperty("class")]
            public string Priority
            {
                get { return ((string)(base["class"])); }
                set { base["class"] = value; }
            }
        }

     


    ---
    Happy Coding!
    Morten Wennevik [C# MVP]
    Wednesday, March 24, 2010 7:35 AM
  • Here is a similar implementation of plug-in framework,

    http://msdn.microsoft.com/en-us/library/ms972962.aspx

    It also involves how to integrate plug ins in the app config file.

     

    Best regards,

    Ji Zhou


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Wednesday, March 24, 2010 9:07 AM
  • Thanks Morten! This almost works.

    I've changed the <configSections> element to look like this to reflect the code you provided.

     

    <configSections>
    <section name="Plugins"
    	type="Test.Plugin.ConfigurationClassLoader , Test.Plugin.Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </section>
    </configSections>

    Then in my code I am making a call like so:

     

    ConfigurationClassLoader pl = (ConfigurationClassLoader)ConfigurationManager.GetSection("Plugins");

     

    But I'm getting an exception:

     

    System.Configuration.ConfigurationErrorsException was unhandled
      Message="Unrecognized attribute 'name'. Note that attribute names are case-sensitive. 
      Source="System.Configuration"
      BareMessage="Unrecognized attribute 'name'. Note that attribute names are case-sensitive."
      Line=23
      StackTrace:
           at System.Configuration.BaseConfigurationRecord.EvaluateOne(String[] keys, SectionInput input, Boolean isTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult)
           at System.Configuration.BaseConfigurationRecord.Evaluate(FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult, Boolean getLkg, Boolean getRuntimeObject, Object& result, Object& resultRuntimeObject)
           at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
           at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
           at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
           at System.Configuration.BaseConfigurationRecord.GetSection(String configKey)
           at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
           at System.Configuration.ConfigurationManager.GetSection(String sectionName)
           at Test.Plugin.LoadPlugins() 
           at Test.Program.Main() 
           at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
           at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
           at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
           at System.Threading.ThreadHelper.ThreadStart()
      InnerException: 
    

     

    Here's the config section

    <Plugins>
    	<Plugin
    		name="AuthenticationSettingsLoader"
    		assembly="Test.Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9d04a1dca2c654ed"
    		class="Test.Plugin.AuthenticationPlugin"
    	/>
    </Plugins>

    Thank you again for your help. I just could not kickstart my brain yesterday to get this started properly.

    Wednesday, March 24, 2010 4:50 PM
  • Ah! I figured it out! it was because the collection class has to be encapsulated and I had to change the AddItemName. So it looks like this now


    <Plugins>
    <PluginList>
    	<Plugin
    		name="AuthenticationSettingsLoader"
    		assembly="Test.Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9d04a1dca2c654ed"
    		class="Test.Plugin.AuthenticationPlugin"
    	/>
    </PluginList>
    </Plugins>
    Thanks a tonne to both of you.

    Wednesday, March 24, 2010 5:46 PM
  • Paulo,

    I am struggling with the same problem, but still can't work out what the classes would look like to accompany that XML structure. Would you mind posting them here too?

     

    Thanks,

    Phil

    Friday, April 30, 2010 7:17 PM
  • Hi Phillip,

     

    So here's the PluginsLoader, this Handles the <Plugins> and the subsequent <PluginList> element.

    And the Plugin Element handles the <Plugin> element loading

     

    using System.Configuration;
    using Test.Configuration;
    
    namespace Test.Plugin
    {
    	internal sealed class PluginsLoader : ConfigurationSection
    	{
    		[ConfigurationProperty("PluginList")]
    		public PluginsCollection PluginConfigs
    		{
    			get { return ((PluginsCollection)(base["PluginList"])); }
    		}
    	}
    
    	[ConfigurationCollection(typeof(PluginElement), AddItemName="Plugin")]
    	public class PluginsCollection : ConfigurationElementCollection
    	{
    		protected override ConfigurationElement CreateNewElement()
    		{
    			return new PluginElement();
    		}
    
    		protected override object GetElementKey(ConfigurationElement element)
    		{
    			return ((PluginElement)(element)).PluginName;
    		}
    
    		public PluginElement this[int idx]
    		{
    			get { return (PluginElement)BaseGet(idx); }
    		}		
    	}
    	public class PluginElement : ConfigurationElement
    	{
    		[ConfigurationProperty("name", IsRequired=false)]
    		public string PluginName
    		{
    			get { return ((string)(base["name"])); }
    			set { base["name"] = value; }
    		}
    		[ConfigurationProperty("class", IsRequired = true)]
    		internal string ClassName
    		{
    			get
    			{ return (string)this["class"]; }
    			set
    			{ this["class"] = value; }
    		}
    		[ConfigurationProperty("assembly", IsRequired = true)]
    		internal string AssemblyName
    		{
    			get
    			{ return (string)this["assembly"]; }
    			set
    			{ this["assembly"] = value; }
    		}
    	}
    
    }
    
    *Note: this code may not be fully complete, its taken from a few different sections in my actual code which is drastically different (class inheritance, etc) but this is the basic premise

     

    Friday, April 30, 2010 7:46 PM
  • I finally got it working. I had to collect information from multiple sources because the samples above did not work for me (e.g. the last Paolo's sample does not contain "PluginList" anywhere in the posted code although it should handle that tag).

    So this is the config file:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    
     <configSections>
      <sectionGroup name="LOCALDEVELOPMENT">
       <section name="CleanUpEntries" type="FilesCleanUp.CleanUpEntriesSection, FilesCleanUp" />
      </sectionGroup>
     </configSections>
    
     <LOCALDEVELOPMENT>
      <CleanUpEntries>
       <CleanUpEntryList>
        <CleanUpEntry folder="c:\temp\test1" fileAgeLimit="6" />
        <CleanUpEntry folder="c:\temp\test2" fileAgeLimit="18" />
       </CleanUpEntryList>
      </CleanUpEntries>
     </LOCALDEVELOPMENT>
    
    </configuration>

     

    This is the source code:

    namespace FilesCleanUp
    {
      public class CleanUpEntriesSection : ConfigurationSection
      {
        [ConfigurationProperty("CleanUpEntryList")]
        [ConfigurationCollection(typeof(CleanUpEntryCollection), AddItemName = "CleanUpEntry")]
        public CleanUpEntryCollection CleanUpEntries
        {
          get { return ((CleanUpEntryCollection)(base["CleanUpEntryList"])); }
        }
      }
    
      public class CleanUpEntryCollection : ConfigurationElementCollection
      {
        protected override ConfigurationElement CreateNewElement()
        {
          return new CleanUpEntry();
        }
    
        protected override object GetElementKey(ConfigurationElement element)
        {
          return ((CleanUpEntry)element).Folder;
        }
    
        public CleanUpEntry this[int idx]
        {
          get { return (CleanUpEntry)BaseGet(idx); }
        }
      }
    
      public class CleanUpEntry : ConfigurationElement
      {
        [ConfigurationProperty("folder", IsRequired = true)]
        public string Folder
        {
          get { return ((string)(base["folder"])); }
          set { base["folder"] = value; }
        }
    
        [ConfigurationProperty("fileAgeLimit", IsRequired = true)]
        public int FileAgeLimit
        {
          get { return ((int)(base["fileAgeLimit"])); }
          set { base["fileAgeLimit"] = value; }
        }
      }
    }

     

    And you can then access the CleanUpEntries like this:

    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    CleanUpEntriesSection cleanUpEntriesSection = (CleanUpEntriesSection)config.SectionGroups["LOCALDEVELOPMENT"
    ].Sections["CleanUpEntries"
    ];
    CleanUpEntry entry1 = cleanUpEntriesSection.CleanUpEntries[0];
    CleanUpEntry entry2 = cleanUpEntriesSection.CleanUpEntries[1];
    
    
    
    Hopefully it will help somebody.

     

    Wednesday, September 01, 2010 11:55 AM
  • Oh! My bad. I screwed up the example I posted when simplifying the code I was actually using. I've edited the post to reflect the code properly.
    Thank you in advance for your help. If you think you may be able to help with any of my unanswered threads please look at them here
    Wednesday, September 01, 2010 5:55 PM