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://msdn2.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.