locked
Custom Config Section, .NET 2.0 RRS feed

  • 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 appreciated 

    thnx 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.aspx

    Thursday, 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
  • User-1074039424 posted

    I recommend trying this add-on for VS2008. 

    http://csd.codeplex.com/

     

    Monday, June 8, 2009 7:34 AM