- Introduction
- An XML Node Representing a Simple Macro Language
- Implementing the Section Handler
- Reconstituting the Object
- Summary
Implementing the Section Handler
Earlier, I indicated that Reflection is used to load an assembly and class. The framework also needs to know about behaviors in this dynamically loaded class. This is where the interface idiom plays a role.
The IConfiguration
SectionHandler defines one method:
Create. Create is provided with a parent object, a
configContext, an XmlNode (our section), and returns an
object. As object is the root class for all .NET classes, we can return
anything from the Create method, but it's our job to implement
Create because we know how the XML section is formatted (we defined it
in Listing 2).
Our solution is slightly more complex, too. A macro language implies more than one command. To support this setup, I defined a base class called Command, specific child classes of Commandone for each command I'd like to supportand the IConfigurationSectionHandler. To store the commands, I used the System.Collections.Queue class. Because Queue is a FIFO (first in, first out) data structure, it will ensure that the commands will be dequeued in the order that they're enqueued and defined in the app.config file. Listing 3 contains the complete implementation as described in this paragraph.
Listing 3 The implementation of the code that supports and implements the IConfigurationSectionHandler.
using System; using System.Collections; using System.Configuration; using System.Diagnostics; using System.Globalization; using System.Text; using System.Timers; using System.Xml; namespace ConfigSectionHandler { public enum RunMode{ OFF, ON } public class Command { private string name = string.Empty; private int delay = 0; public static Command Factory(string name, int delay) { if( name == "App_About" ) return new AboutCommand(name, delay); else return new QuitCommand(name, delay); } public Command(string name, int delay) { this.delay = delay; this.name = name; } public virtual void Execute() { Console.WriteLine(string.Format("Preparing command {0}, waiting {1} seconds", name, delay)); System.Threading.Thread.Sleep(delay * 1000); } } public class AboutCommand : Command { public AboutCommand(string name, int delay) : base(name, delay){} public override void Execute() { base.Execute(); string about = "Implementing an IConfigSectionHandler" + Environment.NewLine + "Copyright (c) 2004. All Rights Reserved." + Environment.NewLine + "By Paul Kimmel. pkimmel@softconcepts.com" + Environment.NewLine; Console.WriteLine(about); } } public class QuitCommand : Command { public QuitCommand(string name, int delay) : base(name, delay){} public override void Execute() { base.Execute(); System.Threading.Thread.CurrentThread.Abort(); } } public class StartupMacro { private Queue commands = null; private RunMode mode = RunMode.OFF; public StartupMacro() { commands = new Queue(); } public void Run() { if( mode == RunMode.OFF ) return; while(commands.Count > 0) { Command command = (Command)commands.Dequeue(); command.Execute(); } } public void AddCommand(Command command) { commands.Enqueue(command); } public RunMode Mode { get{ return mode; } set{ mode = value; } } } public class MacroSectionHandler : IConfigurationSectionHandler { private const string RUN_MODE = "mode"; public MacroSectionHandler(){} #region IConfigurationSectionHandler Members public object Create(object parent, object configContext, XmlNode section) { try { StartupMacro macros = new StartupMacro(); if( section == null ) return macros; XmlNode currentAttribute; XmlAttributeCollection nodeAttributes = section.Attributes; currentAttribute = nodeAttributes.RemoveNamedItem(RUN_MODE); if( currentAttribute != null && currentAttribute.Value == "On" ) { macros.Mode = RunMode.ON; } foreach(XmlNode node in section.ChildNodes) { nodeAttributes = node.Attributes; if( nodeAttributes == null ) continue; currentAttribute = nodeAttributes.RemoveNamedItem("name"); if( currentAttribute == null || node.Name != "command") continue; string name = currentAttribute.Value; int delay; XmlNode childAttribute; foreach(XmlNode child in node.ChildNodes) { XmlAttributeCollection childAttributes = child.Attributes; if( childAttributes == null ) continue; childAttribute = childAttributes.RemoveNamedItem("value"); if( childAttribute == null || child.Name != "delay" ) continue; delay = Convert.ToInt32(childAttribute.Value); macros.AddCommand(Command.Factory(name, delay)); break; } } return macros; } catch(Exception ex) { throw new ConfigurationException("Error loading startup macros", ex, section); } } #endregion } }
The basic idea behind the Command object is that it contains an abstract factory method that uses the name attribute to figure out which specific command to create. Two commands are defined here; you can define as many commands (and parameters) as you'd like. The StartupMacro class is used to signify that all of the commands together represent a macro. StartupMacro defines an Add method that enqueues the commands and a Run command that dequeues the command and calls that command's Execute method.
The class MacroSectionHandler is written to traverse the XmlNode it is provided as the section argument, examine the elements and attributes of the node and child nodes, and use these values to reconstruct a single StartupMacro and as many commands as defined in the app.config file. (If you read the Create method, you'll see that the logic simply follows the known XML section.)
The parent parameter represents the configuration settings in the parent configuration, and the configContext is an HttpConfigurationContext when Create is called by ASP.NET, or null and reserved otherwise. For our purposes, we need only the section argument, which represents the start of our XML node.