Objectifying an XML Node with an IConfigSectionHandler

Introduction

One of my favorite B-movies is Back to School, starring Rodney Dangerfield. If you are unfamiliar with the movie, Dangerfield’s character Thornton Mellon, a rich clothier for fat people, returns to college. Employing savvy business acumen to get through coursework and exams, he is charged with cheating on finals and is required to take an oral exam. When his love interest and English professor asks him to recite Dylan Thomas’s “Do Not Go Gentle into That Good Night,” Mellon recites the poem. Here is an approximation of the dialogue:

Mellon: Do not go gentle into that good night, old age should burn and rave at close of day; Rage, rage against the dying of the light.

Professor: What does that mean to you?

Mellon: It means I don’t gotta take s**t from nobody.

Through Mellon, the screenwriter interprets the poem to be an affirmation for the dignity and rights of each human soul. We all have power, dignity, and a right to assert our existence in the manner we see fit. (Except for an obvious few malcontents, I am optimistic that most people use this power for good.)

What does this have to do with Visual Basic? Well, it means that you and I each are collaborators on our own terms. That we bring value to every endeavor, have a right to express ourselves, and possess knowledge that engenders a certain power. Read this article and rejoice in your burgeoning power.

This article teaches you how to implement the IConfigurationSectionHandler. Classes implementing this interface permit your code to treat a custom block of XML in a .config file as a static, persisted instance of an object. While you can use the AppSettings collection to read individual .config file settings, section handlers enable you to objectify blocks of XML. The result is that an XML node becomes a strongly typed object from the perspective of your code, rather than a bunch of spurious individual values.

IConfigurationSectionHandler

Interfaces are contracts. Like abstaining from performance-enhancing drugs is becoming an enforced contract for professional athletics, interfaces are participation contracts. Quite simply, if a class wants to participate in a certain defined behavior, the class needs to consent to the terms of the contract. This consent is exemplified by implementing an interface: the contract.

For example, the ConfigurationSettings.AppSettings property is a NameValueCollection. ConfigurationSettings itself participates in the IConfigurationSectionhandler contract by using the predefined NameValueSectionHandler, which implements IConfigurationSectionHandler. The result is that XML tags in <appSettings> that define a name and value pair can be read through the ConfigurationSettings.AppSettings collection.

The <appSettings> section of a .config file is useful for spurious application settings like database connection strings or general application settings, but is less expressive for chunks of related data. One example can be found in Microsoft’s Exception Management Application Block (EMAB—check www.microsoft.com for more information on application blocks). The EMAB permits you to dynamically add assemblies that log exceptions after an application has been deployed. This is useful, for example, when you may want to log exceptions to the Event Log and later add e-mail notification. Rather than rebuilding and redeploying an entire application, you can build just the assembly for e-mail notification, add the configuration section to the .config file, and deploy the e-mailer assembly only.

Clearly, an assembly that has entire behaviors, such as managing mail servers, e-mail addresses, and authentication information is much more advanced than just an application setting like a database string. Such an advanced assembly requires special care and management that a class ideally can handle.

This example strikes out somewhere between the simplicity of reading singular application settings and dynamic application block assemblies. I demonstrate how to objectify the settings one might desire to configure an FTP client. (If you are interested in implementing an FTP client for .NET, check out the knowledgebase article 832679 at www.microsoft.com.)

Creating a Section Handler for FTP Client Settings

Technologies such as XML Web Services and .NET Remoting make it possible to connect new systems to legacy systems, but many legacy systems still do not have Web Service or Remote server-wrappers that expose important behaviors and data. As a result, many companies use FTP clients and servers to move data between new systems and local or remote legacy systems. I have worked on many such projects.

Suppose you are working on an FTP client. Whether automating the migration of legacy data or just building your own FTP tool, a reasonable assumption is that connection settings will benefit from being externalized and editable with a plain text editor.

Defining the XML Section

A good strategy is to define the block of XML you want to read. For the purposes of this example, you might want to turn the FTP behavior on or off and configure the remote host, path, user, password, and port. While the port generally is port 21, supporting the configuration of elements like the port number is still useful. Such an XML section might be added to a .config (see Listing 1).

Listing 1: Defining an ftpSettings section of an XML .config file.

<ftpSettings mode="on">
<remoteHost value="127.0.0.1" />
<remotePath value="." />
<remoteUser value="anonymous" />
<remotePassword value="pkimmel@softconcepts.com" />
<remotePort value="21" />
</ftpSettings>

The XML node is ftpSettings with an attribute named mode. The child XML nodes are remoteHost, remotePath, remoteUser, remotePassword, and remotePort. Each child node has an attribute named value. While XML can be as simple or as complex as you like, the XML section in Listing 1 suits the example’s purposes.

Implementing the Section Handler

The XML node ftpSettings represents an XML-persisted object that you want to be able to read and convert to an instance of a class at runtime. Naturally, you might name such a class FtpSettings and the section handler for FtpSettings FtpSettingsSectionHandler. The FtpSettings class needs fields of the correct type to store the persisted XML data, and IConfigurationSectionHandler requires you to implement a Create method.

Listing 2 shows the implementation of the FtpSettings class and the FtpSettings SectionHandler. I added a few helper details for convenience. A discussion of the Create method follows the listing.

Listing 2: The IConfigurationSectionHandler and FtpSettings classes for managing FTP settings.

Imports System
Imports System.Configuration
Imports System.Globalization
Imports System.Text
Imports System.Xml

#Region "Sample .config file with configuration section"

'<?xml version="1.0" encoding="utf-8" ?>
' <configuration>

'   "configSections>
'    <section name="ftpSettings"
'             type="FtpSettings.FtpSettingsSectionHandler,
'                   FtpSettings" />
'   </configSections>

'    <ftpSettings mode="on">
'      <remoteHost value="127.0.0.1" />
'      <remotePath value="." />
'      <remoteUser value="anonymous" />
'      <remotePassword value="pkimmel@softconcepts.com" />
'      <remotePort value="21" />
'   </ftpSettings>
' </configuration>

#End Region

Public Enum FtpSettingsMode
  OFF
  [ON]
End Enum

Public Class FtpSettings
  Public mode As FtpSettingsMode  = FtpSettingsMode.ON
  Public remoteHost As String     = "127.0.0.1"
  Public remotePath As String     = "."
  Public remoteUser As String     = "anonymous"
  Public remotePassword As String = "dummy@nowhere.com"
  Public remotePort As Integer    = 21

  Public Function GetFormatted() As String
    Dim builder As StringBuilder = New StringBuilder
    builder.AppendFormat("remoteHost={0}{1}", remoteHost, _
                         Environment.NewLine) _
    builder.AppendFormat("remotePath={0}{1}", remotePath, _
                         Environment.NewLine)
    builder.AppendFormat("remoteUser={0}{1}", remoteUser, _
                         Environment.NewLine)
    builder.AppendFormat("remotePassword={0}{1}", remotePassword, _
                         Environment.NewLine)
    builder.AppendFormat("remotePort={0}{1}", remotePort, _
                         Environment.NewLine)
    Return builder.ToString()
  End Function
End Class

Public Class FtpSettingsSectionHandler
  Implements IConfigurationSectionHandler

  Private Const FTPSETTINGS_MODE As String = "mode"
  Private Const REMOTE_HOST As String      = "remoteHost"
  Private Const REMOTE_PATH As String      = "remotePath"
  Private Const REMOTE_USER As String      = "remoteUser"
  Private Const REMOTE_PASSWORD As String  = "remotePassword"
  Private Const REMOTE_PORT As String      = "remotePort"

  Public Sub New()
  End Sub

  Public Function Create(ByVal parent As Object, _
    ByVal configContext As Object, _
    ByVal section As System.Xml.XmlNode) As Object _
    Implements IConfigurationSectionHandler.Create

    Try
      Dim settings As FtpSettings = New FtpSettings

      If (section Is Nothing) Then Return settings
      Dim currentAttribute As XmlNode

      Dim nodeAttributes As XmlAttributeCollection = _
          section.Attributes
      currentAttribute = _
             nodeAttributes.RemoveNamedItem(FTPSETTINGS_MODE)

If (Not currentAttribute Is Nothing And _
currentAttribute.Value.ToUpper(CultureInfo.InvariantCulture) = _
                               "OFF") Then
        settings.mode = FtpSettingsMode.OFF
      End If

      Dim node As XmlNode

      For Each node In section.ChildNodes
        nodeAttributes = node.Attributes
        If nodeAttributes Is Nothing Then GoTo Continue

        currentAttribute = nodeAttributes.RemoveNamedItem("value")
        If (currentAttribute Is Nothing) Then GoTo continue

        If (node.Name = REMOTE_HOST) Then
          settings.remoteHost = currentAttribute.Value
        End If

        If (node.Name = REMOTE_PATH) Then
          settings.remotePath = currentAttribute.Value
        End If

        If (node.Name = REMOTE_USER) Then
          settings.remoteUser = currentAttribute.Value
        End If

        If (node.Name = REMOTE_PASSWORD) Then

          settings.remotePassword = currentAttribute.Value
        End If

        If (node.Name = REMOTE_PORT) Then

          settings.remotePort = _
                   Convert.ToInt32(currentAttribute.Value)
        End If
        ' A little short-circuiting never killed anyone
Continue:
      Next
      Return settings
    Catch ex As Exception
      Throw New ConfigurationException("Error loading FtpSettings", _
                                       ex, section)
    End Try
  End Function
End Class

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read