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:
="1.0" ="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.