An XML Node Representing a Simple Macro Language
Before you start defining a custom configuration section handler, you should know how what the XML you want to handle will look like. For completeness, we'll define some XML, too.
If you're building a section handler for predefined XML, you can skip ahead to the section "Implementing the Section Handler."
Suppose you elected to permit controlling your application from its external app.config or web.config with a node that represents a user-defined macro language. Such a technique would permit automatically performing tasks at startup and might be a good choice for batch processing or for completing required, configurable startup tasks.
To begin prototyping a startup macro language solution, we'll need to perform these general steps, described in the subsections that follow:
Add a .config file to your solution.
Add a <configSections> node that points the framework at your custom node.
Define the custom XML node.
If you're very familiar with any of these aspects of Visual Studio .NET and .NET programming, feel free to skip that particular section.
Adding a .config File to Your Solution
Visual Studio .NET supports templates. A template is little more than a preexisting wizard and some stubbed-out source files with replaceable parameters. You select an icon representing the template, and the wizard fills in the blanks using the Visual Studio .NET automation model.
Visual Basic traditionally has a lower barrier to entry than languages such as C#, so Visual Basic .NET programmers had the Application Configuration template in version 1 of .NET. Now C# programmers have it, too.
If you want the startup macro to run in a web application, use the web.config file. For non-Internet solutions, add an application configuration (app.config) file from the Add New Item dialog box (see Figure 1).
Figure 1 The Application Configuration template adds an app.config file to your project.
After you add an app.config file using Project, Add New Item, the compiler will copy the .config file to your solution's bin folder, renaming the file application_name.ext.config. For example, if your solution is called ConfigSectionHandler.exe, after you compile your solution the compiler will copy the app.config file to the bin folder, renaming the file ConfigSectionHandler.exe.config.
Defining the <configSections> Node
The next step is to provide an indicator that tells the framework that you have custom configuration sections. .NET already knows to look for a <configSections> node, in which you introduce custom sections. Listing 1 shows the first part of the app.config file with the <configSections> node in bold.
Listing 1 The <configSections> node for our custom handler.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="startupMacro" type="ConfigSectionHandler.MacroSectionHandler, ConfigSectionHandler" /> </configSections>
Let's break it down briefly:
The <section> tag indicates that we're introducing a new section.
The name attribute indicates what that section's node tag is called. The <configSections> in Listing 1 indicates that we should have a node named <startupMacro> later in this XML file.
The type attribute describes the namespace and class, delimited with a comma and the assembly name of the class. Assembly names, namespaces, and class names are enough to use Reflection to dynamically find and load a .NET assembly, which is precisely what the framework will be doing here.
Defining the Custom XML Node
Next, we need to define the <startupMacro> node. Because the macro language is our invention, we can use any valid XML to represent our macro language. However, to demonstrate reasonable plausibility, I made the example section functional. Listing 2 shows the completed app.config XML file with the <startupMacro> tag.
Listing 2 The completed App.config file.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="startupMacro" type="ConfigSectionHandler.MacroSectionHandler, ConfigSectionHandler" /> </configSections> <startupMacro mode="On"> <command name="App_About"> <delay value="5" /> </command> <command name="App_Quit"> <delay value="10" /> </command> </startupMacro> </configuration>
XML stands for Extensible Markup Language. Extensibility means that we can define our own tags, which is precisely what happens in Listings 1 and 2. And, because we can define our own tags, we can nest them and add any attributes we'd like:
The <startupMacro> tag has a mode attribute, which I use to toggle the macro between on and off states.
I also defined two <command> child nodesof course, we can define as many as we'd likewith a name attribute for each, and a nested <delay> node. In this example, the order implies the order of execution. The name attribute is associated with a behavior in my applicationthe Command behavior pattern would work well hereand the delay is in seconds. delay tells the application how long it should wait before executing that command.
For more on behavior patterns, see Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994, ISBN 0201633612) by Erich Gamma et al.
At this point, we need to implement an IConfigSectionHandler and support our macro behaviors.