I <3 Steve McConnell*
Coding Horror
programming and human factors
by Jeff Atwood

December 21, 2004

The Last Configuration Section Handler..

I stumbled across the Craig Andera post The Last Configuration Section Handler I'll Ever Need a few months ago, but I didn't really understand the implications until I started writing a bunch of configuration section handlers. His approach is very clever; instead of writing a bunch of tedious code to read settings from a .config file, you deserialize an instance of the class using the .config file XML as the input!

Here's the VB.NET version of the necessary ConfigurationSectionHandler:

Imports System.Xml
Imports System.Xml.Xpath
Imports System.Xml.Serialization
Imports System.Configuration

Public Class XmlSerializerSectionHandler
    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 xpn As XPathNavigator = section.CreateNavigator
        Dim TypeName As String = xpn.Evaluate("string(@type)").ToString
        Dim t as Type = Type.GetType(TypeName)
        Dim xs as XmlSerializer = New XmlSerializer(t)

        Return xs.Deserialize(New XmlNodeReader(section))
    End Function

End Class

And here's an example of what your *.config file would look like:

<configuration>
  <configSections>
    <section name="MyStuff" 
      type="MyClass.XmlSerializerSectionHandler, MyClass" />
  </configSections>

  <MyStuff type="MyClass.MyStuff">
    <Foo>234</Foo>
    <Bar>A bunch of information</Bar>
  </MyStuff> 
</configuration>

Note the type= attrib on the MyStuff element. With the type information in that attribute, the <MyStuff> config section can be deserialized to an instance of the MyStuff object:

Class MyStuff
    Public foo As Integer
    Public bar As String
End Class

.. in a single call!

Dim ms As MyStuff 
ms = CType(ConfigurationSettings.GetConfig("MyStuff"), MyClass.MyStuff)

Before going this route, make sure your class serializes to the same XML format exactly-- otherwise you'll get a bunch of non-intuitive deserialization error messages. Here's a quick way to serialize a class to the console and view the correct XML that is expected for deserialization:

Dim o as New MyStuff
o.foo = 3
o.bar = "stuff"

Dim sb As New Text.StringBuilder
Dim sw As New IO.StringWriter(sb)
Dim xs As XmlSerializer = New XmlSerializer(o.GetType)
Dim xsn As New XmlSerializerNamespaces
xsn.Add("", "")

Dim xtw As New Xml.XmlTextWriter(sw)
xtw.Formatting = Xml.Formatting.Indented
xtw.WriteRaw("")

xs.Serialize(xtw, o, xsn)
Dim s As String = sb.ToString
s = Regex.Replace(s, "(<" & o.GetType.Name & ")(>)", "$1 type=""" & o.GetType.FullName & """$2")
Console.WriteLine(s)

Note that some of the contortions in the above code are necessary to get a "clean" set of XML output, free of namespaces, encoding, and the like. This code was borrowed from Mark Allanson's blog.

It really could be The Last Configuration Section You'll Ever Need.

However, troubleshooting XML that won't deserialize can be.. difficult. Here's an improved, more robust XmlSerializerSectionHandler that provides much better feedback when things go wrong.

''' <summary>
''' Configuration section handler that deserializes configuration settings to an object.
''' </summary>
''' <remarks>The root node must have a type attribute defining the type to deserialize to.</remarks>
Public Class XmlSerializerSectionHandler
    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

        '-- get the name of the type from the type= attribute on the root node
        Dim xpn As XPathNavigator = section.CreateNavigator
        Dim TypeName As String = xpn.Evaluate("string(@type)").ToString
        If TypeName = "" Then
            Throw New ConfigurationException( _
                "The type attribute is not present on the root node of " & _
                "the <" & section.Name & "> configuration section ", _
                section)
        End If

        '-- make sure this string evaluates to a valid type
        Dim t As Type = Type.GetType(TypeName)
        If t Is Nothing Then
            Throw New ConfigurationException( _
                "The type attribute '" & TypeName & "' specified in the root node of the " & _
                "the <" & section.Name & "> configuration section " & _
                "is not a valid type.", section)
        End If
        Dim xs As XmlSerializer = New XmlSerializer(t)

        '-- attempt to deserialize an object of this type from the provided XML section
        Dim xnr As New XmlNodeReader(section)
        Try
            Return xs.Deserialize(xnr)
        Catch ex As Exception
            Dim s As String = ex.Message
            Dim innerException As Exception = ex.InnerException
            Do While Not innerException Is Nothing
                s &= " " & innerException.Message
                innerException = innerException.InnerException
            Loop
            Throw New ConfigurationException( _
                "Unable to deserialize an object of type '" & TypeName & "' from " & _
                "the <" & section.Name & "> configuration section: " & s, _
                ex, section)
        End Try
    End Function

End Class

Posted by Jeff Atwood    View blog reactions

 

« The Antidote to ASP.NET Smart Navigation, Part Deux Task Manager Extreme »

 

Comments

I wonder if there is any way to do the same thing with a typed dataset. For some reason, I would feel more comfortable pulling data out of a typed data set than a class. (Although there is probably not much difference, I would prefer to not use reflection.) Could you just duplicate the types, then do a Dataset.ReadXml on the node?

- Joshua

Joshua Bair on December 31, 2004 12:30 AM

Dunno, but it's a fine idea. Care to test it out?

Jeff Atwood on January 1, 2005 02:07 AM

Scott Weinstein has an update on this class:

http://weblogs.asp.net/sweinstein/archive/2005/01/10/350299.aspx

Jeff Atwood on January 10, 2005 11:37 PM

I am new to the many uses/advantages with an app.config file. I tried implementing the above code as an initial test and it doesn't appear to work for me.

I am sure there are details that are assumed. Can someone explain what 'MyClass' refers to within the config file and also the one line that casts the config settings to an object?

Thanks in advance!

mark on July 20, 2005 09:37 AM

Download a newer version of this code, in your choice of C# or VB.NET, here:

http://www.codinghorror.com/blog/archives/000656.html

Jeff Atwood on August 11, 2006 04:06 PM







(hear it spoken)


(no HTML)




Content (c) 2008 Jeff Atwood. Logo image used with permission of the author. (c) 1993 Steven C. McConnell. All Rights Reserved.