ConfigurationSectionHandler the easy way

Have you ever had the need to develop your own configuration section handler for application configuration files (app.config) in C#. Everybody that has been doing this lately knows how tedious this can be. But there is a quick and easy way to implement simple configuration section handlers in C#. This post shows you how…

Sample

In the sample we are going to implement a simple configuration section handler which will input an enumeration and a simple string constant. I quickly define the interface:

 

1
2
3
4
5
6
7
public interface ITracelightConfigurationSectionHandler : 
IConfigurationSectionHandler
   {
      LanguageEnumeration Language { get; }
 
      string SomeInput { get; }
   }

And the enumeration. Now here comes the first trick into the play. We are going to define our enumeration as a DataContract with the DataContractAttribute from the System.Runtime.Serialization namespace. Every enumeration member that we want to be able to serialize must be marked with the EnumMemberAttribute. This allows us later to transparently deserialize the enumeration from the application configuration file.

1
2
3
4
5
6
7
8
9
using System.Runtime.Serialization;
 
[DataContract]
public enum LanguageEnumeration {
   [EnumMember]
   German,
   [EnumMember]
   English,
}

Next we need the concrete class which implements our configuration section handler interface. The class itself are we going to mark as DataContract with the DataContractAttribute. Important here is the Namspace property in the DataContractAttribute must be set to “” (see Line 7). In the Create Method from the IConfigurationSectionHandler interface we are going to use the DataContractSerializer and instantiate it with our concrete class (which implements the ITracelightConfigurationSectionHandler interface). In Line 19 we specify that we want to read the object out of the serializer and must also specify false as second parameter (this surpresses name checking while deserialization). Then we can read the properties out of the configSection instance. Set it on the properties on the instance and return a reference to ourself (this). And we are done!

You only have to remember to set the DataMemberAttribute on every property you want to serialize or deserialize. With the datamember property you can also define how the name looks like in the configuration. If you don’t define a name, the configuration file section must be exactly named as the property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
...
    using System.Configuration;
    using System.Runtime.Serialization;
    using System.Xml;
...
 
[DataContract(Namespace = "")]
public class TracelightConfigurationSectionHandler :
ITracelightConfigurationSectionHandler
   {
         // From IConfigurationSectionHandler
         public object Create(object parent, object configContext, XmlNode section)
        {
            var xs = new DataContractSerializer(typeof(TracelightConfigurationSectionHandler ));
 
            XmlNodeReader xnr = new XmlNodeReader(section);
            try
            {
                var configSection = xs.ReadObject(xnr, false) as TracelightConfigurationSectionHandler;
 
                if (configSection != null)
                {
                    Language = configSection.Language;
                    SomeInput= configSection.SomeInput;
                }
 
                return this;
            }
            catch (Exception ex)
            {
                string s = ex.Message;
                Exception iex = ex.InnerException;
                while (iex != null)
                {
                    s += "; " + iex.Message;
                    iex = iex.InnerException;
                }
 
                throw new ConfigurationErrorsException(
                    "Unable to deserialize an object of type \'" + GetType().FullName +
                    "\' from  the <" + section.Name + "> configuration section: " +
                    s,
                    ex,
                    section);
            }
 
 
 
 
        }
 
        // Automatic property for the language
        [DataMember(Name = "language", Order = 1)]
        public LanguageEnumeration Language 
        {
            get; private set;
        }
 
        // Automatic property for the input, which can be omitted.
        [DataMember(Name = "input", Order = 2, IsRequired = false)]
        public string SomeInput
        {
            get; private set;
        }
 
   }

And how looks the App.config file?

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="tracelightsection" type="Fully.Qualified.Namespace.TracelightConfigurationSectionHandler, MyAssemblyNameWithoutDllExtension"/>
  </configSections>
  <tracelightsection>
    <language>German</language>
    <input>SomeTextualInput</title>
  </tracelightsection>
</configuration>

And how can the data be read from the configuration file? That’s pretty easy…

var configSection = (ITracelightConfigurationSectionHandler)ConfigurationManager.GetSection("tracelightsection");

This works even with complex collections. All you need to now is how to handle the data with the DataContractSerializer. But this is fearly simple to learn.

Remarks:
When you see strange behaviour such as the data is not deserialized althoug set in the configuration file you need to specify the order property (especially if you are using non required properties or default values with EmitDefaultValue…).

Tags: ,

Einen Kommentar schreiben