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.
Related
I am trying to create a custom section in my web.config. I have a custom class and a declaration section in my web.config.
when I run my code however I am getting this error "...does not inherit from System.Configuration.IConfigurationSectionHandler'."
MSDN documentation states that this Interface is now deprecated.
I don't understand what cold be happening.
FYI I used this (http://blogs.perficient.com/microsoft/2017/01/4-easy-steps-to-custom-sections-in-web-config/ as my guide to create my custom section.
Here is my web.config
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<section name ="BDKConfigs" type="SPS.CardDecryption.Services.BDKConfigs" allowLocation="true" allowDefinition="Everywhere" />
</configSections>
Here is my custom section in the web.config
<BDKConfigs>
<!--collection-->
<BDK>
<!--elements-->
<add name="IDTechPublicBDK" value="0123456789ABCDEFFEDCBA9876543210"/>
<add name="IDTechProdBDK" value=""/>
</BDK>
</BDKConfigs>
this is my custom class
using System.Configuration;
namespace myNameSpace
{
//This class reads the defined config section (if available) and stores it locally in the static _Config variable.
public class BDKConfigs
{
public static BDKConfigsSection _Config = ConfigurationManager.GetSection("BDKConfigs") as BDKConfigsSection;
public static BDKElementCollection GetBDKGroups()
{
return _Config.BDKConfigs;
}
}
public class BDKConfigsSection : ConfigurationSection
{
//Decorate the property with the tag for your collection.
[ConfigurationProperty("BDK")]
public BDKElementCollection BDKConfigs
{
get { return (BDKElementCollection)this["BDK"]; }
}
}
//Extend the ConfigurationElementCollection class.
//Decorate the class with the class that represents a single element in the collection.
[ConfigurationCollection(typeof(BDKElement))]
public class BDKElementCollection : ConfigurationElementCollection
{
public BDKElement this[int index]
{
get { return (BDKElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new BDKElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((BDKElement)element).Name;
}
}
//Extend the ConfigurationElement class. This class represents a single element in the collection.
//Create a property for each xml attribute in your element.
//Decorate each property with the ConfigurationProperty decorator. See MSDN for all available options.
public class BDKElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("queryString", IsRequired = false)]
public string Value
{
get { return (string)this["Value"]; }
set { this["Value"] = value; }
}
}
}
The issue was that I had the wrong type specified in the web.config. When I changed my web.config to
type="SPS.CardDecryption.Services.BDKConfigsSection , worked like a charm.
I have updated answer:
public class BDKConfigsSection : ConfigurationSection
{
[ConfigurationProperty("BDK")]
public BDKElementCollection BDKConfigs
{
get { return (BDKElementCollection) base["BDK"]; }
}
}
[ConfigurationCollection(typeof(BDKElement))]
public class BDKElementCollection : ConfigurationElementCollection
{
public BDKElement this[int index]
{
get { return (BDKElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new BDKElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((BDKElement)element).Name;
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMapAlternate; }
}
protected override string ElementName => "BDKItem";
}
public class BDKElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("queryString", IsRequired = false)]
public string Value
{
get { return (string)this["queryString"]; }
set { this["queryString"] = value; }
}
}
that one working fine :
<BDKConfigsSection>
<BDK>
<BDKItem name="IDTechPublicBDK" queryString="0123456789ABCDEFFEDCBA9876543210"/>
<BDKItem name="IDTechProdBDK" queryString=""/>
</BDK> </BDKConfigsSection>
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.
I am writing a custom configuration section using the .NET configuration api. I want to define a section that can have exactly one of two sub-elements,
e.g.
<MyCustomSection>
<myItems>
<myItemType name="1">
<firstSubTypeConfig />
</myItemType>
<myItemType name="2">
<secondSubTypeConfig />
</myItemType>
</myItems>
</MyCustomSection>
Currently, I have the sections defined like:
public class MyCustomSection : ConfigurationSection
{
public override bool IsReadOnly()
{
return false;
}
[ConfigurationProperty("myItems")]
public MyItemsCollection MyItems
{
get { return (MyItemsCollection)base["myItems"]; }
set { base["myItems"] = value; }
}
}
[ConfigurationCollection(typeof(MyItemType), AddItemName="myItemType",
CollectionType=ConfigurationElementCollectionType.BasicMap)]
public class MyItemsCollection : ConfigurationElementCollection
{
public override bool IsReadOnly()
{
return false;
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName
{
get { return "myItemType"; }
}
protected override ConfigurationElement CreateNewElement()
{
return new MyItemType();
}
protected override object GetElementKey(ConfigurationElement element)
{
return (element as MyItemType).Name;
}
}
public class MyItemType : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
[ConfigurationProperty("name", IsRequired=true)]
public string Name
{
get { return (string)base["name"]; }
set { base["name"] = value; }
}
[ConfigurationProperty("firstSubTypeConfig")]
public FirstSubTypeConfig FirstSubTypeConfig
{
get { return (FirstSubTypeConfig)base["firstSubTypeConfig"]; }
set { base["firstSubTypeConfig"] = value; }
}
[ConfigurationProperty("secondSubTypeConfig")]
public SecondSubTypeConfig SecondSubTypeConfig
{
get { return (SecondSubTypeConfig)base["secondSubTypeConfig"]; }
set { base["secondSubTypeConfig"] = value; }
}
}
public class FirstSubTypeConfig : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
}
public class SecondSubTypeConfig : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
}
The configuration is also modified and saved programmatically, and currently, saving the
configuration section will add the secondSubTypeConfig element even if I only specify the
firstSubTypeConfig element.
My first thought was to introduce a common base class for FirstSubTypeConfig and SecondSubTypeConfig, but I'm not clear on if or how the configuration api would handle that.
How do I setup mutually exclusive custom config elements?
I'm not sure this is the "correct" approach, but this works.
The code as written in the question will load the config file fine. You can check the ElementInformation.IsPresent property to validate that exactly one element is included.
e.g.
var custom = (MyCustomSection)ConfigurationManager.GetSection("MyCustomSection");
foreach (MyItemType itemType in custom.MyItems)
{
if (itemType.FirstSubTypeConfig.ElementInformation.IsPresent
&& itemType.SecondSubTypeConfig.ElementInformation.IsPresent)
{
throw new ConfigurationErrorsException("At most one of firstSubTypeConfig or secondSubTypeConfig can be specified in a myItemType element");
}
else if (!itemType.FirstSubTypeConfig.ElementInformation.IsPresent
&& !itemType.SecondSubTypeConfig.ElementInformation.Ispresent)
{
throw new ConfigurationErrorsException("Either a firstSubTypeConfig or a secondSubTypeConfig element must be specified in a myItemType element");
}
}
As for saving the config, it seems that checking for ElementInformation.IsPresent and explicitly setting it to null will prevent the element from being written to the config file. e.g.
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var custom = (MyCustomSection)config.GetSection("MyCustomSection");
//make modifications against the custom variable ...
foreach (MyItemType itemType in custom.MyItems)
{
if (!itemType.FirstSubTypeConfig.ElementInformation.IsPresent)
itemType.FirstSubTypeConfig = null;
if (!itemType.SecondSubTypeConfig.ElementInformation.IsPresent)
itemType.SecondSubTypeConfig = null;
}
config.Save();
I think that you should do that on the PostDeserialize method ihnerited from the ConfigurationElement class without doing that on your business logic.
For example:
protected override void PostDeserialize()
{
base.PostDeserialize();
if (FirstSubTypeConfig != null && SecondTypeCOnfig != null)
{
throw new ConfigurationErrorsException("Only an element is allowed.");
}
}
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();
}
}
Example from MSDN on making a custom config section that should work as follows,
class RemoteServiceSection : ConfigurationSection
{
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
[ConfigurationCollection(typeof(RemoteServiceCollection), AddItemName="addService", ClearItemsName="clearServices",
RemoveItemName="removeService")]
public RemoteServiceCollection Services
{
get
{
return this["remoteServices"] as RemoteServiceCollection;
}
}
}
class RemoteServiceCollection : ConfigurationElementCollection, IList<RemoteServiceElement>
{
public RemoteServiceCollection()
{
RemoteServiceElement element = (RemoteServiceElement)CreateNewElement();
Add(element);
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.AddRemoveClearMap;
}
}
protected override ConfigurationElement CreateNewElement()
{
return new RemoteServiceElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((RemoteServiceElement)element).Hostname;
}
protected override string ElementName
{
get
{
return "remoteService";
}
}
public new IEnumerator<RemoteServiceElement> GetEnumerator()
{
foreach (RemoteServiceElement element in this)
{
yield return element;
}
}
public void Add(RemoteServiceElement element)
{
BaseAdd(element, true);
}
public void Clear()
{
BaseClear();
}
public bool Contains(RemoteServiceElement element)
{
return !(BaseIndexOf(element) < 0);
}
public void CopyTo(RemoteServiceElement[] array, int index)
{
base.CopyTo(array, index);
}
public bool Remove(RemoteServiceElement element)
{
BaseRemove(GetElementKey(element));
return true;
}
bool ICollection<RemoteServiceElement>.IsReadOnly
{
get { return IsReadOnly(); }
}
public int IndexOf(RemoteServiceElement element)
{
return BaseIndexOf(element);
}
public void Insert(int index, RemoteServiceElement element)
{
BaseAdd(index, element);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public RemoteServiceElement this[int index]
{
get
{
return (RemoteServiceElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
}
class RemoteServiceElement : ConfigurationElement
{
public RemoteServiceElement() { }
public RemoteServiceElement(string ip, string port)
{
this.IpAddress = ip;
this.Port = port;
}
[ConfigurationProperty("hostname", IsKey = true, IsRequired = true)]
public string Hostname
{
get
{
return (string)this["hostname"];
}
set
{
this["hostname"] = value;
}
}
[ConfigurationProperty("ipAddress", IsRequired = true)]
public string IpAddress
{
get
{
return (string)this["ipAddress"];
}
set
{
this["ipAddress"] = value;
}
}
[ConfigurationProperty("port", IsRequired = true)]
public string Port
{
get
{
return (string)this["port"];
}
set
{
this["port"] = value;
}
}
}
}
I am getting the error that says 'Unrecognized element 'addService'. I think I've followed the MSDN article exactly. It can be found here - http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx
Thanks in advance for your help. This is what I wrote in app.config (with brackets of course that dont show up here?):
<remoteServices>
<addService hostname="xxxxxxx" ipAddress="xxx.x.xxx.xx" port="xxxx" >
</remoteServices>
Here is app.config as requested, x'ing out the specific names just for privacy purposes, they are just strings:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
For future generations:
Your config should look like this:
<configuration>
<configSections>
<section name="remoteServices" type="AqEntityTests.RemoteServiceSection,
AqEntityTests" allowLocation="true" allowDefinition="Everywhere"/>
</configSections>
<remoteServices>
<remoteServices>
<addService hostname="xxxxxx.xxxxxxx.com"
ipAddress="xxx.x.xxx.xx"
port="xx" />
</remoteServices>
</remoteServices>
</configuration>
Why?
You add to node:
<configSections>
custom section named:
name="remoteServices"
with type
type="AqEntityTests.RemoteServiceSection
and then in code, you add property to your custom section:
[ConfigurationProperty("remoteServices", IsDefaultCollection=false)]
Meaning you created node inside node with both having same name. Because of that you have received error "Unrecognized element 'addService'". Just compiler informing you that such element should not be in that node.
Two links for quick learning of custom configuration:
Custom Configuration Sections for Lazy Coders
How to create sections with collections
You might also look into using an unnamed default collection as I mention here
That allows you to add items in the manner you suggest.