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="[email protected]" /> <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="[email protected]" /> ' <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 = "[email protected]" 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