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