Custom configuration unrecognized attribute for remove element - c#

I have a custom configuration section that is throwing the following error:
Unrecognized attribute 'path'. Note that attribute names are case-sensitive.
The rest of the section does not have any problems loading. But once I add a <remove> element to the collection in question, the configuration fails to load.
Here's the code for the problematic section:
[ConfigurationCollection(typeof(PathElement), CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
public class PathElementCollection : ConfigurationElementCollection
{
public PathElementCollection()
{
// Load the default values...
BaseAdd(new PathElement() { Path = "/content/" });
}
protected override ConfigurationElement CreateNewElement()
{
return new PathElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((PathElement)element).Path;
}
}
public class PathElement : ConfigurationElement
{
public const string PathPropertyName = "path";
[ConfigurationProperty(PathPropertyName, IsRequired = true)]
public string Path
{
get { return (string)this[PathPropertyName]; }
set { this[PathPropertyName] = value; }
}
}
How I'm loading the section in the module that uses it:
CustomSection configSection = (CustomSection)ConfigurationManager.GetSection(CustomSection.SectionName);
Example configuration:
<ignoredPaths>
<remove path="/content/" />
<add path="/test/" />
</ignoredPaths>
Does anyone have any ideas as to what I'm doing wrong?

Your classes should look like this
[XmlRoot("ignoredPaths")]
public class IgnoredPaths
{
PathElementCollection paths {get;set;}
}
[XmlInclude(typeof(Remove))]
[XmlInclude(typeof(Add))]
public class PathElementCollection
{
[XmlAttribute("path")]
public string path {get;set;}
}
[XmlRoot("remove")]
public class Remove : PathElementCollection
{
}
[XmlRoot("add")]
public class Add : PathElementCollection
{
}​

Related

C# .Net 4.0 - Custom Configuration File with Attributes and sections

I know that this topic has been covered in a number of different Stackoverflow articles, and I have read about 30 of them to make sure that what I am doing matches up with those. It is (even fro the .Net 2.0, 3.0, and 4.0 version of the answers)
I am attempting to create a very simple (at least in my mind) configuration file with custom attributes on the sections, and then optional items within the sections. So, now to the code:
<?xml version="1.0" encoding="utf-8" ?>
<CustomSiteConfiguration>
<Sites>
<Site siteRoot="/Site US" name="SiteUS_en">
</Site>
<Site siteRoot="/Site Canada" name="SiteCanada_en">
</Site>
<Site siteRoot="/Partner" name="Partner_en">
<siteSettings>
<setting name="" value="" />
</siteSettings>
<JavaScriptBundles>
<file name="" />
</JavaScriptBundles>
<CSSBundles>
<file name="" />
</CSSBundles>
</Site>
</Sites>
</CustomSiteConfiguration>
So, what you are looking at is a global Section of type Sites which contains multiple sections (CollectionElementCollections) of type Site. Site is defined by custom attributes on the item, as well as optional items within the section itself. So, siteSettings is optional, JavaScriptBundles is optional, and CSSBundles are also optional.
The C# Code is below:
For Sites
public class CustomGlobalSiteConfiguration : ConfigurationSection
{
public CustomGlobalSiteConfiguration() { }
[ConfigurationProperty("Sites")]
[ConfigurationCollection(typeof(SitesCollection), AddItemName="Site")]
public SitesCollection Sites
{
get
{
return (SitesCollection)base["Sites"];
}
}
}
For Site Collections
[ConfigurationCollection(typeof(SitesCollection), AddItemName="Site")]
public class SitesCollection : ConfigurationElementCollection
{
// Constructor
public SitesCollection() { }
/*
public CustomSiteConfiguration this[int index]
{
get { return (CustomSiteConfiguration)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public siteSetting this [int index]
* */
protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomSiteConfiguration)element).name;
}
protected override ConfigurationElement CreateNewElement()
{
return new SitesCollection();
}
}
For Site Definition
/**
* Overarching structure of the Site Item
**/
public class CustomSiteConfiguration : ConfigurationElement
{
[ConfigurationProperty("siteRoot")]
public String siteRoot
{
get
{
return (String)this["siteRoot"];
}
set
{
this["siteRoot"] = value;
}
}
[ConfigurationProperty("name")]
public String name
{
get
{
return (String)this["name"];
}
set
{
this["name"] = value;
}
}
[ConfigurationProperty("siteSettings", IsRequired=false)]
public CustomSiteSiteSettings siteSettings
{
get
{
return this["siteSettings"] as CustomSiteSiteSettings;
}
}
[ConfigurationProperty("JavaScriptBundles", IsRequired = false)]
public JavaScriptBundles javaSciptBundle
{
get
{
return this["JavaScriptBundles"] as JavaScriptBundles;
}
}
[ConfigurationProperty("CSSBundles", IsRequired = false)]
public CSSBundles cssBundle
{
get
{
return this["CSSBundles"] as CSSBundles;
}
}
} // end of public class CustomSiteConfiguration : ConfigurationSection
For SiteSettings Definition
/**
* Subsection - Site Settings
**/
public class CustomSiteSiteSettings : ConfigurationElementCollection
{
// Constructor
public CustomSiteSiteSettings() { }
public siteSetting this [int index]
{
get { return (siteSetting)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public siteSetting this [int index]
protected override object GetElementKey(ConfigurationElement element)
{
return ((siteSetting)element).name;
}
protected override ConfigurationElement CreateNewElement()
{
return new CustomSiteSiteSettings();
}
} // end of public class CustomSiteSiteSettings : ConfigurationSection
Site Setting Element
public class siteSetting : ConfigurationElement
{
[ConfigurationProperty("name")]
public String name
{
get
{
return (String)this["name"];
}
set
{
this["name"] = value;
}
} // end of public String name
[ConfigurationProperty("value")]
public String value
{
get
{
return (String)this["value"];
}
set
{
this["value"] = value;
}
} // end of public String value
} // end of public class siteSetting : ConfigurationElement
I am leaving out the other items for space, but the other parts look the same. Basically, what is happening is, I am getting
Unrecognized attribute 'siteRoot'. Note that attribute names are case-sensitive.
Looking at everything, it appears that I should be fine, however, I think I may be doing too much and missing things. Any help with this would be greatly appreciated.
Thanks
I have figured out what was wrong with my code. I am going to provide the information below. I used the following article for help on tracking down some of the pieces: How to implement a ConfigurationSection with a ConfigurationElementCollection
I took the entire code base back to nothing and built it up from scratch. The XML is still the same
<?xml version="1.0" encoding="utf-8" ?>
<CustomSiteConfiguration>
<Sites>
<Site siteRoot="/Site US" name="SiteUS_en">
</Site>
<Site siteRoot="/Site Canada" name="SiteCanada_en">
</Site>
<Site siteRoot="/Partner" name="Partner_en">
<siteSettings>
<setting name="" value="" />
</siteSettings>
<JavaScriptBundles>
<file name="" />
</JavaScriptBundles>
<CSSBundles>
<file name="" />
</CSSBundles>
</Site>
</Sites>
</CustomSiteConfiguration>
So, first I started with the Sites Container
public class CustomSiteSettingsSection : ConfigurationSection
{
[ConfigurationProperty("Sites")]
[ConfigurationCollection(typeof(SiteCollection), AddItemName="Site")]
public SiteCollection Sites
{
get
{
return (SiteCollection)base["Sites"];
}
} // end of public SiteCollection Site
} // end of public class CustomSiteSettings : ConfigurationSection {
And then I added the SiteCollection for the Collection of Site Elements
public class SiteCollection : ConfigurationElementCollection
{
// Constructor
public SiteCollection() { }
public SiteElement this[int index]
{
get { return (SiteElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public SiteElement this[int index]
protected override ConfigurationElement CreateNewElement()
{
return new SiteElement();
} // end of protected override ConfigurationElement CreateNewElement()
protected override object GetElementKey(ConfigurationElement element)
{
return ((SiteElement)element).name;
}
} // end of public class SiteCollection : ConfigurationElementCollection
Then I added the definition for the Site with optional values
public class SiteElement : ConfigurationElement
{
// Constructor
public SiteElement() { }
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public String name
{
get { return (String)this["name"]; }
set { this["name"] = value; }
} // end of public String name
[ConfigurationProperty("siteRoot", IsRequired = true)]
public String siteRoot
{
get { return (String)this["siteRoot"]; }
set { this["siteRoot"] = value; }
} // end of public String siteRoot
[ConfigurationProperty("siteSettings", IsRequired=false)]
[ConfigurationCollection(typeof(SiteSettingsElementCollection), AddItemName = "setting")]
public SiteSettingsElementCollection siteSettings
{
get
{
return (SiteSettingsElementCollection)base["siteSettings"];
}
} // end of public SiteCollection Site
} // end of public class SiteElement : ConfigurationElement
Next I added the SiteSettings Collection
public class SiteSettingsElementCollection : ConfigurationElementCollection
{
// Constructor
public SiteSettingsElementCollection() { }
public SiteSettingElement this[int index]
{
get { return (SiteSettingElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
} // end of public SiteElement this[int index]
protected override ConfigurationElement CreateNewElement()
{
return new SiteSettingElement();
} // end of protected override ConfigurationElement CreateNewElement()
protected override object GetElementKey(ConfigurationElement element)
{
return ((SiteSettingElement)element).name;
}
} // end of public class SiteCollection : ConfigurationElementCollection
And finally, I added the Setting Element Definition
public class SiteSettingElement : ConfigurationElement
{
public SiteSettingElement() { }
[ConfigurationProperty("name", IsRequired=true, IsKey=true)]
public String name
{
get { return (String)this["name"]; }
set { this["name"] = value; }
} // end of public String name
[ConfigurationProperty("value", IsRequired = true)]
public String value
{
get { return (String)this["value"]; }
set { this["value"] = value; }
} // end of public String value
} // end of public class SiteSettingElement : ConfigurationElement
At this point, I just repeat the same for the two bundles. In the end this all works, and allows for optional settings and sections.

App.config: custom configuration nested sections

I have found a great example for custom configuration handler and tried to use it for my own implementation.
I have set up App.config like this:
<configSections>
<section name="DocumentationSettings" type="ConfigHandler.DocumentationSettings,Settings"/>
</configSections>
<DocumentationSettings>
<DocumentationSections>
<DocumentationSection Id="AAA">
<SectionDescription Value="SectionDescriptionAAA"/>
</DocumentationSection>
<DocumentationSection Id="BBB">
<SectionDescription Value="SectionDescriptionBBB"/>
</DocumentationSection>
<DocumentationSection Id="CCC">
<SectionDescription Value="SectionDescriptionCCC"/>
</DocumentationSection>
</DocumentationSections>
</DocumentationSettings>
I use this code to access my custom configuration:
DocumentationSettings documentationSettings = ConfigurationManager.GetSection("DocumentationSettings") as DocumentationSettings;
foreach (DocumentationSectionConfigElement section in (documentationSettings.DocumentationSections.Sections))
{
Console.WriteLine(section.Id);
Console.WriteLine(section.SectionDescription.Properties.Value);
}
First 'Console.WriteLine' works perfect.
So I have the following problems and implementation related questions:
I get error on second 'Console.WriteLine', error: Unrecognized attribute 'Value'. I have put "public SectionDescription SectionDescription" since "DocumentationSectionConfigElement" Class exposes property access, but I might be wrong, I tried first to put it into "DocumentationSectionCollection" but I don't know how to implement it there and for me it seems that "DocumentationSectionCollection" only implements "Collection" logic.
I want to access "fields" either both like this:
section.Id
section.SectionDescription.Value
or like this:
section.Properties.Id
section.SectionDescription.Properties.Value
I see that "Collection" allows to use these properties directly using indexer methods like this:
public DocumentationSectionConfigElement this[int index]
But I can't implement indexer method on "SectionDescription" class, because it is a single section not a collection, so this "Properties" name persists when I access fields.
What do I need to add to be able to use LINQ on these configuration objects?
I mean like this:
(documentationSettings.DocumentationSections.Sections).Select(x => x.Id)
Are there any really good examples of complex XML structure configuration handler? From those which I found there were mostly simple structures like these:
but no any example of complex structure like this:
<section>
<subSections>
<subSection name="111">
<Description Value="AAA"></Description>
<Headers>
<Header type="Main">
<Properties>
<Property name="Property1"/>
<Property name="Property2"/>
<Property name="Property3"/>
</Properties>
</Header>
</Headers>
</subSection>
</subSections>
</section>
which is more close to real scenarios when you need to better organize application configuration.
What is the point of "sectionGroup" tag in "configSections" if "Custom Configuration Handler" works and it is enough with one "section" registered ?
Why all this stuff so complex? I have spent so much time these "custom configuration" things that shouldn't be so complex I believe. Aren't there any Visual Studio Add-ins that handle this stuff and generate code based on XML configuration structure?
Here is my Configuration Handler implementation:
public class DocumentationSettings : ConfigurationSection
{
[ConfigurationProperty("DocumentationSections")]
public DocumentationSections DocumentationSections
{
get { return (DocumentationSections)base["DocumentationSections"]; }
}
}
public class DocumentationSections : ConfigurationElement
{
[ConfigurationProperty("", IsDefaultCollection = true)]
public DocumentationSectionCollection Sections
{
get { return (DocumentationSectionCollection)base[""]; }
}
}
public class SectionDescription : ConfigurationElement
{
[ConfigurationProperty("SectionDescription")]
public new SectionDescriptionConfigElement Properties
{
get { return (SectionDescriptionConfigElement)base["SectionDescription"]; }
}
}
public class DocumentationSectionCollection : ConfigurationElementCollection
{
public DocumentationSectionCollection()
{
DocumentationSectionConfigElement details = (DocumentationSectionConfigElement)CreateNewElement();
if (details.Id != "")
{
Add(details);
}
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override ConfigurationElement CreateNewElement()
{
return new DocumentationSectionConfigElement();
}
protected override Object GetElementKey(ConfigurationElement element)
{
return ((DocumentationSectionConfigElement)element).Id;
}
public DocumentationSectionConfigElement this[int index]
{
get { return (DocumentationSectionConfigElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
new public DocumentationSectionConfigElement this[string name]
{
get { return (DocumentationSectionConfigElement)BaseGet(name); }
}
public int IndexOf(DocumentationSectionConfigElement details)
{
return BaseIndexOf(details);
}
public void Add(DocumentationSectionConfigElement details)
{
BaseAdd(details);
}
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
public void Remove(DocumentationSectionConfigElement details)
{
if (BaseIndexOf(details) >= 0)
BaseRemove(details.Id);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string name)
{
BaseRemove(name);
}
public void Clear()
{
BaseClear();
}
protected override string ElementName
{
get { return "DocumentationSection"; }
}
}
public class DocumentationSectionConfigElement : ConfigurationElement
{
[ConfigurationProperty("Id", IsRequired = true, IsKey = true)]
[StringValidator(InvalidCharacters = " ~!##$%^&*()[]{}/;’\"|\\")]
public string Id
{
get { return (string)this["Id"]; }
set { this["Id"] = value; }
}
[ConfigurationProperty("Name", IsRequired = false)]
public string Name
{
get { return (string)this["Name"]; }
set { this["Name"] = value; }
}
[ConfigurationProperty("SectionDescription")]
public SectionDescription SectionDescription
{
get { return ((SectionDescription)base["SectionDescription"]); }
}
}
public class SectionDescriptionConfigElement : ConfigurationElement
{
[ConfigurationProperty("Value", IsRequired = true)]
public string Value
{
get { return (string)this["Value"]; }
set { this["Value"] = value; }
}
}
Since no activity here, I will try answering my questions one by one:
Found this nice article that describes a lot of things and shows somewhat complex example of custom configuration implementation.
I found this great tool - ".NET Configuration Code Generator", that does exactly what I wanted - it takes XML structure and generates custom configuration code.

Does .net custom config section elements have to have keys?

Concider the following config section:
<PluginSection>
<Plugins>
<Plugin name="Plug1">
<add MessageType="1" MessageSubType="1" Ringtone="chime.wav" Vibrate="1000:0:1"/>
<add MessageType="1" MessageSubType="2" Ringtone="chime2.wav" Vibrate="1000:0:1"/>
</Plugin>
<Plugin name="Plug2">
<add MessageType="1" MessageSubType="1" Ringtone="chime.wav"/>
<add MessageType="1" MessageSubType="2" Ringtone="chime2.wav"/>
<add MessageType="2" Ringtone="chime3.wav"/>
</Plugin>
</Plugins>
</PluginSection>
I've implemented the parsing of this as a c# IConfigSectionHandler. Now I understand that this method is deprecated, and that I should use ConfigurationSection, ConfigurationElements and ConfigurationElementCollections. I have no problem understanding the examples of this on the web (msdn and SO). But all the examples I've seen so far have used one of the properties as a key. My elements are unique as a combination of the Plugin name, MessageType and MessageSubType. The MessageSubType is also optional. Could i parse a config section that looks like this using the recommended classes, or do I have to alter my configuration to fit the regime of the ConfigurationClasses by e.g. adding a "dummy" key?
No.
But to avoid keys you need to do more work.
The concrete type KeyValueConfigurationCollection allows easy creation of a configuration collection by setting some properties.
To create a more customised collection requires either extending the abstract ConfigurationElementCollection (but this will still be based on the add/remove/clear model as used by <appSettings>. but allowing the element names to be configured but this is still based on ahaving a key value for each member of the collection (this is determined by an override of GetElementKey so does not need to be directly included in the XML).
Alternatively you can create you own, completely custom configuration collection by extending ConfigurationElement, but you'll need to do all the work of parsing the child elements yourself (remember ConfigurationElementCollection is itself a child class of ConfigurationElement).
So based on the excelent answer from Richard, i decided to rewrite my config parsing. Heres the result:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace PluginConfiguration
{
public class PluginConfigSection : ConfigurationSection
{
[ConfigurationProperty("Plugins", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(PluginCollection), AddItemName = "Plugin")]
public PluginCollection Plugins
{
get
{
PluginCollection coll = (PluginCollection)base["Plugins"];
return coll;
}
}
}
public class PluginCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new MessageMappingElementCollection();
}
protected override object GetElementKey(ConfigurationElement element)
{
MessageMappingElementCollection coll = element as MessageMappingElementCollection;
return coll.Name;
}
}
public class MessageMappingElementCollection : ConfigurationElementCollection
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name
{
get { return this["name"].ToString(); }
set { this["name"] = value; }
}
protected override ConfigurationElement CreateNewElement()
{
return new MessageMappingElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
MessageMappingElement msgElement = element as MessageMappingElement;
string ret = String.Format("{0}|{1}", msgElement.MessageType, msgElement.MessageSubType);
return ret;
}
}
public sealed class MessageMappingElement : ConfigurationElement
{
public MessageMappingElement()
{
MessageType = 0;
MessageSubType = 0;
RingTone = "";
Description = "";
Vibrate = "";
}
[ConfigurationProperty("MessageType", IsRequired = true)]
public int MessageType
{
get { return int.Parse(this["MessageType"].ToString()); }
set { this["MessageType"] = value; }
}
[ConfigurationProperty("MessageSubType", IsRequired = false)]
public int MessageSubType
{
get { return int.Parse(this["MessageSubType"].ToString()); }
set { this["MessageSubType"] = value; }
}
[ConfigurationProperty("RingTone", IsRequired = false)]
public string RingTone
{
get { return this["RingTone"].ToString(); }
set { this["RingTone"] = value; }
}
[ConfigurationProperty("Description", IsRequired = false)]
public string Description
{
get { return this["Description"].ToString(); }
set { this["Description"] = value; }
}
[ConfigurationProperty("Vibrate", IsRequired = false)]
public string Vibrate
{
get { return this["Vibrate"].ToString(); }
set { this["Vibrate"] = value; }
}
}
}

how to have custom attribute in ConfigurationElementCollection?

for configuration as following
<MyCollection default="one">
<entry name="one" ... other attrubutes />
... other entries
</MyCollection>
when implement a MyCollection, what should i do for the "default" attribute?
Let's suppose you have this .config file:
<configuration>
<configSections>
<section name="mySection" type="ConsoleApplication1.MySection, ConsoleApplication1" /> // update type & assembly names accordingly
</configSections>
<mySection>
<MyCollection default="one">
<entry name="one" />
<entry name="two" />
</MyCollection>
</mySection>
</configuration>
Then, with this code:
public class MySection : ConfigurationSection
{
[ConfigurationProperty("MyCollection", Options = ConfigurationPropertyOptions.IsRequired)]
public MyCollection MyCollection
{
get
{
return (MyCollection)this["MyCollection"];
}
}
}
[ConfigurationCollection(typeof(EntryElement), AddItemName = "entry", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class MyCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new EntryElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return ((EntryElement)element).Name;
}
[ConfigurationProperty("default", IsRequired = false)]
public string Default
{
get
{
return (string)base["default"];
}
}
}
public class EntryElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name
{
get
{
return (string)base["name"];
}
}
}
you can read the configuration with the 'default' attribute, like this:
MySection section = (MySection)ConfigurationManager.GetSection("mySection");
Console.WriteLine(section.MyCollection.Default);
This will output "one"
I don't know if it's possible to have a default value in a ConfigurationElementCollection. (it doesn't seen to have any property for default value).
I guess you have to implement this by yourself. Look at the example below.
public class Repository : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get { return (string)this["key"]; }
}
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
}
}
public class RepositoryCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new Repository();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as Repository).Key;
}
public Repository this[int index]
{
get { return base.BaseGet(index) as Repository; }
}
public new Repository this[string key]
{
get { return base.BaseGet(key) as Repository; }
}
}
public class MyConfig : ConfigurationSection
{
[ConfigurationProperty("currentRepository", IsRequired = true)]
private string InternalCurrentRepository
{
get { return (string)this["currentRepository"]; }
}
[ConfigurationProperty("repositories", IsRequired = true)]
private RepositoryCollection InternalRepositories
{
get { return this["repositories"] as RepositoryCollection; }
}
}
Here's the XML config:
<myConfig currentRepository="SQL2008">
<repositories>
<add key="SQL2008" value="abc"/>
<add key="Oracle" value="xyz"/>
</repositories>
</myConfig>
And then, at your code, you access the default item using the following:
MyConfig conf = (MyConfig)ConfigurationManager.GetSection("myConfig");
string myValue = conf.Repositories[conf.CurrentRepository].Value;
Of course, the MyConfig class can hide the details of accessing the Repositories and CurrentRepository properties. You can have a property called DefaultRepository (of type Repository) in MyConfig class to return this.
This may be a bit late but may be helpful to others.
It is possible but with some modification.
ConfigurationElementCollection inherits ConfigurationElement as such "this[string]" is available in ConfigurationElement.
Usually when ConfigurationElementCollection is inherited and implemented in another class, the "this[string]" is hidden with "new this[string]".
One way to get around it is to create another implementation of this[] such as "this[string, string]"
See example below.
public class CustomCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new CustomElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CustomElement)element).Name;
}
public CustomElement this[int index]
{
get { return (CustomElement)base.BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
// ConfigurationElement this[string] now becomes hidden in child class
public new CustomElement this[string name]
{
get { return (CustomElement)BaseGet(name); }
}
// ConfigurationElement this[string] is now exposed
// however, a value must be entered in second argument for property to be access
// otherwise "this[string]" will be called and a CustomElement returned instead
public object this[string name, string str = null]
{
get { return base[name]; }
set { base[name] = value; }
}
}
If you want to genericize it, this should help:
using System.Configuration;
namespace Abcd
{
// Generic implementation of ConfigurationElementCollection.
[ConfigurationCollection(typeof(ConfigurationElement))]
public class ConfigurationElementCollection<T> : ConfigurationElementCollection
where T : ConfigurationElement, IConfigurationElement, new()
{
protected override ConfigurationElement CreateNewElement()
{
return new T();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((IConfigurationElement)element).GetElementKey();
}
public T this[int index]
{
get { return (T)BaseGet(index); }
}
public T GetElement(object key)
{
return (T)BaseGet(key);
}
}
}
Here's the interface referenced above:
namespace Abcd
{
public interface IConfigurationElement
{
object GetElementKey();
}
}

how to read in a list of custom configuration objects

I want to implement Craig Andera's custom XML configuration handler in a slightly different scenario. What I want to be able to do is to read in a list of arbitrary length of custom objects defined as:
public class TextFileInfo
{
public string Name { get; set; }
public string TextFilePath { get; set; }
public string XmlFilePath { get; set; }
}
I managed to replicate Craig's solution for one custom object but what if I want several?
Craig's deserialization code is:
public class XmlSerializerSectionHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
XPathNavigator nav = section.CreateNavigator();
string typename = (string)nav.Evaluate("string(#type)");
Type t = Type.GetType(typename);
XmlSerializer ser = new XmlSerializer(t);
return ser.Deserialize(new XmlNodeReader(section));
}
}
I think I could do this if I could get
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
to work but it throws
Could not load type 'System.Collections.Generic.List<Test1.TextFileInfo>' from assembly 'Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I don't think this would work in that scenario. Craig's solution works well for simple object graphs, but collections are a little trickier. Lists are serialised as arrays, so putting your example in, a serialised List is something like:
<ArrayOfTextFileInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlsns:xsd="http://www.w3.org/2001/XMLSchema>
<TextFileInfo>
<Name>My Text File</Name>
<TextFilePath>C:\MyTextFile.txt</TextFilePath>
<XmlFilePath>C:\MyXmlFile.xml</XmlFilePath>
</TextFileInfo>
</ArrayOfTextFileInfo>
Now, I'm guessing you could probably put that in a config, as long as the config section is named as "ArrayOfTextFileInfo". Not exactly that friendly. I think what you should probably do is use the standard configuration classes to build this:
public class TextFileConfigurationElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name {
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("textFilePath")]
public string TextFilePath {
get { return (string)this["textFilePath"]; }
set { this["textFilePath"] = value; }
}
[ConfigurationProperty("xmlFilePath")]
public string XmlFilePath {
get { return (string)this["xmlFilePath"]; }
set { this["xmlFilePath"] = value; }
}
}
[ConfigurationCollection(typeof(TextFileConfigurationElement))]
public class TextFileConfigurationElementCollection : ConfigurationElementCollection
{
protected override void CreateNewElement() {
return new TextFileConfigurationElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((TextFileConfigurationElement)element).Name;
}
}
public class TextFilesConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("files")]
public TextFileConfigurationElementCollection Files {
get { return (TextFileConfigurationElementCollection)this["files"]; }
set { this["files"] = value; }
}
public static TextFilesConfigurationSection GetInstance() {
return ConfigurationManager.GetSection("textFiles") as TextFilesConfigurationSection;
}
}
Once you've registered the config section:
<configSections>
<add name="textFiles" type="...{type here}..." />
</configSections>
You can add in the configs:
<textFiles>
<files>
<add name="File01" textFilePath="C:\File01.txt" xmlTextFile="C:\File01.xml" />
</files>
</textFiles>
Using that in code:
public List<TextFileInfo> GetFiles() {
var list = new List<TextFileInfo>();
var config = TextFileConfigurationSection.GetInstance();
if (config == null)
return list;
foreach (TextFileConfigurationElement fileConfig in config.Files) {
list.Add(new TextFileInfo
{
Name = fileConfig.Name,
TextFilePath = fileConfig.TextFilePath,
XmlFilePath = fileConfig.XmlFilePath
});
}
return list;
}
Also, this:
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
Won't work for a couple of reasons, you haven't fully qualified the TextFileInfo type (needs a namespace), and your definition of a generic type is wrong (I can see why you haven't specified it that way), it should look like:
Type t = Type.GetType("System.Collections.Generic.List`1[MyNamespace.TextFileInfo]");
Hope that helps!

Categories