Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Configuration Sections in .NET 2.0

0.00/5 (No votes)
19 Feb 2006 1  
Creating a custom configuration section in .NET 2.0.

Introduction

This article describes how to develop a custom configuration section in .NET 2.0. To make it easier, this example starts from an existing section in .NET 1.1 and shows the changes needed to translate it to .NET 2.0. Of course, it explains why we should do it.

You can download two examples with the same application for 1.1 and 2.0. The 2.0 version adds some of the new functionalities furthermore.

The Configuration

Our first step, no matter what framework version or environment we're targeting, is to define the configuration properties we need. For this example, we will use the following simple configuration class:

Public Class ConfigurationSample
    Public OneProperty As String
    Public OneNumericValue As Integer
    Public OneElement As ConfigurationSampleElementClass
End Class

Public Class ConfigurationSampleElementClass
    Public AnotherProperty As String
End Class

Note: Fields are being used here to make it simpler. You already know that it's always preferred to use properties instead of fields, isn't it? Now, let's see how we could get this configuration information from the CONFIG file.

Background: The 1.1 Version

In .NET 1.1 every configuration section is managed by a configuration handler, which must implement the IConfigurationSectionHandler interface. In the Create method of that interface you should look through the XML node that is passed as a parameter and create with it an instance of our ConfigurationSample class.

Of course, this seems a little complicated for such a little problem. Knowing that the source of your information is an XML node and that all that you want is an instance of a class that represents it, XML serialization is the answer to our problems.

Thinking in XML serialization terms the configuration file that we need may be something like this:

<sampleConfiguration oneProperty="the value for the property"
                                            oneNumericValue="25">
    <oneElement anotherProperty="some usefull string here"/>
</sampleConfiguration>

In order to make our ConfigurationSample class serialize as the XML data above, we need to add some attributes to it:

Public Class ConfigurationSample
    <XmlAttributeAttribute("oneProperty")> _
                       Public OneProperty As String
    <XmlAttributeAttribute("oneNumericValue")> _
                       Public OneNumericValue As Integer
    <XmlElement("oneElement")> _
         Public OneElement As ConfigurationSampleElementClass
End Class

Public Class ConfigurationSampleElementClass
    <XmlAttributeAttribute("anotherProperty")> _
                             Public AnotherProperty As String
End Class

Now, all we must do is to write a configuration handler that deserializes the XML from the CONFIG file into our class. This should be something like this:

Public Class ConfigurationSampleHandler
   Implements IConfigurationSectionHandler

   Public Function Create(ByVal parent As Object, _
     ByVal configContext As Object, _
     ByVal section As System.Xml.XmlNode) As Object _
     Implements System.Configuration.IConfigurationSectionHandler.Create
        Dim ser As New XmlSerializer(GetType(ConfigurationSample), _
                   New Xml.Serialization.XmlRootAttribute(section.Name))
        Dim res As Object = ser.Deserialize(New XmlNodeReader(section))
        Return res
   End Function
End Class

And let the framework know that this is the handler for our new configuration section. This is done by means of the <configSections> tag in the CONFIG file. So our CONFIG file should be something like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="sampleConfiguration" 
          type="Configuration_dotNet1_1.ConfigurationSampleHandler, ...
          ... Configuration_dotNet1_1" />
    </configSections>
    <sampleConfiguration 
        oneProperty="the value for the property"
        oneNumericValue="25">
        <oneElement anotherProperty="some usefull string here"/>
    </sampleConfiguration>
</configuration>

We are now closer to the end. We only need some way to access this information. In order to accomplish this we're going to add some extra code to the ConfigurationSample:

Private Shared m_current As ConfigurationSample
Public Shared ReadOnly Property Current() As ConfigurationSample
    Get
        If m_current Is Nothing Then
            m_current = DirectCast(ConfigurationSettings.GetConfig( _
                          "sampleConfiguration"), ConfigurationSample)
        End If
        Return m_current
    End Get
End Property

And that's it. We now have our application's configuration ready to work. So to read one of the values in our configuration file all we need to do is:

ConfigurationSample.Current.OneProperty

The 2.0 Version

The implementation in .NET 1.1 was easy. Then, why can't we do the same in the new framework version? In fact, you can do it. But, although this code will compile correctly, you'll get a little warning from the IDE:

Ups! So, should we use ConfigurationManager instead of ConfigurationSettings? It's no big deal. We change that and everything will run fine now. But wait, while reading the ConfigurationManager documentation you will realize that there are a lot of new classes related to configuration. There is a whole new framework assembly devoted only for configuration.

The first thing that we'll notice are the new ConfigurationSection, ConfigurationElement, ... classes. The first one is the base for our custom sections. The second one is the base for every complex property in our sections.

But, why should we change our code if it's working well? You're right, it's not a good practice to change something only because of new "fashions". So, let's find out some important problems with our 1.1 implementation before we go on:

  • Our ConfigurationSample properties are defined as Read/Write. The reason for this is simple: XML serialization only works with public read/write fields/properties. This will mislead everyone who uses our class, because the changes will only be persisted while our application is running. Of course, you can open the configuration file directly as an XMLDocument, change the right XMLNode and save the changes. But this is not a trivial task.
  • If we need to store some confidential information (DB connection strings, for example) anyone with physical access to the configuration file will be able to read it.

These problems, and others outside the scope of this article, are addressed by those new configuration classes.

So let's start with the modifications. As we've already said, we must inherit from the ConfigurationSection in our configuration class. There are some new attributes too that will allow us to define more exactly what our configuration properties are:

Public Class ConfigurationSample
    Inherits System.Configuration.ConfigurationSection
    <ConfigurationProperty("oneProperty", _
                      DefaultValue:="a default Value", _
                      IsRequired:=True, IsKey:=True)> _
        Public ReadOnly Property OneProperty() As String
           Get
               Return CStr(Me("oneProperty"))
           End Get
        End Property

    <ConfigurationProperty("oneNumericValue", IsRequired:=True)> _
        Public ReadOnly Property OneNumericValue() As Integer
            Get
                Return CInt(Me("oneNumericValue"))
            End Get
        End Property

    <ConfigurationProperty("oneElement", IsRequired:=True)> _
        Public ReadOnly Property OneElement() As _
                              ConfigurationSampleElementClass
            Get
                Return DirectCast(Me("oneElement"), _
                          ConfigurationSampleElementClass)
            End Get
        End Property

    Private Shared m_current As ConfigurationSample

    Public Shared ReadOnly Property Current() As ConfigurationSample
        Get
            Return DirectCast(ConfigurationManager.GetSection( _
                "sampleConfiguration"), _
                           ConfigurationSample)
        End Get
    End Property
End Class

Public Class ConfigurationSampleElementClass
   Inherits ConfigurationElement

   <ConfigurationProperty("anotherProperty", IsRequired:=True)> _
       Public ReadOnly Property AnotherProperty() As String
           Get
               Return CStr(Me("anotherProperty"))
           End Get
       End Property
End Class

What we have done here is an exact functional translation from the 1.1 code.

The more interesting news here is the ConfigurationProperty attributes. With them we can define the default values for a property, or decide if a property must be specified in the CONGIG file or not, or even define the validation rules for each property.

The next thing to notice is that ConfigurationSample now inherits from ConfigurationSection and ConfigurationSampleElementClass inherits from ConfigurationElement. You'll notice a lot of new properties in our configuration classes resulting from this inheritance... More about this later.

One side effect of this is that we don't need the ConfigurationSampleHandler class any more. In fact, if you inspect the ConfigurationSection class you'll notice that this class implements the IConfigurationSectionHandler interface. So, we must change our configuration file to show this change:

<configSections>
    <section name="sampleConfiguration" 
     type="Configuration_dotNet2_0.ConfigurationSample, ...
        ... Configuration_dotNet2_0" />
</configSections>

So, now we have implemented a complete 2.0 configuration. Let's start with some of the new functionalities.

The first thing we're going to do is to add the functionality to our application configuration. Looking through the MSDN documentation we find a Save method in the Configuration class. This looks like a good place to start with... This Configuration instance is obtained from the ConfigurationManager.OpenExeConfiguration function. From this instance, we can get a read/write version of our ConfigurationSample section. So now we'll start changing our current property:

Private m_cfg As Configuration
Private Shared m_current As ConfigurationSample
Public Shared ReadOnly Property Current() As ConfigurationSample
   Get
      If m_current Is Nothing Then
         Dim cfg As Configuration
         Dim ctx As System.Web.HttpContext 
         ctx = System.Web.HttpContext.Current
         If ctx Is Nothing Then
            cfg = ConfigurationManager.OpenExeConfiguration( _
                   ConfigurationUserLevel.None)
         Else
            cfg = WebConfigurationManager.OpenWebConfiguration( _
                ctx.Request.ApplicationPath)
         End If
         m_current = DirectCast(cfg.Sections(CS_SECTION), _
                                         ConfigurationSample)
         m_current.m_cfg = cfg
      End If
      Return m_current
   End Get
End Property

In 2.0, the configuration loading process in a Win Forms or console application differs from the ASP.NET application, so this code must know the running environment (checking HttpContext.Current) to load the configuration according to the environment. In an ASP.NET environment, you must use WebConfigurationManger instead of ConfigurationManager.

The next thing to notice here is that we store a copy of the Configuration instance in the ConfigurationSample instance. We will need it later to save the changes. We must change the Property definitions to allow writing, of course. Something like this:

...
<ConfigurationProperty("oneNumericValue", IsRequired:=True)> _
    Public Property OneNumericValue() As Integer
        Get
            Return CInt(Me("oneNumericValue"))
        End Get
        Set(ByVal value As Integer)
            Me("oneNumericValue") = value
        End Set
    End Property
...

Note: If we try to execute the Set method over a ConfigurationSection obtained by means of the ConfigurationManager.GetSection method we'll get a ConfigurationErrorsException, with a "The configuration is read only" message.

And now we can save the configuration values. To do this we'll add a new method in the ConfigurationSample class:

Public Sub Save()
    Me.SectionInformation.ForceSave = True
    m_cfg.Save(ConfigurationSaveMode.Full)
End Sub

As you can see we only need to call the Configuration.Save method. The ForceSave=True line instructs the framework to save all the properties to the CONFIG file, no matter if they have changed its value or not. This could be useful if some property had not been set in the CONFIG file, it takes its default value (specified with the DefaultValue property in the ConfigurationProperty attribute). In this case, this default value would be saved to the CONFIG file.

Note that in an ASP.NET application this causes an application restart, because of the web.config file modification, so you must take this into account and modify the configuration only when it's really necessary.

And now, to finish this article, we'll add to this class the functionality to encrypt itself. To accomplish this all we must do is to add another method to the ConfigurationSample class:

Public Sub Encript(ByVal encrypt As Boolean)
    If encript Then
        Me.SectionInformation.ProtectSection( _
        "DataProtectionConfigurationProvider")
    Else
        Me.SectionInformation.UnprotectSection()
    End If
    Me.Save()
End Sub

With this method we protect (or unprotect, depending on the parameter) the configuration section using the specified provider ("DataProtectionConfigurationProvider"). This provider is supplied by the framework by default, and it's based in the Windows CAPI. We should be able to make out our own provider, but this is out of the scope of this article. Once executed this method in our <sampleConfiguration> section will look like this:

<sampleConfiguration 
  configProtectionProvider="DataProtectionConfigurationProvider">
    <EncryptedData>
        <CipherData>
            <CipherValue>AQAAANCMnd8BFdERjHoAwE/C ...
        ... 4794DC2Is=</CipherValue>
        </CipherData>
    </EncryptedData>
</sampleConfiguration>

Note: This protection is machine specific, so once protected this section will be successfully decrypted only in that computer.

Clarification: It's important to remember that this encryption does not protect your settings against the system administrator's curiosity. It's intended to protect you against any hacker trying to obtain access to the CONFIG files, normally protected by the framework, using some unexpected "backdoor". For example, a page in your application that serves the stored documents to the client using a GET or POST argument with the path of the file to show (very dangerous page, by the way... but this goes beyond the scope of this article). So, it protects you against anyone who could read your CONFIG file, but not against anyone who could execute something in the machine. Remember that nothing can protect you against someone with administrative privileges in your computer or server.

Conclusion

This new version of the .NET Framework includes a lot of new features related to Configuration. This article has been a little example to let you start playing with it, but there are many other cool features that you can explore, for example:

  • Reading a section from a separated configuration file. Look at the SectionInformation.Source property for more information.
  • Defining type validators for some properties. Look at the ConfigurationValidator attribute and its descendants.

Now it's time to download the samples and start reading the documentation. Here are some useful links that you'll need:

Update

  • 02/23/2006: Some modifications based on readers doubts:
    • Added the dual environment (Web/Windows) configuration load process in the ConfigurationSample.Current function.
    • Added encryption clarification.
    • Updated the 2.0 sample to show the dual environment functionality. It's now a three projects solution with one Win Forms app, one ASP.NET app and a common DLL with the ConfigurationSample class. The ASP.NET application shows you how the application is restarted after modifying the web.config file.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here