- Introduction
- Defining a Custom XML Section
- Implementing a Custom IConfigurationSectionHandler
- Adding the configSection to the Configuration File
- Food for Thought
Implementing a Custom IConfigurationSectionHandler
IConfigurationSectionHandler is an interface defined in the System.Configuration namespace. As when using any interface, all you have to do is implement the contract—the features—defined by the interface. Because at some point you may want to reuse the custom configuration block, and the handler is loaded dynamically and invoked using Reflection, you'll place the section handler in its own class library. That said, we can define our objectives in broad strokes and then complete each task:
- To implement an IConfigurationSectionHandler for the custom XML block in Listing 1, we'll create a separate class library project. (The language doesn't matter, but we'll use C# for this example.)
- In that project, we'll add a using statement for the System.Configuration namespace.
- In that project, we'll also define a public class that implements IConfigurationSectionHandler, and implement the required Create method defined by the interface.
The Create method will be called—we'll see how that works in a minute—with the block of XML defined in Listing 1. The goal is to read those elements using XML helper classes and convert the text into an object. How we initialize the object dictates how we reconstruct the object from XML. Listing 2 shows one possible implementation of the PlayerDefaultOptions, which is the class we will reconstruct from the XML block in Listing 1.
Listing 2 An implementation of the PlayerDefaultOptions class.
public class PlayerDefaultOptions { private string playerName = "no name"; private bool hints = false; private bool noSurrender = false; private bool playHintsOnly = false; public PlayerDefaultOptions(){} public string PlayerName { get { return playerName; } set { playerName = value; } } public bool Hints { get{ return hints; } set{ hints = value; } } public bool NoSurrender { get{ return noSurrender; } set{ noSurrender = value; } } public bool PlayHintsOnly { get{ return playHintsOnly; } set{ playHintsOnly = value; } } }
From the listing we can determine that we need to construct an instance of PlayerDefaultOptions and initialize four properties: Hints, NoSurrender, PlayHintsOnly, and the PlayerName. Relative to the XML block we have two child nodes, player and options, and nested in options we have hints, noSurrender, and playHintsOnly. Consequently, we need to implement Create to read the custom section based on its layout. Since we've defined the section, the layout, and the IConfigurationSectionHandler, we can easily put these elements together. Listing 3 show some pretty straightforward, brute-force code that gets the jobs done.
Listing 3 The custom IConfigurationSectionHandler for the playerDefaultOptions block.
#region Using directives using System; using System.Collections; using System.Configuration; using System.Diagnostics; using System.Globalization; using System.Text; using System.Xml; #endregion namespace ConfigSectionHandler { public class MySectionHandler : IConfigurationSectionHandler { public MySectionHandler(){} #region IConfigurationSectionHandler Members object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode section) { try { // construct the target object PlayerDefaultOptions options = new PlayerDefaultOptions(); // if the section is null return the empty object if (section == null) return options; //Walk the sections' child nodes, reading the elements foreach (XmlNode node in section.ChildNodes) { if (node.Name == "player") ReadPlayer(node, options); else if (node.Name == "options") ReadOptions(node, options); } return options; } catch(Exception ex) { throw new ConfigurationException( "Error loading startup default options", ex, section); } } #endregion private void ReadPlayer(XmlNode node, PlayerDefaultOptions options) { // load the player attributes XmlNode currentAttribute = node.Attributes.RemoveNamedItem("name"); if(currentAttribute != null ) options.PlayerName = currentAttribute.Value; } private void ReadOptions(XmlNode node, PlayerDefaultOptions options) { XmlNode currentAttribute = null; // walk the options' child nodes foreach( XmlNode child in node.ChildNodes ) { currentAttribute = child.Attributes.RemoveNamedItem("value"); // read the child attributes if (currentAttribute == null) continue; if (child.Name == "hints") options.Hints = (currentAttribute.Value == "On"); else if (child.Name == "noSurrender") options.NoSurrender = (currentAttribute.Value == "On"); else if (child.Name == "playHintsOnly") options.PlayHintsOnly = (currentAttribute.Value == "On"); } } } }
The basic idea is that the section argument to Create contains the XML. The tags are composed of names and attributes; child nodes can be nested in tags. Simply read the XML incrementally and convert the attributes by name to state-data for the object you're constructing. Finally, it must be mentioned that you have to have a reference to the object you're constructing, in this instance PlayerDefaultOptions.
I didn't use the parent and configContext arguments in the example; if you're interested, parent refers to the enclosing parent section and configContext is reserved for future use.