Answered by:
Custom Config Section, .NET 2.0

Question
-
User-1328050895 posted
I'm trying to write a custom config section in .NET 2.0. I'm having a hard time finding broad examples of how to use the ConfigSection, ConfigElementCollection and ConfigElement classes...particularly the ElementCollection. Of course, my code look ok to me but not the app, or I wouldn't be here. ;-) I get various errors, mostly non-specific, like "Exception has been thrown by the target of an invocation".
Can anyone help me find where I went astray here?
Here's the XML (ignore the erronious values):
<uploadDirector> <filegroup name="documents" defaultDirectory="/documents/"> <clear/> <add extention="pdf" mime="application/pdf" maxsize="100"/> <add extention="doc" mime="application/word" maxsize="500"/> </filegroup> <filegroup name="images"> <clear/> <add extention="gif" mime="image/gif" maxsize="100"/> </filegroup> </uploadDirector>
Here's the ConfigSection class:
Public Class UploadDirectorConfigSection Inherits ConfigurationSection Private _rootpath As String Public Sub New() End Sub <ConfigurationProperty("rootpath", DefaultValue:="/", IsRequired:=False, IsKey:=False)> _ <StringValidator(InvalidCharacters:="~!.@#$%^&*()[]{};'\|\\")> _ Public Property RootPath() As String Get Return _rootpath End Get Set(ByVal value As String) _rootpath = value End Set End Property <ConfigurationProperty("", IsRequired:=True, IsKey:=False, IsDefaultCollection:=True)> _ Public Property FileGroups() As FileGroupCollection Get Return MyBase.Item("") End Get Set(ByVal value As FileGroupCollection) MyBase.Item("") = value End Set End Property End Class
And here's the first collection, FileGroupCollection
Public Class FileGroupCollection Inherits ConfigurationElementCollection Protected Overrides Function CreateNewElement() As System.Configuration.ConfigurationElement Return New FileGroupElement End Function Protected Overrides Function GetElementKey(ByVal element As System.Configuration.ConfigurationElement) As Object Return DirectCast(element, FileGroupElement).name End Function Public Overrides ReadOnly Property CollectionType() As ConfigurationElementCollectionType Get Return ConfigurationElementCollectionType.BasicMap End Get End Property Protected Overrides ReadOnly Property ElementName() As String Get Return "filegroup" End Get End Property Protected Overrides Function IsElementName(ByVal elementName As String) As Boolean If ((String.IsNullOrEmpty(elementName)) Or (elementName <> "filegroup")) Then Return False End If Return True End Function Public Overloads Property Item(ByVal index As Integer) As FileGroupElement Get Return DirectCast(MyBase.BaseGet(index), FileGroupElement) End Get Set(ByVal value As FileGroupElement) If Not MyBase.BaseGet(index) Is Nothing Then MyBase.BaseRemoveAt(index) End If MyBase.BaseAdd(index, value) End Set End Property End Class
The FileGroupElement class:
Public Class FileGroupElement Inherits ConfigurationElement <ConfigurationProperty("name", IsKey:=True, IsRequired:=True)> _ <StringValidator(InvalidCharacters:=" ~.!@#$%^&*()[]{}/;'""|\")> _ Public Property Name() As String Get Return CType(MyBase.Item("name"), String) End Get Set(ByVal value As String) MyBase.Item("name") = value End Set End Property 'TODO:RegExp validate this <ConfigurationProperty("defaultDirectory", DefaultValue:=".")> _ Public Property DefaultDirectory() As String Get Return MyBase.Item("Path") End Get Set(ByVal value As String) MyBase.Item("Path") = value End Set End Property <ConfigurationProperty("", IsDefaultCollection:=True, IsRequired:=True)> _ Public ReadOnly Property Files() As FileInfoCollection Get Return MyBase.Item("") End Get End Property End Class
Next, the FileInfoCollection, which should be the default type of AddRemoveClearMap:
Public Class FileInfoCollection Inherits ConfigurationElementCollection Protected Overrides Function CreateNewElement() As System.Configuration.ConfigurationElement Return New FileInfoElement() End Function Protected Overrides Function GetElementKey(ByVal element As System.Configuration.ConfigurationElement) As Object Return DirectCast(element, FileInfoElement).FileExtention End Function End Class
And Lastly, the FileInfoElement:
Public Class FileInfoElement Inherits ConfigurationElement Public Sub New() Me.FileExtention = "txt" Me.MIME = "text/plain" Me.MaxSize = 0 End Sub <ConfigurationProperty("extention", IsKey:=True, IsRequired:=True)> _ Public Property FileExtention() As String Get Return MyBase.Item("Extention") End Get Set(ByVal value As String) MyBase.Item("Extention") = value End Set End Property <ConfigurationProperty("mime", DefaultValue:="text/plain")> _ Public Property MIME() As String Get Return MyBase.Item("MIME") End Get Set(ByVal value As String) MyBase.Item("MIME") = value End Set End Property <ConfigurationProperty("maxsize", DefaultValue:="0")> _ <IntegerValidator(MinValue:=0)> _ Public Property MaxSize() As Integer Get Return MyBase.Item("MaxSize") End Get Set(ByVal value As Integer) MyBase.Item("MaxSize") = value End Set End Property <ConfigurationProperty("validatorType")> _ Public Property FileValidatorType() As String Get Return MyBase.Item("ValidatorType") End Get Set(ByVal value As String) MyBase.Item("ValidatorType") = value End Set End Property End Class
Thursday, August 24, 2006 6:25 PM
Answers
-
User-1328050895 posted
Well, I did finaly get this working. I'll post some links and some advice here for anyone else who is looking.
One of my major problems with implementing a custom config section in .NET 2.0 was the lack of solid documentation. It's there, but not very deep, and the "example below" examples all seem to be missing at the time I'm writing this. I was able to piece things together thanks to some tenacious people with blogs. Here's a list of links that helped me out, in no particular order.
http://fredrik.nsquared2.com/viewpost.aspx?PostID=233
http://msdn.microsoft.com/en-us/library/ms228062.aspx
http://blogs.msdn.com/markgabarra/archive/2006/06/27/648742.aspx
http://blogs.msdn.com/markgabarra/archive/2006/06/25/646775.aspx
http://msdn.microsoft.com/msdnmag/issues/06/06/ConfigureThis/
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=132716&SiteID=1&mode=1
http://www.agileprogrammer.com/oneagilecoder/archive/2005/06/11/3689.aspx
http://jason.diamond.name/weblog/2006/05/25/custom-configuration-section-validator-weirdness
That last link was particularly helpful; it set me in the right direction. Jason Diamond admits that he didn't look deep enough into the problem to find the reason, but I think I may have found the answer in the Microsoft forums (third last link).
In my mind, the declarative programming model for custom config sections is broken. The issue is with default values, validators, and the ConfigurationElement base class constructor.
It seems that the ConfigurationElement base constructor attempts to set "default" values for your ConfigurationElement's properties. If it's an integer property, the ConfigurationElement constructor will attempt to assign a zero to it, "" for a String etc. Reasonably, this default assignment is still passed through any validation you may have assigned to the property declaritively using an attribute, since the declarative validation attribute's routines are attached to the properties before the constructor attempts to assign a default value (I think).
However, as Jason Diamond and Paul D. Murphy point out, this becomes a problem if, for example, your validation says that your integer property needs to be between 2 and 5; zero is invalid. OK, so you also decorate your property with the DefaultValue attribute and inform the ConfigurationElement constuctor that when it assigns a value to that property, it should use, say, 3 instead of zero. Now the framework can instantiate your ConfigurationElement derived class without the validator throwing an error. But.
Now you have a property of your ConfigurationElement that has a default value. Default value...that means that the implementor/user of your custom configuration section is no longer required to type in a value for that property (an attribute on an xml tag in the config file). What if you wanted to require that the implementor type a value? You're stuck, if you wanted to use the declarative model to code your custom config section. The only solution is to use the programatic model.
Justin Diamonds post shows how this can be done. http://jason.diamond.name/weblog/2006/05/25/custom-configuration-section-validator-weirdness
Another Tip:
If you want to have a ConfigurationElementCollection in your config section without actually having a parent tag around the collection, declare the collection name in the ConfigurationProperty attribute as an empty string. For example if you have a <MyConfigSection> section with a collection of FileGroup elements, you can code:
<ConfigurationProperty("", IsRequired:=True, IsDefaultCollection:=True)> _ Public Property FileGroups() As FileGroupCollection Get Return CType(Me(""), FileGroupCollection) End Get Set(ByVal value As FileGroupCollection) Me("") = value End Set End Property
which will give you:
<MySection> <FileGroup/> <FileGroup/> <FileGropu/> <MySection>
as opposed to
<ConfigurationProperty("FileGroups", IsRequired:=True, IsDefaultCollection:=True)>
which would require the section to look like:
<MySection> <FileGroups> <FileGroup/> <FileGroup/> <FileGropu/> </FileGroups> <MySection>
Something Odd
Most of the example code I've found on the web write the property accessors as I did above, using Me("someName") (or base["someName"] in C#) to store the values. Reading the documentation, it looks like the default the default property of the ConfigurationElement is Item(), but tracing through the code I found that the values I was trying to retrieve could be found in the Values property collection (a hash I think, probably a ConfigurationPropertiesCollection object) , a property which doesn't appear in the MSDN documentation for the ConfigurationElement. Me("someName") seems to be functionaly equivalent to Me.Values("someName"). I'm not sure why they omitted that from the documentation, or what purpose the Item() property serves except that it's documented as:
In C#, this property is the indexer for the ConfigurationSectionCollection class.
which leaves me wondering why it's in the ConfigurationElement class and what it does for VB. In my explorations, I found that Me.Item("someName") couldn't get me to my value, but Me.Values("someName") could. If anyone has some insight into this, I'd be very happy to hear it.
I hope this helps someone out.
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Tuesday, September 12, 2006 1:05 PM
All replies
-
User-1328050895 posted
Well, I did finaly get this working. I'll post some links and some advice here for anyone else who is looking.
One of my major problems with implementing a custom config section in .NET 2.0 was the lack of solid documentation. It's there, but not very deep, and the "example below" examples all seem to be missing at the time I'm writing this. I was able to piece things together thanks to some tenacious people with blogs. Here's a list of links that helped me out, in no particular order.
http://fredrik.nsquared2.com/viewpost.aspx?PostID=233
http://msdn.microsoft.com/en-us/library/ms228062.aspx
http://blogs.msdn.com/markgabarra/archive/2006/06/27/648742.aspx
http://blogs.msdn.com/markgabarra/archive/2006/06/25/646775.aspx
http://msdn.microsoft.com/msdnmag/issues/06/06/ConfigureThis/
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=132716&SiteID=1&mode=1
http://www.agileprogrammer.com/oneagilecoder/archive/2005/06/11/3689.aspx
http://jason.diamond.name/weblog/2006/05/25/custom-configuration-section-validator-weirdness
That last link was particularly helpful; it set me in the right direction. Jason Diamond admits that he didn't look deep enough into the problem to find the reason, but I think I may have found the answer in the Microsoft forums (third last link).
In my mind, the declarative programming model for custom config sections is broken. The issue is with default values, validators, and the ConfigurationElement base class constructor.
It seems that the ConfigurationElement base constructor attempts to set "default" values for your ConfigurationElement's properties. If it's an integer property, the ConfigurationElement constructor will attempt to assign a zero to it, "" for a String etc. Reasonably, this default assignment is still passed through any validation you may have assigned to the property declaritively using an attribute, since the declarative validation attribute's routines are attached to the properties before the constructor attempts to assign a default value (I think).
However, as Jason Diamond and Paul D. Murphy point out, this becomes a problem if, for example, your validation says that your integer property needs to be between 2 and 5; zero is invalid. OK, so you also decorate your property with the DefaultValue attribute and inform the ConfigurationElement constuctor that when it assigns a value to that property, it should use, say, 3 instead of zero. Now the framework can instantiate your ConfigurationElement derived class without the validator throwing an error. But.
Now you have a property of your ConfigurationElement that has a default value. Default value...that means that the implementor/user of your custom configuration section is no longer required to type in a value for that property (an attribute on an xml tag in the config file). What if you wanted to require that the implementor type a value? You're stuck, if you wanted to use the declarative model to code your custom config section. The only solution is to use the programatic model.
Justin Diamonds post shows how this can be done. http://jason.diamond.name/weblog/2006/05/25/custom-configuration-section-validator-weirdness
Another Tip:
If you want to have a ConfigurationElementCollection in your config section without actually having a parent tag around the collection, declare the collection name in the ConfigurationProperty attribute as an empty string. For example if you have a <MyConfigSection> section with a collection of FileGroup elements, you can code:
<ConfigurationProperty("", IsRequired:=True, IsDefaultCollection:=True)> _ Public Property FileGroups() As FileGroupCollection Get Return CType(Me(""), FileGroupCollection) End Get Set(ByVal value As FileGroupCollection) Me("") = value End Set End Property
which will give you:
<MySection> <FileGroup/> <FileGroup/> <FileGropu/> <MySection>
as opposed to
<ConfigurationProperty("FileGroups", IsRequired:=True, IsDefaultCollection:=True)>
which would require the section to look like:
<MySection> <FileGroups> <FileGroup/> <FileGroup/> <FileGropu/> </FileGroups> <MySection>
Something Odd
Most of the example code I've found on the web write the property accessors as I did above, using Me("someName") (or base["someName"] in C#) to store the values. Reading the documentation, it looks like the default the default property of the ConfigurationElement is Item(), but tracing through the code I found that the values I was trying to retrieve could be found in the Values property collection (a hash I think, probably a ConfigurationPropertiesCollection object) , a property which doesn't appear in the MSDN documentation for the ConfigurationElement. Me("someName") seems to be functionaly equivalent to Me.Values("someName"). I'm not sure why they omitted that from the documentation, or what purpose the Item() property serves except that it's documented as:
In C#, this property is the indexer for the ConfigurationSectionCollection class.
which leaves me wondering why it's in the ConfigurationElement class and what it does for VB. In my explorations, I found that Me.Item("someName") couldn't get me to my value, but Me.Values("someName") could. If anyone has some insight into this, I'd be very happy to hear it.
I hope this helps someone out.
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Tuesday, September 12, 2006 1:05 PM -
User436273434 posted
That was a really great post. Very helpful, and well done!
Wednesday, June 27, 2007 1:46 PM -
User-1854994243 posted
I need some more help here. How do i go about using this to extract specific filegroup\add[maxsize] .
help appreciatedthnx loads
Thursday, February 14, 2008 5:42 AM -
User436273434 posted
In your ConfigurationElementCollection subclass, you typically have an indexer that uses BaseGet(string) to extract specific elements.
See here for an example:
http://msdn.microsoft.com/en-us/library/system.configuration.configurationelementcollection.aspxThursday, February 14, 2008 1:53 PM -
User-1854994243 posted
I dont want to do
val = ISconfigs.configs[2].settings[1].Value;
I wanted to fetch it like hashtable.
val = configs.configs["SomeName"].settings[key].Value;
I am lost. how do i go about this now. If i try to
public configElement this[string idx]
{
get
{
return (configElement)BaseGet(idx);
}
}
it does not work.
dumb me, please help. thnx in advance.
Friday, February 15, 2008 12:30 AM -
User-1854994243 posted
got it :D
for the second level children elements i had to loop through the output. excellent. thnx for all help.
Friday, February 15, 2008 1:38 AM -
Monday, June 8, 2009 7:34 AM