Unrecognized elements in custom config files

Here’s one of those nagging quirks that you may never even consider the solution for, unless it was by sheer dumb luck.

The problem is as follows :
You have created a custom configuration section to put all your application settings and created the appropriate classes to read from them. But for some reason, you keep getting “Unrecognized element ‘clear'” or “Unrecognized element ‘add'” errors and every one of your custom elements are rejected as well.

I have the following in my root web.config file inside the <configuration> tag.

<configSections>
	<sectionGroup name="Section12">
		<section name="ForumSettings" 
				 type="Section12.AppSettings.ForumSection"
				 requirePermission="false" 
				 restartOnExternalChanges="true"/>
	</sectionGroup>
</configSections>

<!-- Custom section group with an external forum config file -->
<Section12>
	<ForumSettings configSource="App_Data\Configurations\Forum.config" />
</Section12>

And I have the following in that custom Forum.config file :

<ForumSettings>
	<clear/>
	<add name="ForumTopicsPerPage" value="20" />
	<add name="ForumPostsPerPage" value="10" />
	<!-- ... And so on. You get the idea... -->
</ForumSettings>

The reason for the seperate file is because I wanted to be able to change the app settings at runtime. Editing XML in-app is far simpler, I think, than hitting a database for your settings. But this problem happens even if you don’t use a seperate file for your configurations.

This class is handling my custom forum settings (Section12.AppSettings.ForumSection).

public class ForumSection : ConfigurationSection
{
	[ConfigurationProperty("ForumSettings", IsRequired = true)]
	[ConfigurationCollection(typeof(SettingsCollection), 
		AddItemName="add",
		ClearItemsName="clear",
		RemoveItemName="remove")]
	public SettingsCollection ForumConfig
	{
		get
		{
			SettingsCollection set = (SettingsCollection)base["ForumSettings"];
			return set;
		}
	}
}

And I have the following two classes that handle the settings collection and each setting element

public class SettingsCollection : ConfigurationElementCollection
{
	public SettingsCollection()
	{
		SettingElement key = (SettingElement)CreateNewElement();
		Add(key);
	}

	public override ConfigurationElementCollectionType CollectionType
	{
		get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
	}

	protected override ConfigurationElement CreateNewElement()
	{
		return new SettingElement();
	}

	protected override Object GetElementKey(ConfigurationElement element)
	{
		return ((SettingElement)element).Name;
	}

		public SettingElement this[int index]
	{
		get { return (SettingElement)BaseGet(index); }
		set
		{
			if (BaseGet(index) != null)
				BaseRemoveAt(index);
			
			BaseAdd(index, value);
		}
	}

	new public SettingElement this[string Name]
	{
		get { return (SettingElement)BaseGet(Name); }
	}

	public int IndexOf(SettingElement key)
	{
		return BaseIndexOf(key);
	}

	public void Add(SettingElement key)
	{
		BaseAdd(key);
	}

	protected override void BaseAdd(ConfigurationElement element)
	{
		BaseAdd(element, false);
	}

	public void Remove(SettingElement key)
	{
		if (BaseIndexOf(key) >= 0)
			BaseRemove(key.Name);
	}

	public void RemoveAt(int index)
	{
		BaseRemoveAt(index);
	}

	public void Remove(string name)
	{
		BaseRemove(name);
	}

	public void Clear()
	{
		BaseClear();
	}

}


// Settings element class...
public class SettingElement : ConfigurationElement
{
	public SettingElement() {
		this.Name = "NoName";
		this.Value = "NoValue";
		this.Perm = 0;
	}

	public SettingElement(string name, string value)
	{
		this.Name = name;
		this.Value = value;
	}

	[ConfigurationProperty("name", DefaultValue = "", IsRequired = true, IsKey = true)]
	public string Name
	{
		get { return (string)this["name"]; }
		set { this["name"] = value; }
	}

	[ConfigurationProperty("value", DefaultValue = "", IsRequired = true)]
	public string Value
	{
		get { return (string)this["value"]; }
		set { this["value"] = value; }
	}

	[ConfigurationProperty("perm", DefaultValue = "0", IsRequired = true)]
	[IntegerValidator(MinValue=0, MaxValue=1, ExcludeRange=false)]
	public int Perm
	{
		get { return (int)this["perm"]; }
		set { this["perm"] = value; }
	}
}

Now everything seemed fine and dandy, but it was when I actually tried to run this that I realized I need to stock up on Asprin.

Someone kill me please!

I was scratching my head to no end, until I found a truly WTF?! solution to it. I had to wrap the ForumSettings tag inside another ForumSettings tag!!!

<ForumSettings>
	<ForumSettings>
		<clear/>
		<add name="ForumTopicsPerPage" value="20"></add>
		<add name="ForumPostsPerPage" value="10"></add>
	</ForumSettings>
</ForumSettings>

Maybe I’m too tired, too strung up or too stupid, but there has to be a better solution to this. I hate to blame everything on Microsoft, but considering how hyped C# 4.0 and MVC is right now, it’s pretty silly that this is the only workaround for this problem.

If anyone has a better idea on how to fix this, I’m all ears.

Advertisements

13 thoughts on “Unrecognized elements in custom config files

  1. Adding the IsDefaultCollection attribute to the ForumSectionForumConfig() method may help.

    See http://msdn.microsoft.com/en-us/library/system.configuration.configurationproperty.isdefaultcollection.aspx

    With your example it would be

    [ConfigurationProperty(“ForumSettings”,
    IsRequired = true,
    IsDefaultCollection = false)]
    [ConfigurationCollection(typeof(SettingsCollection),
    AddItemName=”add”,
    ClearItemsName=”clear”,
    RemoveItemName=”remove”)]
    public SettingsCollection ForumConfig
    {
    get
    {
    SettingsCollection set = (SettingsCollection)base[“ForumSettings”];
    return set;
    }
    }

    • Thanks for dropping by. I tried IsDefaultCollection before and it doesn’t fix it either.

      Funny we came to this again, because I’m in the middle of writing another discussion forum (this time in MVC 3) and I had just come to the same config issue. There has to be something else missing. I’m sure it’s a minor thing that’s easily overlooked.

      It’s not a huge issue, but it’s annoying that I can’t get it to work without the extra wrapping. It just doesn’t look right.

  2. I was having the same issue until I added the ConfigurationCollection attribute to my property. The only other optional part I filled out was the AddItemName field. I did not have to “double-wrap” my xml settings exactly like you did, however there is a slight difference between what you have and what I have. In my config file, I have a distinct section which corresponds to my ConfigurationSection class. This appears to be missing in your example. In my application, I use the ConfigurationManager to get this specific section. I also have a property on my section class (like you do) to return my custom collection class. In my xml file, I have a nested tag within my section which corresponds to the collection (in your example, it would be named “ForumSettings”). Within that is where I have my individual “add” items (which I had optionally renamed). This configuration seems to work for me. I think your issue was that you did not specify a section tag in your file. Here is my xml example:

    ConsumerSettings is my ConfigurationSection.
    ConsumerConfigs corresponds to my ConfigurationElementCollection.
    ConsumerConfig is my ConfigurationElement (essentially the renamed tag according to the AddItemName property of the ConfigurationCollection attribute). I have additional properties on this element, but I excluded them in this sample.

    Also, my file is wrapped in the element, and in order to register my custom section, I had to add this at the top:

  3. Use ConfigurationProperty(“”, IsDefaultCollection=true) for the ForumConfig property, this gives you the invisible default collection that you are looking for.

  4. I know this is old but has anyone figured a way to eliminate the need for doubling the collection tag. It looks a little strange in my web.config

  5. I found the answer. I missed it in Rolf’s comment, but in the ConfigurationSection class, I replaced the strings that had the collection name with empty strings. I also didn’t use the ConfigurationCollection attribute in that class and I included IsDefaultCollection=true . Using the example from this post, the class would look like this:

    public class ForumSection : ConfigurationSection
    {
    [ConfigurationProperty(“”, IsRequired = true, IsDefaultCollection=true)]
    public SettingsCollection ForumConfig
    {
    get
    {
    SettingsCollection set = (SettingsCollection)base[“”];
    return set;
    }
    }
    }

  6. Hi, the thing is that the section’s name is exactly the same like the collection itself. That is the point that will be confusing
    If you have more properties in the section, you will see what I mean.

    The following code is only for explanation:

    // ConfigurationSection class
    public class SettingsConfigurationSection : ConfigurationSection
    {
    [ConfigurationProperty(“ForumSettings”, IsRequired = true)]
    [ConfigurationCollection(typeof(ForumSettingsCollection),
    AddItemName=”add”,
    ClearItemsName=”clear”,
    RemoveItemName=”remove”)]
    public ForumSettingsCollectionForumConfig
    {
    get
    {
    return (ForumSettingsCollection)base[“ForumSettings”];
    }
    }

    [ConfigurationProperty(“Design”, IsRequired = true)]
    public Design SelectedDesign
    {
    // …
    }
    }

    // usage
    System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

    SettingsConfigurationSection serviceConfigSection = config.GetSection(“ConsoleSettings/Settings”) as SettingsConfigurationSection;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s