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.
Related
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.
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 made a small custom configuration setting and I keep getting the error "The entry '' has already been added." when I try to use my custom collection. My code looks like this.
The issue comes from my tag.
I don't see what I am missing since I have the same thing implemented for and this one works perfectly.
My .NET version is 4.0 if that helps.
The app config section in question:
<WorkersCollectionSection>
<WorkersList>
<Worker name="Category" isEnabled="false"
assemblyNamespace="xxxxxx.xxxxxxxxxxxxxxx.Models.Category"
queueName="CategorQueue"
saveToFolder="false">
<HandlesList>
<Handle name="xxxxxxx" isEnabled="true"/>
<Handle name="yyyyyyy" isEnabled="true"/>
<Handle name="zzzzzzzzzz" isEnabled="true"/>
</HandlesList>
</Worker>
<WorkersList>
<WorkersCollectionSection>
The definition of the property:
[ConfigurationProperty("HandlesList")]
[ConfigurationCollection(typeof(WorkerCollection), AddItemName = "Handle")]
public HandleCollection HandleCollection
{
get { return (HandleCollection) base["HandlesList"]; }
}
The code for the tag :` public class HandleCollection : ConfigurationElementCollection, IEnumerable
{
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
BaseAdd(handle);
}
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.AddRemoveClearMap; }
}
protected override ConfigurationElement CreateNewElement()
{
return new HandleElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((HandleElement)element).Name;
}
public HandleElement this[int index]
{
get { return (HandleElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public new HandleElement this[string Name]
{
get { return (HandleElement)BaseGet(Name); }
}
public int IndexOf(HandleElement handle)
{
return BaseIndexOf(handle);
}
public void Add(HandleElement url)
{
BaseAdd(url);
}
protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}
public void Remove(HandleElement handle)
{
if (BaseIndexOf(handle) >= 0)
BaseRemove(handle);
}
public void RemoveAt(int index)
{
BaseRemoveAt(index);
}
public void Remove(string name)
{
BaseRemove(name);
}
public void Clear()
{
BaseClear();
}
IEnumerator<ConfigurationElement> IEnumerable<ConfigurationElement>.GetEnumerator()
{
return (from i in Enumerable.Range(0, this.Count)
select this[i])
.GetEnumerator();
}
protected override string ElementName
{
get { return "Handle"; }
}
public static explicit operator HandleCollection(Dictionary<string, string> v)
{
throw new NotImplementedException();
}
public static explicit operator HandleCollection(ConfigurationSection v)
{
throw new NotImplementedException();
}
}`
Code for the handle elements inside the list:
public class HandleElement: ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
[StringValidator(InvalidCharacters = "~!##$%^&*()[]{}/;'\"|\\", MinLength = 0, MaxLength = 60)]
public string Name
{
get { return base["name"] as string; }
set { base["name"] = value; }
}
[ConfigurationProperty("isEnabled", IsRequired = true)]
public bool IsEnabled
{
get { return (bool)base["isEnabled"]; }
set { base["isEnabled"] = value; }
}
}
It seems that the issue was that I did not put a check in the collection adding method to check if the element I am adding has a different key than the empty one. I changed my collection constructor to :
public HandleCollection()
{
HandleElement handle = (HandleElement)CreateNewElement();
if (handle.Name != "")
BaseAdd(handle);
}
Now it seems to work fine.
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.
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.");
}
}