Global editable config settings in C#.NET? - c#

I want the best of both worlds: I want to be able to persist changes during runtime, like the User scoped application settings can, and I also want these settings to be global. Is there some way to accomplish this through app.config settings files? Should I look at some other way to persist global, runtime editable settings for my application?

The built in configuration manager in .Net which is used to deal with application settings in config files is read-only so technically, you can't do it using the built in libraries, however, the config file is just xml, so there's no reason why you can't just update the config file using the standard xml methods and then call
ConfigurationManager.RefreshSection("appSettings")
when you want to reload your settings

Furthermore, the OpenMappedExeConfiguration() Method of the ConfigurationManager lets you dynamically load a config file of your choosing (granted it follows the .NET xml config schema) and have your application load configuariton options from it, so you can modifcations to the file as #lomax indicated and have a common file that you can load from all your applications, using this same method.
Here's some info on OpenMappedExeConfiguration

Ok, this is how I solved it:
I created really basic ConfigurationSection, ConfigurationElement, and ConfigurationElementCollection implementations:
public class CoreConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("settings", IsDefaultCollection = true)]
[ConfigurationCollection(typeof(CoreSettingCollection), AddItemName = "setting")]
public CoreSettingCollection Settings
{
get
{
return (CoreSettingCollection)base["settings"];
}
}
}
public class CoreSetting : ConfigurationElement
{
public CoreSetting() { }
public CoreSetting(string name, string value)
{
Name = name;
Value = value;
}
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("value", DefaultValue = null, IsRequired = true, IsKey = false)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class CoreSettingCollection : ConfigurationElementCollection
{
public new string this[string name]
{
get { return BaseGet(name) == null ? string.Empty : ((CoreSetting)BaseGet(name)).Value; }
set { Remove(name); Add(name, value); }
}
public void Add(string name, string value)
{
if (!string.IsNullOrEmpty(value))
BaseAdd(new CoreSetting(name, value));
}
public void Remove(string name)
{
if (BaseGet(name) != null)
BaseRemove(name);
}
protected override ConfigurationElement CreateNewElement()
{
return new CoreSetting();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CoreSetting)element).Name;
}
}
And then a class to manage the configuration file:
public static class Settings
{
private static string _root { get { return "core"; } }
private static Configuration Load()
{
string filename = Path.Combine(Core.BaseDirectory, "core.config");
var mapping = new ExeConfigurationFileMap {ExeConfigFilename = filename};
var config = ConfigurationManager.OpenMappedExeConfiguration(mapping, ConfigurationUserLevel.None);
var section = (CoreConfigurationSection)config.GetSection(_root);
if (section == null)
{
Console.Write("Core: Building core.config...");
section = new CoreConfigurationSection();
config.Sections.Add(_root, section);
Defaults(section);
config.Save(ConfigurationSaveMode.Modified);
Console.WriteLine("done");
}
return config;
}
private static void Defaults(CoreConfigurationSection section)
{
section.Settings["Production"] = "false";
section.Settings["Debug"] = "false";
section.Settings["EventBot"] = "true";
section.Settings["WebAccounting"] = "true";
section.Settings["AllowPlayers"] = "true";
}
#region Accessors
public static string Get(string setting)
{
var config = Load();
var section = (CoreConfigurationSection)config.GetSection(_root);
return section.Settings[setting];
}
public static bool GetBoolean(string setting)
{
var config = Load();
var section = (CoreConfigurationSection)config.GetSection(_root);
return section.Settings[setting].ToLower() == "true";
}
public static void Set(string setting,string value)
{
var config = Load();
var section = (CoreConfigurationSection)config.GetSection(_root);
if (value == null)
section.Settings.Remove(setting);
section.Settings[setting] = value;
config.Save(ConfigurationSaveMode.Modified);
}
public static void SetBoolean(string setting, bool value)
{
var config = Load();
var section = (CoreConfigurationSection)config.GetSection(_root);
section.Settings[setting] = value.ToString();
config.Save(ConfigurationSaveMode.Modified);
}
#endregion
#region Named settings
public static bool Production
{
get { return GetBoolean("Production"); }
set { SetBoolean("Production", value); }
}
public static bool Debug
{
get { return GetBoolean("Debug"); }
set { SetBoolean("Debug", value); }
}
public static bool EventBot
{
get { return GetBoolean("EventBot"); }
set { SetBoolean("EventBot", value); }
}
public static bool WebAccounting
{
get { return GetBoolean("WebAccounting"); }
set { SetBoolean("WebAccounting", value); }
}
public static bool AllowPlayers
{
get { return GetBoolean("AllowPlayers"); }
set { SetBoolean("AllowPlayers", value); }
}
#endregion
}
I couldn't really think of a better way to make typed configurations than hardcoding them, but other than that it seems pretty solid to me, you can create and update configurations at runtime, they are global, editable, and located on my application root, so basically that covers all features I wanted.
The core.config file is created at runtime if it doesn't exist, this is verified whenever you try to load or save a setting, with some default values just to "get started"... (you can skip that "initialization" though.
The core.config file looks like this
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="core" type="Server.CoreConfigurationSection, ServerCore, Version=2.1.4146.38077, Culture=neutral, PublicKeyToken=null" />
</configSections>
<core>
<settings>
<setting name="Production" value="false" />
<setting name="Debug" value="false" />
<setting name="EventBot" value="true" />
<setting name="WebAccounting" value="true" />
<setting name="AllowPlayers" value="true" />
</settings>
</core>
</configuration>

Related

How to implement a custom "ConfigurationSection" with a nested "ConfigurationElementCollection" containing a custom "Element"

I am trying to implement a custom configuration section containing a collection of another custom element. The customer element contains some simple strings but also a collection of certificateReference.
I have only included one instance of <it2.jwtAuthorisation> for now in the web.config, but this should be able to have multiple.
The issue I'm having is when loading the configuration I get the following error:
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request.
Parser Error Message: Unrecognized element 'audience'.
Source Error:
Line 15: <it2.AuthorisationSchemes>
Line 16: <it2.jwtAuthorisation>
Line 17: <audience aud="https://localhost" />
I have tried changing the classes several times but without any luck.
This is the web.config file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<configSections>
<section name="it2.AuthorisationSchemes" type="WebAPI.Authentication.Configuration.JWT.MultipleCertAuthorisationConfigurationSection, WebAPI, Version=1.0.0.0, Culture=neutral" />
</configSections>
<it2.AuthorisationSchemes>
<it2.jwtAuthorisation>
<audience aud="https://localhost" />
<issuer iss="IT2" />
<certificateSigningKeys>
<certificateReference x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" findValue="IT2.AccessTokenSigningKey" />
</certificateSigningKeys>
</it2.jwtAuthorisation>
</it2.AuthorisationSchemes>
</configuration>
This is the MultipleCertAuthorisationConfigurationSection definition:
public class MultipleCertAuthorisationConfigurationSection : ConfigurationSection
{
private const string authSchemes = "it2.jwtAuthorisation";
[ConfigurationProperty(authSchemes, IsRequired = true)]
[ConfigurationCollection(typeof(JWTAuthorisationCollection),
AddItemName = "add",
ClearItemsName = "clear",
RemoveItemName = "remove")]
public JWTAuthorisationCollection jwtAuthSchemes
{
get
{
JWTAuthorisationCollection jwtAuthorisationCollection =
(JWTAuthorisationCollection)base[authSchemes];
return jwtAuthorisationCollection;
}
set
{
JWTAuthorisationCollection jwtAuthorisationCollection = value;
}
}
}
This is the JWTAuthorisationCollection definition:
public class JWTAuthorisationCollection : ConfigurationElementCollection
{
public JWTAuthorisationCollection()
{
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.AddRemoveClearMap;
}
}
protected override ConfigurationElement CreateNewElement()
{
return new JWTAuthorisationElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((JWTAuthorisationElement)element).Issuer;
}
public JWTAuthorisationElement this[int index]
{
get
{
return (JWTAuthorisationElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
new public JWTAuthorisationElement this[string Issuer]
{
get
{
return (JWTAuthorisationElement)BaseGet(Issuer);
}
}
public int IndexOf(JWTAuthorisationElement jwtAuth)
{
return BaseIndexOf(jwtAuth);
}
public void Add(JWTAuthorisationElement jwtAuth)
{
BaseAdd(jwtAuth);
}
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
public void Remove(JWTAuthorisationElement jwtAuth)
{
if (BaseIndexOf(jwtAuth) >= 0)
{
BaseRemove(jwtAuth.Issuer);
}
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string issuer)
{
BaseRemove(issuer);
}
public void Clear()
{
BaseClear();
}
}
This is the JWTAuthorisationElement definition:
public class JWTAuthorisationElement : ConfigurationElement
{
public JWTAuthorisationElement(AudienceProviderElement audience, IssuerProviderElement issuer,
JWKSEndpointProviderElement jwksEndpoint, MultipleCertReferenceSigningKeyProviderElements certificateSigningKeys, AppSecretSigningKeyProviderElement appSecretSigningKey)
{
Audience = audience;
Issuer = issuer;
JWKSEndpoint = jwksEndpoint;
CertificateSigningKeys = certificateSigningKeys;
AppSecretSigningKey = appSecretSigningKey;
}
public JWTAuthorisationElement()
{
}
private const string audience = "audience";
[ConfigurationProperty(audience, IsRequired = true)]
public AudienceProviderElement Audience
{
get
{
return this[audience] as AudienceProviderElement;
}
set
{
this[audience] = value;
}
}
private const string issuer = "issuer";
[ConfigurationProperty(issuer, IsKey = true, IsRequired = true)]
public IssuerProviderElement Issuer
{
get
{
return this[issuer] as IssuerProviderElement;
}
set
{
this[issuer] = value;
}
}
private const string jwksEndpoint = "JWKSEndpoint";
[ConfigurationProperty(jwksEndpoint, IsRequired = false)]
public JWKSEndpointProviderElement JWKSEndpoint
{
get
{
return this[jwksEndpoint] as JWKSEndpointProviderElement;
}
set
{
this[jwksEndpoint] = value;
}
}
private const string certificateSigningKeys = "certificateSigningKeys";
[ConfigurationProperty(certificateSigningKeys, IsRequired = false)]
[ConfigurationCollection(typeof(MultipleCertReferenceSigningKeyProviderElements), AddItemName = "certificateReference")]
public MultipleCertReferenceSigningKeyProviderElements CertificateSigningKeys
{
get
{
return this[certificateSigningKeys] as MultipleCertReferenceSigningKeyProviderElements;
}
set
{
this[certificateSigningKeys] = value;
}
}
private const string appSecretSigningKey = "appSecretSigningKey";
[ConfigurationProperty(appSecretSigningKey, IsRequired = false)]
public AppSecretSigningKeyProviderElement AppSecretSigningKey
{
get
{
return this[appSecretSigningKey] as AppSecretSigningKeyProviderElement;
}
set
{
this[appSecretSigningKey] = value;
}
}
}
It gets loaded by the following function and this is where the error occurs:
public AuthorisationConfigurationFactory()
: this(System.Configuration.ConfigurationManager.GetSection("it2.AuthorisationSchemes") as JWT.MultipleCertAuthorisationConfigurationSection)
{
}
I managed to solve this after finding Correct implementation of a custom config section with nested collections?. The Answer was exactly what I was looking for and the linked blog post gave a detailed description with the code on how to do this.
It seems like another person in the comments had the same issue following the Microsoft example:
The key for me here was setting CollectionType on the nested
collection to ConfigurationElementCollectionType.BasicMap. Without it
I kept getting Unrecognized element 'tunnel'
This was also the error I was receiving.

Cannot get custom configuration section to work in app.config

I followed a tutorial on Stackoverflow to add a custom configuration section to my exe's config file, however on calling it, it is returning null. It's not even getting into the static constructors so something is clearly wrong, but I can't see what.
Here is my config file and the section I wish to find.
<configuration>
<appSettings>
</appSettings>
<configSections>
<section name="PresetFilters" type="ImageTool.PresetFiltersConfiguration, ImageTool" />
</configSections>
<PresetFilters>
<add key="Default,-20,0,0,0,0" />
<add key="No Change,0,0,0,0,0" />
<add key="Dark Photo,10,10,0,0,-10" />
</PresetFilters>
</configuration>
I call it like this:
PresetFiltersConfiguration pf = (PresetFiltersConfiguration)ConfigurationManager.GetSection("PresetFilters");
and it returns null and doesn't even enter my class or class statics. Here's the code. Any help would be appreciated. Thanks.
public class PresetFiltersConfiguration : ConfigurationSection
{
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propPresets;
static PresetFiltersConfiguration()
{
propPresets = new ConfigurationProperty(null, typeof(PresetFiltersElementCollection),
null,
ConfigurationPropertyOptions.IsDefaultCollection);
properties = new ConfigurationPropertyCollection { propPresets };
}
protected override ConfigurationPropertyCollection Properties
{
get
{
return properties;
}
}
public PresetFiltersElementCollection PresetFilter
{
get
{
return this[propPresets] as PresetFiltersElementCollection;
}
}
}
public class PresetFiltersElementCollection : ConfigurationElementCollection
{
public PresetFiltersElementCollection()
{
properties = new ConfigurationPropertyCollection();
}
private static ConfigurationPropertyCollection properties;
protected override ConfigurationPropertyCollection Properties
{
get
{
return properties;
}
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.BasicMap;
}
}
protected override string ElementName
{
get
{
return "add";
}
}
protected override ConfigurationElement CreateNewElement()
{
return new PresetFiltersElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
var elm = element as PresetFiltersElement;
if (elm == null) throw new ArgumentNullException();
return elm.KeyName;
}
}
public class PresetFiltersElement : ConfigurationElement
{
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propKey;
protected override ConfigurationPropertyCollection Properties
{
get
{
return properties;
}
}
public PresetFiltersElement()
{
propKey = new ConfigurationProperty("key", typeof(string),
null,
ConfigurationPropertyOptions.IsKey);
properties = new ConfigurationPropertyCollection { propKey };
}
public PresetFiltersElement(string keyName)
: this()
{
KeyName = keyName;
}
public string KeyName
{
get
{
return this[propKey] as string;
}
set
{
this[propKey] = value;
}
}
}
You need something like this in your app.config, otherwise the application won't know how to process your new section.
<configSections>
<sectionGroup name="pageAppearanceGroup">
<section
name="PresetFilters"
type="PresetFiltersConfiguration"
allowLocation="true"
allowDefinition="Everywhere"
/>
</sectionGroup>
</configSections>
After applying the fixes it worked, but the actual fault is to do with it not working for class libraries, and I've created a new question for this.
Thanks.

Not able to save changes to config file

I've been following this Stackoverflow topic. Everything works on the read side. I get a collection which I transform into a dictionary of the section contents. I publish this to other projects. The problems occur when I try to save a modified section entry. One of the external projects send a key/Value pair and I need to save it to my section. Here is my App.Config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="fileEnvironmentSection" type="MyApp.Infrastructure.Configuration.FileEnvironmentSection, MyApp.Infrastructure"/>
</configSections>
<fileEnvironmentSection>
<fileEnvironment>
<add key="TestEntry1" value="A nice value"/>
<add key="TestEntry2" value="Another value"/>
</fileEnvironment>
</fileEnvironmentSection>
</configuration>
Here are the three layers of types needed to interact with the config section.
using System.Configuration;
namespace SlamDunk.Infrastructure.Configuration {
public class FileEnvironmentSection : ConfigurationSection {
[ConfigurationProperty("fileEnvironment", IsDefaultCollection = false)]
[ConfigurationCollection(typeof(FileEnvironmentCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
public FileEnvironmentCollection FileEnvironmentList {
get { return (FileEnvironmentCollection)base["fileEnvironment"]; }
}
}
}
using System.Configuration;
namespace SlamDunk.Infrastructure.Configuration {
public class FileEnvironmentCollection : ConfigurationElementCollection {
public FileEnvironmentElement this[int index] {
get { return (FileEnvironmentElement)BaseGet(index); }
set {
if(BaseGet(index) != null) BaseRemoveAt(index);
BaseAdd(index, value);
}
}
public void Add(FileEnvironmentElement fileEnvironmentElement) { BaseAdd(fileEnvironmentElement); }
public void Clear() { BaseClear(); }
protected override ConfigurationElement CreateNewElement() { return new FileEnvironmentElement(); }
protected override object GetElementKey(ConfigurationElement element) { return ((FileEnvironmentElement)element).Key; }
public void Remove(FileEnvironmentElement fileEnvironmentElement) { BaseRemove(fileEnvironmentElement.Key); }
public void Remove(string key) { BaseRemove(key); }
public void RemoveAt(int index) { BaseRemoveAt(index); }
}
}
using System.Configuration;
namespace SlamDunk.Infrastructure.Configuration {
public class FileEnvironmentElement : ConfigurationElement {
private const string appConfigDefaultString = "missing";
private const string _appConfigNameKey = "key";
private const string _appConfigNameValue = "value";
public FileEnvironmentElement() { }
public FileEnvironmentElement(string key, string value) {
Key = key;
Value = value;
}
[ConfigurationProperty(_appConfigNameKey, DefaultValue = appConfigDefaultString, IsRequired = true, IsKey = true)]
public string Key {
get { return (string)this[_appConfigNameKey]; }
set { this[_appConfigNameKey] = value; }
}
[ConfigurationProperty(_appConfigNameValue, DefaultValue = appConfigDefaultString, IsRequired = true, IsKey = false)]
public string Value {
get { return (string)this[_appConfigNameValue]; }
set { this[_appConfigNameValue] = value; }
}
}
}
Here is the code I'm using to save a key/value change.
var appConfiguration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var fileEnvironmentSection = appConfiguration.GetSection("fileEnvironmentSection") as FileEnvironmentSection;
var fileEnvironmentList = fileEnvironmentSection.FileEnvironmentList;
fileEnvironmentList.Remove(key);
var element = new FileEnvironmentElement(key, value);
fileEnvironmentList.Add(element);
//appConfiguration.Save(ConfigurationSaveMode.Modified);
//fileEnvironmentSection.CurrentConfiguration.Save(ConfigurationSaveMode.Modified);
fileEnvironmentList.CurrentConfiguration.Save(ConfigurationSaveMode.Modified);
I've checked and the changes appear in the list as expected. I've tried the two commented save call plus the last one. I think I'm getting a new instance of FileEnvironmentSection to avoid ConfigurationManager caching issues. After each test run I look in MyApp.exe.config and do not find any changes. I'm missing something and could use some help figuring out what. Thanks.
appConfiguration.Save(ConfigurationSaveMode.Modified, true);
appConfiguration.Save(ConfigurationSaveMode.Full, true);
the latter should force a full save of the config file, i think the former will as well.
i noticed that you are re-adding the same key to you collect, possibly that's why it is not picked up as modified?

Custom Configuration Collection - Unrecognized element 'addService'

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.

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