P3.NET

Using T4 to Create an AppSettings Wrapper, Part 4

In the previous article we finished the basic appsettings template that allowed us to generate a strongly typed class from the settings in a configuration file. We now want to expand the template to allow it to be customized depending upon the needs of the project, a later article. Before we get there though it is time to refactor the code to make it more reusable and easier to maintain. That is the focus of this article.

Read More

Using T4 to Create an AppSettings Wrapper, Part 3

In the previous article we expanded the template to dynamically set the type and namespace names based upon the project the template is used in.Now we are going to turn our focus to generating properties for each of the settings in the configuration file. For this we’ll be adding more code to the class block to open and read the settings from the project’s configuration file (which we’ll have to write code to retrieve). We’ll also have to decide how to convert string values to typed equivalents.

Read More

Using T4 to Create an AppSettings Wrapper, Part 2

In the first article in this series we created a basic, static T4 template.The template allowed us to replace standard boilerplate code for reading an app setting

string setting = ConfigurationManager.AppSettings["someSetting"];

with strongly typed property references like this

var myIntSetting = AppSettings.Default.IntValue;
var myDoubleSetting = AppSettings.Default.DoubleValue;

Here’s a summary of the requirements from the first article (slightly reordered).

  1. All settings defined in the project’s config file should be exposed, by default, as a public property that can be read. I’m not interested in writing to them.
  2. Based upon the default value (in the config file) each setting should be strongly typed (int, double, bool, string).
  3. The configuration file cannot be cluttered with setting-generation stuff. This was the whole issue with the Settings designer in .NET.
  4. Sometimes the project containing the config file is different than the project where the settings are needed (ex. WCF service hosts) so it should be possible to reference a config file in another project.
  5. Some settings are used by the infrastructure (such as ASP.NET) so they should be excluded.
  6. Some settings may need to be of a specific type that would be difficult to specify in the value (ex. a long instead of an int).

In this article we’re going to convert the static template to a dynamic template and begin working on requirements 1-3. From last time here are the areas of the template that need to be made more dynamic.

  1. The namespace of the type
  2. The name of the type, including pretty formatting
  3. Each public property name and type
Read More

Using T4 to Create an AppSettings Wrapper, Part 1

AppSettings are settings stored in your configuration file under the <appSettings> element. Almost every application has them. Each setting consists of a name and value. To access such a setting in code you need only do this.

string setting = ConfigurationManager.AppSettings["someSetting"];

There are a couple of problems with this approach.

  • Quite a bit of boilerplate code to access a setting given what it is actually doing
  • The setting name is hard coded and must match the config file
  • The returned value is a string so if you need a different type then you’ll need to convert it

In .NET v2.0 Microsoft added the Settings class to work around these issues. It allows you to create a setting with a type and value and the designer will generate a type to back it where each property matches the setting. This seems great but never really took off. Not even Microsoft uses it in their own framework. Part of the problem is that the config entries it generates are overly complex storing things like type information, default values and other things. Needless to say appSettings continue to be popular anyway. Fortunately we can get the simplicity of appSettings with the power of the newer Settings class all via T4.

In this series of posts I’m going to walk through the process of generating such a template including the ability to add some more advanced functionality. A full discussion of T4 is beyond the scope of a blog so refer to the following links for more information.

Read More

Strongly Typed Application Settings Using T4

We all know that the Settings infrastructure added to .NET a while back is the “correct” way to create strongly typed application settings in configuration files. The problem with this approach though is that the entries generated in the config file aren’t pretty. You have only limited control over the naming, there are lots of extra information to wade through and non-devs can easily get confused. The Appsettings that has existed since v1 is a simpler approach that most people are comfortable with. But you lose a lot of functionality by using AppSettings. The Settings infrastructure brings into play a Settings class with strongly typed properties for the settings and default values for settings that are missing. But sometimes that is overkill or simplicity is just too important.

Read More

App.config Magic In Visual Studio

One area that causes lots of confusion to developers is the behavior of app.config while running within Visual Studio.  Because VS treats app.config specially it can be confusing when app.config does not work the way we expect.  This post will clarify how the config file is treated while running inside VS.  But first an aside.

Visual Studio Hosting Process

Debugging another process is not trivial.  There are lots of details to worry about.  Consequently debuggers are not simple pieces of code.  Starting a debugger can be expensive.  Beginning a few versions back VS started using the VS hosting process for debugging processes.  This process (vshost) is a wrapper around the debugger.  It allows the debugger to be started onced and then continue to run between debug sessions.  This helps to alleviate the overhead of the debugger and to also allow the debugger to maintain information between debugging sessions. 

The vshost process is started when VS loads a project that is using it.  You can see this by using Task Manager to see the running processes.  The actual process is called <project>.vshost.exe.  It is within this process that your code runs rather than through the normal <process>.exe.  Running through a host process is generally the best idea but it can cause some issues.  Therefore in the Debug properties of the executable’s project settings is an option to turn off the hosting process.

App.config

In .NET an application configuration file is required to be called <process>.exe.config.  This is the file that the configuration subsystem will load.  The file must reside in the same directory as the executable.  Because VS recreates the output directory each time you build you cannot just create this file and store it in the output directory.  Instead when you add an Application Configuration File item to your project it is added to the root of the project and called app.config.  This file is automatically copied to the output directory and renamed on each build.  Therefore after each build you will have a brand new configuration file.  Note that if you call the file anything else then this process will not work.  Rarely should you change the name.

For most development you will edit the configuration file in VS.  These changes will be automatically seen when the program runs under the debugger.  However if you make changes to the configuration file at runtime (while running under the debugger) the changes will only persist until the next build. At that time VS will overwrite the file with the root copy.  Therefore you CAN NOT run your program and modify the project’s configuration file.  The only way to emulate this behavior would be to copy the (modified) configuration back to the root project and rename it.  As an aside note that if you modify the configuration file at runtime but do not rebuild your code then the configuration changes may persist. 

Which Config Is Which?

One area of confusion is which configuration file your application is actually using while running under the debugger.  Many people will say it is the <process>.exe.config file but that is only partially correct.  Remember the vshost process of earlier?  It complicates things.  Remember that if you are using the VS host (the default) then your process is actually <process>.vshost.exe.  The configuration subsystem knows nothing about the VS host process so it expects to load the <process>.vshost.exe.config file.  When a project is configured to use the hosting process then VS will copy the standard <process>.exe.config file to <process>.vshost.exe.config when the debugger starts.  This gives the appearance that the hosting process is using the application’s configuration file when in fact it is VS that is just copying the file for us.  More importantly the configuration subsystem is not impacted by this change at all.  If the program modifies the configuration file while it runs then those changes are made to the <process>.vshost.exe.config file.  The actual process’s configuration and the project’s app.config files are not modified.   

DLL Config Files – Myth or Fact?

Many class libraries (DLLs) require configuration information.  Most devs like to rely on the configuration subsystem to store this information.  Makes sense.  If you add an application configuration to the class library project then VS will copy the app.config to the output directory and call it <project>.dll.config as you would expect.  It behaves just like a standard Windows application project.  Here’s the issue though – the configuration subsystem neither understands nor supports these files.  Let me say it again.  The configuration subsystem WILL NOT load .dll.config files. 

Technically you can change the mappings used by the subsystem to get it to load the files but that is beyond the scope of this article and beyond most applications.  The configuration subsystem will only load the <process>.exe.config file.  There are forum postings all the time from devs who are confused as to why their dll.config file contains some configuration section but the configuration subsystem won’t read it.  They believe, incorrectly, that the subsystem uses the configuration file associated with the assembly that is calling it.  That is false.  The subsystem only reads the application’s configuration file.

There are two possible workarounds to this issue.  The first workaround, the most common, is to merge the dll.config file into the application’s configuration.  This is generally the easiest approach and can be done at build time.  The alternative approach is to read the configuration file manually at runtime.  Some complex libraries follow this route but I cannot recommend it.  That is just too much code to write.

Redirecting Dependent Assembly Versions In .NET

With the release of Windows Vista there has been a large number of additions and changes made to the existing Windows common controls.  Unfortunately many of these changes require that you use version 6.0 of the common controls library.  This is not the version WinForms apps will use by default. 

This article will discuss one solution for changing the version of a dependent assembly an application will use.  For example purposes this article will discuss how to redirect a WinForms application to use a specific version of the unmanaged common controls library.  The concept can be applied to any unmanaged library or assembly that an application may depend upon.

How .NET Finds a Dependent Assembly

Loading an assembly is a two step process for the runtime.  The first step is to identify the specific version to load.  The second step is to find the appropriate assembly.  A full discussion is beyond the topic of this article so I recommend you read the book Essential .NET: Volume 1 for a more detailed discussion.  The MSDN topic How the Runtime Locates Assemblies also covers this in detail. 

When an assembly needs to be loaded the loader first tries to determine which version to load.  If this is an assembly contained in the metadata (through a reference in the IDE) then the entire assembly information is available including the version number.  If you call Assembly.Load explicitly then the loader only has the information you provide.  Assuming a version is specified the loader will now look for the the appropriate assembly.  Note that only strongly named assemblies have version matching done.  For non-strongly named assemblies the first assembly found will be used.

Once the loader has identified the version to load it then goes through the process of finding the assembly.  For strongly named assemblies the loader will look in the GAC first.  If the appropriate version is not found then the loader continues using the standard search path.  The search path, slightly configurable, includes the application directory and a few child directories based upon the assembly name.  If the loader finds the assembly then it will try to load it otherwise it will fail the call.

The above discussion is a simplification of the process.  Refer to the resources mentioned earlier for full details.

A Typical Scenario

Let us set up a typical application architecture so we can have a better understanding of the issues involved in assembly versioning.  We will have the following solutions set up.

 

The SharedCompanyBusiness assembly is a binary reference to a company-wide assembly used in all products.  It is not under our direct control and is versioned and released independently.  It is not strongly named nor is it stored in the GAC.  Products must compile against the version they are most compatible with.  The shared assembly is stored with the application during installation.  It is currently at v3.5.

ThirdPartyControls is a strongly named assembly stored in the GAC.  It contains some UI controls the application uses.  It is currently at v10.0.

The two addin projects are built as part of the main solution but they are actually maintained separately.  Whenever a new version of the application is released then customers get new versions of the addins but the dev teams responsible for the addins can released interim versions as well.

All references other than the third party and shared assemblies are project references.  All the projects are currently v1.0.

Conflicting Assembly Versions

Non-strongly Named Assemblies

The above scenario is pretty common and will work as designed.  But now we will introduce a change into the application that will have a ripple effect.  SharedCompanyBusiness is updated to v4.0 and new classes are added.  The two addins are updated to use the newer version because they need some of the functionality it exposes.  The addins need to be released but with the newer shared assembly.  We have a problem.

The problem is that the application itself uses v3.5 but the addins are expecting v4.0.  Since the shared assembly is not strongly named version numbers do not matter.  If we ship the updated version of the shared assembly with the addins then the application will be using v4.0 even though it was never tested against that version. Provided v4.0 is backwards compatible with v3.5 the application will run fine.  If the addins do not update the shared assembly then they will crash at runtime because they will attempt to use a type or member that does not exist in the assembly.  The worse possible situation is when v4.0 makes a breaking change to the code, such as removing a type.  We are then in a no win situation as we cannot use either version without causing a crash.

In summary, for non-strongly named assemblies no version checking is done.  The first assembly that is found is used irrelevant of whether the code was compiled against it or not.  This can cause runtime crashes if the assemblies are not compatible.

Strongly Named Assemblies

Now suppose that ThirdPartyControls is updated from v10.0 to v11.0.  This is a strongly named assembly and resides in the GAC.  The GAC allows for side-by-side versioning of assemblies.  Therefore irrelevant of what versions are installed the application will still want to use v10.0.  This is great if the version is installed but suppose it gets uninstalled.  In that case the resolver will know to look for v10.0 but it will not find it.  The resolver will fail the call.  It does not matter whether a newer version is available or not. 

Versioning Policy

In some cases it is reasonable to use a newer version of an assembly if it is available.  Security fixes that result in a patched assembly come to mind.  The loader does not know the assembly versions are compatible so it will always fail the call.  You have to tell the loader that it is OK to use the newer version.  To do that you create a versioning policy inside the application’s config file.  Here is the entry we would place in CoolApp’s config file to tell it that it should use v11.0 of the ThirdPartyControls library.

<configuration>
   <runtime>
      <assemblyBinding xmlns=”urn:schemas-microsoft-com:asm.v1“>
         <dependentAssembly>
               <assemblyIdentity 
                     name=”ThirdPartyControls” 
                     publicKeyToken=”abcdef1234567890ab“>
                  <bindingRedirect 
                       oldVersion=”10.0.0.0” 
                       newVersion=”11.0.0.0” />                            
               </assemblyIdentity>            
         </dependentAssembly>              
      </assemblyBinding>
   </runtime>
</configuration>

The assemblyIdentity element identifies the specific assembly we care about. For strongly named assemblies this will be the assembly name, the public key and any other information we would like to use to distinguish the assembly from other variants.  One attribute that was not included here is the type attribute. The type attribute indicates the processor type of the assembly such as x86, msil or amd64.  It is useful for differentiating between processor architectures.

The bindingRedirect element tells the loader that instead of using v10.0 of the assembly we should instead use v11.0.  This allows us to use newer versions of an assembly even though we compiled with an older version.  Of course if the versions are not compatible then a runtime error will occur.

Wildcards are not allowed in the oldVersion attribute but you can specify a range of versions like so: 10.0.0.0-10.99.99.99.  This allows you to redirect all versions of an assembly to a newer version.

Publisher Policy

The biggest issue with versioning policies is that it must be applied to each application.  If the shared assembly is used by many applications, which is primarily why you would store it in the GAC, then it can be a hassle to update each application.  For some changes, such as security fixes, you can be confident in compatibility and you want to force everyone to use the new, more secure, version.  As the publisher of the assembly you can create a publisher policy. 

A publisher policy is added to the updated assembly, as a manifest, and stored in the GAC.  The publisher policy works just like a versioning policy except it applies to all applications irrelevant of whether they realize it or not.  It is ideal for security fixes.  In fact the .NET framework uses this approach when releasing service packs.  When a service pack is released a publisher policy is included with it.  This causes all applications to use the new service packed version irrelevant of what version they were compiled against.

Mixed Assembly Versioning

The above discussion handles the original topic of this article but a few additional topics are worth mentioning.  In the original scenario there was not a situation where an assembly was referenced more than once within a single project.  We will modify the scenario to introduce this issue.

In this modified scenario the addin logic has been moved from CoolBusiness to CoolAddin.  The addin projects now reference the CoolAddin project.  CoolAddin requires some core logic so it references CoolBusinessCoolBusiness is removed from the addin projects as the core logic is in CoolAddinCoolApp requires access to the addins and the business logic so it references both CoolBusiness and CoolAddin

Technically CoolApp has two references to CoolBusiness: one explicit and one implicit through CoolAddin.  This is where mixed assembly versions can cause problems.  If CoolAddin uses a different version of CoolBusiness than CoolApp (possible if the projects were developed by different teams) then the compiler will not know which version to use.  The compiler will generate a warning in this case but the warning might be a little confusing.  The warning will say that CoolApp is using version X of assembly CoolBusiness but it depends on an assembly that uses version Y.  Ideally this should have been an error because it will cause untold suffering at runtime but there are very rare occasions where the message can be safely ignored.

If you ever get this warning then you need to fix it.  The problem will manifest itself at runtime in one of several different ways.  One of the more common ways is for a TypeLoadException to occur when trying to load a type from the assembly.  The exception will say that the type does not exist but using Reflector you will be able to verify that it does.  Another common exception is an InvalidCastException when you try to assign a variable of type A to an instance of type A where type A is defined in the conflicting assembly.  What is going on?

First a couple of important points about the runtime.  Firstly the loader will only load an assembly once.  Once it is loaded subsequent requests for the same assembly will result in the original assembly being returned.  A consequence of this is that the first assembly that the loader finds a match for will be the one it uses. 

The second point is that for strongly named assemblies with fully versioned names the loader can load multiple versions of the same assembly.  The runtime uniquely identifies a type by its fully scoped name, including assembly.  A type called Utility in namespace MyCompany.MyProduct of assembly A is distinct from the MyCompany.MyProduct.Utility type in assembly B.  The runtime knows the differences as they were fully generated during compilation.  You cannot automagically redirect a type to a different assembly without the runtime throwing an exception.

Do you see the problem yet?  If CoolApp loads v1.0 of CoolBusiness but CoolAddin tries to load v2.0 it will not work.  Since the assemblies are not strongly named whichever one gets loaded first wins.  In the case of v1.0 being loaded CoolAddin will likely try to load a type that does not exist and, hence, get an exception.  Of course if CoolApp was compiled with v2.0 and CoolAddin used v1.0 then things would likely work, for now at least.

If we now turn our attention to strongly named assemblies we can see the other common problem with mixed assembly versions.  Suppose for a minute that CoolBusiness was strongly named.  During compilation CoolApp used v1.0 but CoolAddin used v2.0.  Because it is strongly named two copies (v1.0 and v2.0) of CoolBusiness could be loaded at the same time.  But that would mean we have two copies of every type that is shared by the two versions.  Which one gets used at runtime depends upon where it is referenced.  Anywhere inside CoolApp would use v1.0 while anywhere inside CoolAddin would use v2.0.  Provided they remained separate things would work but this is unlikely.  Instead it is more likely that eventually CoolAddin would pass a v2.0 object to CoolApp, which expects v1.0, and we would get a type exception.  What makes this hard to trace down is that even in the debugger we would see that the object is of the correct named type but the full typename would not match.

To avoid the issues of mixed assembly versions ensure that all your projects use the same dependent assembly versions.  If necessary use versioning policies to enforce this.

Win32 Libraries

We have come full circle but we have not addressed the original example of the article: how do we force a WinForms app to use newer versions of Common Controls, a Win32 library?  We have already learned how to do it.  We just need to apply it.  We will create a versioning policy that tells our application to use the desired version, v6.0 in this case, rather than the standard version.  The problem is that we are not loading an assembly but a Win32 library.  Therefore we will place the versioning information in an application manifest and store it in the assembly directly.  The manifest will be located and read by the loader before our application gets far in the loading process.  The syntax is identical to the configuration file.  Here are the steps for Visual Studio 2008 (VS2005 has a few additional steps).

  1. Add a manifest file to your executable project if you do not already have one.  The application manifest is an item in the Add New Items dialog for the project.
  2. Modify the assemblyIdentity and description elements to match your application.
  3. Add the following XML fragment to the manifest.
    <assembly …>
       <dependency>
          <dependentAssembly>
             <assemblyIdentity
                type=”win32
                name=”Microsoft.Windows.Common-Controls
                version=”6.0.0.0
                processorArchitecture=”X86
                publicKeyToken=”6595b64144ccf1df
                language=”*” />                    
          </dependentAssembly>
       <dependency
    </assembly>

Compile and run the application and the appropriate version should be loaded.  You can do this with any Win32 library that supports side-by-side versioning.  The hard part is determining the values to use.  The easiest way is to load the code up in the debugger and get the library from the debugger.  The type will be win32 for Win32 libraries. 

Note that as of VS2008 SP1 Beta it appears that v6.0 of the Common Controls library is automatically used for v2.0 applications.  Still this technique can be used for other native libraries as well.

Creating Configuration Sections

The .NET configuration subsystem is used throughout the framework.  Entire subsystems rest on top of it including security policies, application settings and runtime configuration.  ASP.NET uses the configuration subsystem heavily.  Applications can take advantage of the subsystem as well by creating their own configuration sections.  Unfortunately it is not straightforward.  This article will discuss how to create new configuration sections in .NET.

A point of clarification is needed.  Application settings, while relying on the configuration subsystem, are not related to configuration sections.  This article will not discuss the creation or usage of application settings.  Application settings, for purposes of this article, would be though settings that you can define in a project’s property pages.

History

Since the configuration subsystem is used throughout the framework it has been available since the initial release of .NET.  In v1.x you could extend the configuration subsystem by implementing the IConfigurationSectionHandler.  This interface boiled down to parsing XML elements.  While usable it was a little much to implement.  It also didn’t allow much more than reading in XML attributes and translating them into usable values.

In v2.0 the v1.x interface was wrapped in a more complete, complex set of classes.  The v2.0 subsystem allows for declarative or programmatic declaration of strongly type configuration properties, nested configuration settings and validation.  The newer subsystem also allows reading and writing configurations.  Additionally the need to manually parse XML elements has been all but removed.  While this makes it much easier to define configuration sections it also makes it much harder to deviate from the default behavior. Adding in sparse documentation and confusing examples results in a lot of postings in the forums about how to get things to work.

This article will focus exclusively on the v2.0 classes.  While we will not discuss the v1.x interface any it is important to remember that it still exists and resides under the hood.

Are We there Yet?

Rather than building up an example as we go along we are going to first take a look at the configuration that we ultimately want to be able to read.  We will then spend the rest of the time getting it all set up.  This is a typical design approach for configurations.  You know what you want.  All you need to do is get the subsystem to accept it.

For this article we are going to build a configuration section for a hypothetical test engine.  The test engine runs one or more tests configured in the configuration file.  The engine simply enumerates the configured tests and runs them in order.  Here is a typical section that we will want to support.  This will be refered to as the target XML throughout this article.

<tests version=”1.0” logging=”True“>
    <test name=”VerifyWebServer” 
          type
=”TestFramework.Tests.ServerAvailable
          failureAction=”Abort“>
        <parameter name=”url” value=”http:\www.myserver.com“>
    </test>
    <test name=”CheckService1” 
          type
=”TestFramework.Tests.WebServiceInvoke” 
          async
=”true” timeOut=”120“>
        <parameter name=”url” 
                   value
=”http:\www.myserver.comservice1.asmx” />
        <parameter name=”parameter_1” value=”hello” />
        <parameter name=”returns” value=”HELLO” />
    </test>
</tests>

Each test is represented by an XML element and all tests are contained in a parent tests element.  Each test must have a unique name and a type attribute.  The type attribute is used by the engine to create the appropriate test class to run.  A test can have some optional attributes as well.  The failureAction attribute specifies what should happen if the test fails (continue, abort, alert).  The default is to continue.  The async attribute is a boolean value indicating whether the test should be run synchronously or asynchronously.  The default is false.  The timeOut attribute specifies how long to wait for the test to complete (in seconds) and is only meaningful for async tests.  The default is 10 seconds.  

Configuration Files

The configuration subsystem must be able to map each XML element to a .NET type in order to be able to read it.  The subsystem refers to a top level XML element as a configuration section.  Generally speaking each configuration section must map to a section handler.  This is where the v1.x configuration interface mentioned earlier comes in.  When creating a new configuration you must define the configuration section that the subsystem will load.  Let’s take a look at a standard application configuration for a moment.  This will be refered to as the example XML throughout this article.

<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>    
    <configSections>
        <section name=”tests” 
            type
=”TestFramework.Configuration.TestsSection,ConfigurationTest” />
    </configSections>

    <tests>
        <!– Tests go here –>          
    </tests>        
</configuration>

All XML elements will reside inside the configuration root element.  In the above file the application has defined a new section for the tests and created an empty tests element where the tests will go.  Every section inside the configuration element must have a  section handler defined for it.

To define a custom section in the configuration file we have to use the configSections element.  This element might already exist in the file.  It is a best practice to always put it as the first section in the file.  In the above example the element tells the subsystem that whenever it finds a tests element it should create an instance of the TestFramework.Configuration.TestsSection class and then pass the XML on for processing.  The type is the full typename (including namespace) followed by the assembly containing the type.  It can be a full or partial assembly name.  If the subsystem cannot find a section handler for an XML element or the type is invalid then the configuration subsystem will throw an exception.

“So I have to define a section handler for every XML element?  That’s nuts, forget it.”  Well, not exactly.  Firstly the configuration subsystem only cares about the top level XML elements (anything direct under configuration).  Child elements do not need a configuration section (but they do need a backing class as we’ll discuss later).  Secondly the subsystem supports section groups.  Section groups can be used to group sections together without requiring a handler.  The framework itself generally separates sections by the namespace they are associated with.  For example ASP.NET configuration sections generally reside in the system.web section group.  You define a section group in the configSections element like so.

<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>
    <configSections>
        <sectionGroup name=”testFramework“>
            <section name=”tests” 
                 type
=”TestFramework.Configuration.TestsSection,TestFramework.Configuration” />                 
        </sectionGroup>
          
    </configSections
    
    <testFramework>
        <tests>
            …                 
        </tests>          
    </testFramework>
</configuration>

Section groups are useful for grouping together related sections. Groups can be nested inside other groups.  We will not discuss section groups further as they have no impact on section handlers.  A section handler does not care about anything above it in the XML tree.

A question you may be wondering about is why you do not see any section definitions for the framework sections.  That is because the subsystem actually looks for section handlers in the application, machine and domain configuration files.  The machine and domain configurations reside in the framework directory and can be configured by an administrator to control .NET applications.  If you were to look into these files you will eventually find section handler definitions for each of the pre-defined sections.  Furthermore you can look into the system assemblies and find their corresponding handler classes.  There is nothing special about the pre-defined sections.

A final note about configuration files if you have never worked with them.  When you add an application configuration file to your project it will be called app.config.  The runtime expects the configuration file to be named after the program executable (so prog1.exe would be prog1.exe.config).  The IDE will automatically copy and rename the app.config project item to the appropriate name and store it with the binary during a build.  Any changes you make to the configuration file (in the output directory) will be lost when you rebuild. 

Mapping XML to .NET

Before diving into the .NET code we need to understand how the subsystem will map the XML data to .NET.  The subsystem uses sections, elements and properties to map from XML elements and attributes. 

A configuration element is a class that derives from ConfigurationElement.  This class is used to represent an XML element.  Every XML element will ultimately map to a configuration element.  XML elements are normally used to house data that is too complex for a simple attribute value.  Elements are also used for holding collections of child elements.  Configuration elements will be used, therefore, to represent complex objects and/or parent objects.

A configuration property is ultimately represented by a ConfigurationProperty.  Normally, however, we apply an attribute to a class property to define them so the actual declaration will be hidden.  Configuration properties represent XML attributes.  Every XML attribute associated with an XML element will map to a configuration property on the corresponding configuration element.  As we will discuss later properties can be optional, have default values and even do validation.

A configuration section is a special type of configuration element.  A section derives from ConfigurationSection, which itself derives from ConfigurationElement.  For our purposes the only distinction is whether the element is a top level element or not.  If the element is a top level element that is defined in the configSections of the configuration file then it will be a configuration section (and derive from the appropriate class).  For all other purposes it works like an element. 

The configuration subsystem requires that every XML element and attribute be defined by either a configuration element/section or configuration property.  If the subsystem cannot find a mapping then an exception will occur.  We will discuss later how we can have some control over this.

To start things off we will define the configuration section for our example.  Following the precedence set up by the framework we will isolate our configuration classes to a Configuration subnamespace.  We will name configuration sections as -Section and elements as -Element.  The beginning name will match the XML element name but using Pascal casing.

Here is how we would define our configuration section. 

using System;
using System.Configuration;

namespace TestFramework.Configuration
{
   public class TestsSection : ConfigurationSection
   {
      …
   }
}

We will fill this class in as we progress.  At this point we have discussed enough to get the subsystem to load our example XML and return an instance of the TestsSection class.

Configuration Manager

In v2+ you will use the ConfigurationManager static class to interact with the configuration subsystem.  This class provides some useful functionality but the only one we care about right now is GetSection().  This method requires the name of a section and, upon return, will give us back an instance of the associated configuration section class with the configuration data.  Here is how our test engine would get the list of tests to run. 

using System;
using System.Configuration;> 
using TestFramework.Configuration;

namespace TestFramework
{
   class TestEngine
   {
      public void LoadTests ( )
      {
         TestsSection section = 
             ConfigurationManager.GetSection(“tests”as TestsSection;
         …
      }
   }
}

The subsystem will only parse a section once.  Subsequent calls will return the same data.  If the file is changed externally then the changes will not be seen without restarting the application.  You can force the subsystem to reload the data from disk by using ConfigurationManager.RefreshSection.

ConfigurationManager is in the System.Configuration namespace of the same named assembly.  This assembly is not automatically added as a reference so you will need to add it manually.  For those of you familiar with the v1.x subsystem, especially AppSettings, note that most of the classes are obsolete.  You should use ConfigurationManager for all new development.

As an aside almost all errors in the subsystem will cause an exception of type ConfigurationException or a derived class.  It can be difficult to tell from the exception what went wrong.  Badly formed XML or a missing section handler, for example, will generate a generic error saying the subsystem failed to initialize.  Use exception handling around all access to the subsystem but do not expect to be able to generate useful error messages from the resulting exception.

Configuration Properties

XML elements normally have one or more attributes associated with them.  In the target XML the tests element has a version and logging attribute.  The version is used for managing multiple versions of the engine and must be specified.  The logging attribute specifies that the engine should log all test runs.  It defaults to false.  Modify the example XML to include these attributes.  Trying to load the tests at this point will cause an exception because the attributes are not supported by the configuration section.

For each attribute on a section/element a configuration property must be defined.  Configuration properties can be defined either declaratively through attributes or programmatically.  Declarative properties use an attribute on public properties to identify configuration properties.  Programmatically declaring configuration properties requires that a configuration property field be created for each attribute.  Declarative properties are easier to write and understand but run slightly slower than programmatic properties.  Otherwise either approach can be used or they can be used together. 

Declaratively

To define a property declaratively do the following.

  1. Declare a public property in the section/element class.
  2. Define a getter and, optionally, a setter for the property.
  3. Add a ConfigurationProperty attribute to the property.

The public property name will generally match the XML attribute but use Pascal casing.  The type will be a standard value type such as bool, int or string.  Use the type most appropriate for the property.

There are no fields to back the properties.  The subsystem is responsible for managing the property values.  The base class defines an indexed operator for the section/element that accepts a string parameter.  The parameter is assumed to be the name of an XML attribute.  The base class will look up the attribute and get or set the value as needed.  The properties are assumed to be objects so casting will be necessary.

The ConfigurationProperty only requires the name of the XML attribute.  There are several optional parameters that can be specified as well.

Parameter Default Meaning
DefaultValue None If the attribute is not specified then the corresponding property will have the specified value.
IsDefaultCollection False Used for collections.
IsKey False Used for collections.
IsRequired False True to specify that the attribute is required.
Options None Additional options.

The DefaultValue parameter should be used to give a property a default value.  Since the base class manages property values rather than using fields it is not necessary to worry about this parameter in code.

The IsRequired parameter specifies that the attribute must be specified.  If it is not then an exception will occur.  This parameter should be used for properties that can have no reasonable default value.  The IsRequired parameter for the attribute does not work when applied to a child element.  The subsystem will automatically create a new instance of any child elements when it reflects across the configuration properties.  Later when the subsystem tries to verify that all required properties have received a value it cannot tell a difference between default initialized elements and those that were contained in the configuration file.  To use the IsRequired parameter with a child element you must programmatically declare the property instead.

Here is the modified TestsSection class with the configuration properties declaratively defined.  After modifying the code run it and verify the attribute values are correct.  Try removing each attribute from the example XML and see what happens.

public class TestsSection : ConfigurationSection 

    [ConfigurationProperty(“logging”, DefaultValue=false)] 
    public bool Logging 
    { 
        get { return (bool)this[“logging”]; } 
        set { this[“logging”] = value; } 
    } 

    [ConfigurationProperty(“version”, IsRequired=true)] 
    public string Version 
    { 
        get { return this[“version”as string; } 
        set { this[“version”] = value; } 
    } 

 

Programmatically

To define a property programmatically do the following.

  1. Create a field for each attribute of type ConfigurationProperty.
  2. Add each field to the Properties collection of the base class.
  3. Declare a public property for each attribute.
  4. Define a getter and, optional, a setter for each property.

The configuration field that is created for each attribute is used in lieu of an attribute.  The constructor accepts basically the same set of parameters.  Once the fields have been created they must be associated with the section/element.  The configuration properties associated with a section/element are stored in the Properties collection of the base class.  When using declarative programming the properties are added automatically.  In the programmatic approach this must be done manually.  The properties cannot be changed once created so it is best to add the configuration  properties to the collection in the constructor.

A public property for each attribute is created in a similar manner as with declarative programming. The only difference is the lack of an attribute on the property.  Since the configuration property is a field in the class you can use the field rather than the property name if desired.

Here is a modified TestsSection class using the programmatic approach. 

public class TestsSection : ConfigurationSection 

    public TestsSection () 
    { 
        Properties.Add(m_propLogging); 
        Properties.Add(m_propVersion); 
    } 

    public bool Logging 
    { 
        get { return (bool)this[m_propLogging]; } 
        set { this[m_propLogging] = value; } 
    } 

    public string Version 
    { 
        get { return (string)this[m_propVersion]; } 
        set { this[m_propVersion] = value; } 
    } 

    private ConfigurationProperty m_propLogging = 
        new
 ConfigurationProperty(“logging”typeof(bool), false);
    private ConfigurationProperty m_propVersion = 
        new
 ConfigurationProperty(“version”typeof(string), 
               null, ConfigurationPropertyOptions .IsRequired); 
}

The programmatic approach can be optimized by using static fields and static constructors.  But that requires more advanced changes so we won’t cover that today.

Validation

The subsystem will ensure that an XML attribute can be converted to the type of the property.  If it cannot then an exception will occur.  There are times though that you want to do even more validation.  For example you might want to ensure a string is in a certain format or that a number is within a certain range.  For that you can apply the validator attribute to the property as well.  There are several different validators available and you can define your own.  The following table defines some of them.

Type Description
CallbackValidator Calls a method for more complex validation.
IntegerValidator Validates a numeric value including range and precision.
LongValidator Same As IntegerValidator but applies to longs.  (Notice that MS failed to follow the naming guidelines for this type :})
PositiveTimeSpanValidator Validates a time duration.
RegexStringValidator Validates a string against a regular expression.
StringValidator Validates a string for length and content.
SubclassTypeValidator Validates the type of a value.
TimeSpanValidator Validates a time duration.

The type name given is the name of the underlying validator class that is used.  You can, if you like, create an instance of the type and do the validation manually.  More likely though you’ll apply the attribute of the same name instead.  Here is a modified version of the version attribute to ensure that it is of the form x.y.

[ConfigurationProperty(“version”, IsRequired = true
[RegexStringValidator(@”(d+(.d+)?)?”)] 
public string Version 

   get { return this[“version”as string; } 
   set { this[“version”] = value; } 
}

If you are good with regular expressions you might have noticed that the expression allows for an empty string.  This is a peculiarity of the subsystem.  It will call the (at least the Regex) validator twice.  The first time it passes an empty string.  The validator must treat an empty string as valid otherwise an exception will occur.

Child Elements

Now that we can define sections and properties all we have to do is add support for child elements and we’re done.  A child element, as already mentioned, is nothing more than a configuration element.  In fact all that is needed to support child elements is a new configuration element class with the child configuration properties.  Remember that a configuration section is just a top-level configuration element.  Everything we have discussed up to now applies to configuration elements as well.

Here is the declaration for the configuration element to back the test XML element.  The properties are included.

public class TestElement : ConfigurationElement 

   [ConfigurationProperty(“async”, DefaultValue=false)] 
   public bool Async 
   { 
      get { return (bool)this[“async”]; } 
      set { this[“async”] = value; } 
   } 

   [ConfigurationProperty(“failureAction”, DefaultValue=“Continue”)] 
   public FailureAction FailureAction 
   { 
      get { return (FailureAction)this[“failureAction”]; } 
      set { this[“failureAction”] = value; } 
   } 

   [ConfigurationProperty(“name”, IsKey=true, IsRequired=true)] 
   public string Name 
   { 
      get { return this[“name”as string; } 
      set { this[“name”] = value; } 
   } 

   [ConfigurationProperty(“timeOut”, DefaultValue=120)] 
   [IntegerValidator(MinValue=0, MaxValue=300)]
   public int TimeOut 
   { 
      get { return (int)this[“timeOut”]; } 
      set { this[“timeOut”] = value; } 
   } 

   [ConfigurationProperty(“type”, IsRequired = true)] 
   public string Type 
   { 
      get { return this[“type”as string; } 
      set { this[“type”] = value; } 
   } 
}

public enum FailureAction { Continue = 0, Abort, }

A couple of things to note.  FailureAction is actually an enumeration.  This is perfectly valid.  The only restriction is that the attribute value must be a, properly cased, member of the enumeration.  Numbers are not allowed. 

The second thing to note is the IsKey parameter applied to Name.  Only one property can have this parameter set.  It is used to uniquely identify the element within a collection of elements.  We will discuss it shortly.

The configuration section needs to be modified to expose the element as a child.  Here is the modified class definition.

public class TestsSection : ConfigurationSection 

   …

   [ConfigurationProperty(“test”)] 
   public TestElement Test 
   { 
       get
 { return this[“test”as TestElement; } 
   } 
}

Notice that we did not add a setter here since the user will not be adding the test explicitly.  Modify the example XML to include (only) one of the elements from the target XML.  Comment out any parameter elements for now.  Compile and run the code to confirm everything is working properly.

As an exercise try creating the parameter child element yourself.  Modify the example XML to include (only) one of the elements from the target XML and verify it is working properly.  Do not forget to update the TestElement class to support the parameter.

Collections

The final piece of the configuration puzzle is collections.  To support a collection of elements a configuration collection class must be created.  This collection is technically just a class deriving from ConfigurationElementCollection.  What makes collections so frustrating is that there are quite a few things that have to be done to use them in a friendly manner.  Add to that confusing documentation and incorrect examples and it is easy to see why people are confused.

To keep things simple for now we are going to temporarily modify our example XML to fall in line with the default collection behavior.  We will then slowly morph it into what we want.  We will add support for multiple tests in the section first.  Here are the steps for adding a collection with default behavior to a section/element.

  1. Create a new collection class deriving from ConfigurationElementCollection.
  2. Add a ConfigurationCollection attribute to the class.
  3. Override the CreateNewElement method to create an instance of the appropriate type.
  4. Override the GetElementKey method to return the key property of an element.
  5. In the parent element modify the public property to return an instance of the collection type.

Here is boilerplate code for an element collection.  In fact you can create a generic base class if you want.  You’ll see why that is a good idea later.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 

   protected override ConfigurationElement CreateNewElement () 
   { return new TestElement(); } 

   protected override object GetElementKey ( 
           ConfigurationElement element ) 
   { return ((TestElement)element).Name; } 
}

CreateNewElement is called when the subsystem wants to add a new element to the collection as it is parsing.  The GetElementKey method is used to map an element to a unique key.  This is where the IsKey parameter comes in.  These are the only methods that have to be implemented but it is generally advisable to add additional methods for adding, finding and removing elements if the collection can be written to in code.

Now that the collection is defined it needs to be hooked up to the parent element.  Here is the updated TestsSection class property definition.  Notice that the property name and the XML element name were changed to clarify that it is a collection.

[ConfigurationProperty(“tests”)] 
public TestElementCollection Tests 

    get 
    { 
        return this[[“tests”as TestElementCollection; 
    } 
}

As an aside the ConfigurationCollection element can be applied to the public property in the parent class rather than on the collection type itself.  This might be useful when a single collection type can be used in several different situations.  In general though apply the attribute to the collection type.

The final changes we need to make are to the example XML itself.  The collection of tests need to be contained in a child element rather than directly in the section because that is how the collection is defined.  Additionally the default collection behavior is to treat the collection as a dictionary where each element maps to a unique key.  Elements in the collection can be added or removed or the entire collection cleared using the XML elements: add, remove and clear; respectively.  Here is the (temporary) updated example XML fragment.

<tests version=”1.0” logging=”True“>
    <tests>
        <add name=”VerifyWebServer” 
             type
=”TestFramework.Tests.ServerAvailable” 
             failureAction
=”Abort“>
            <parameter name=”url” value=”http:\www.myserver.com“>
        </add>
        <add name=”CheckService1” 
             type
=”TestFramework.Tests.WebServiceInvoke” 
             async
=”true” timeOut=”120“>
            <parameter name=”url” 
                 value
=”http:\www.myserver.comservice1.asmx” />
            <!–<parameter name=”parameter_1” value=”hello” />
              <parameter name=”returns” value=”HELLO” />–>                       
        </add>
    </tests>
</tests>

 

Altering Nameses

The first thing that you will likely want to change is the name used to add new items to the collection.  It is standard to use the element name when adding new items.  To change the element name for adding, removing and clearing items use the optional parameters on the ConfigurationCollection

[ConfigurationCollection(typeof(TestElement), AddItemName=“test”)] 
public class TestElementCollection : ConfigurationElementCollection
{
   …
}

 

<tests version=”1.0” logging=”True“>
    <tests>
        <test name=”VerifyWebServer” 
              type
=”TestFramework.Tests.ServerAvailable” failureAction=”Abort“>
            <parameter name=”url” value=”http:\www.myserver.com” />                  
        </test>
        …
           
    </tests>
</tests>

 

Default Collectionon

Having a parent element for a collection is necessary when you are dealing with multiple collections inside a single parent element.  Normally however this is not the case.  You can eliminate the need for an element around the collection children by using the default collection option on the configuration property.

Modify the configuration property that represents the default collection to include the IsDefaultCollection parameter.  Set the name of the property to an empty string.  If this is not done then the subsystem will fail the request.  During parsing any element that is found that does not match an existing configuration property will automatically be treated as a child of the default collection.  There can be only one default collection per element.

Here is the TestsSection modified to have Tests be the default collection.  The example XML follows.

public class TestsSection : ConfigurationSection 

   …

   [ConfigurationProperty(“”, IsDefaultCollection=true)] 
   public TestElementCollection Tests 
   { 
      get { return this[“”as TestElementCollection; } 
   } 
}

 

<tests version=”1.0” logging=”True“>
    <test name=”VerifyWebServer” 
          type
=”TestFramework.Tests.ServerAvailable” 
          failureAction
=”Abort“>
        <!–<parameter name=”url” value=”http:\www.myserver.com” />–>           
    </test>
    <test name=”CheckService1” 
          type
=”TestFramework.Tests.WebServiceInvoke” 
          async
=”true” timeOut=”120“>
        <!–<parameter name=”url” value=”http:\www.myserver.comservice1.asmx” /> 
        <parameter name=”parameter_1” value=”hello” /> 
        <parameter name=”returns” value=”HELLO” />–>            
    </test>
</tests>  

 

Collection Options

The default collection type allows for elements to be added, removed or the entire list cleared.  This is often not what is desired.  The alternative collection type allows for new elements to be added only.  To tell the subsystem that the collection should not allow changes to the existing elements and to allow only new elements it is necessary to overload a couple properties in the collection class.   

The CollectionType property specifies the type of the collection being used.  The default is AddRemoveClearMap which specifies a modifiable collection.  The alternative is BasicMap which allows only additions.  In the case of a basic map the add, remove and clear item names are not used.  Instead it is necessary to override the ElementName property to specify the name of child elements.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 

   protected override string ElementName 
   { 
      get { return “test”; } 
   } 

   public override ConfigurationElementCollectionType CollectionType 
   { 
      get { return ConfigurationElementCollectionType.BasicMap; } 
   }
   …
}

A warning about basic maps.  The collection type is a parameter to the ConfigurationCollection attribute.  However it does not appear to work properly when using a basic map.  Stick with overriding the property instead.

Now that you have seen how to add support for collections try updating the TestElement class to support multiple parameters using a default basic map collection.  At this point everything has been covered to create the code to read the target XML from the beginning of the article.

Updating Configurations

One of the features added in v2.x of the configuration subsystem was the ability to modify and save the configuration data.  While application configurations should remain read-only (for security purposes), user configuration files can be modified.  The example code is going to be modified to allow new tests to be added and saved.

To support modification of a configuration section/element the properties must support setters.  In our example code we made all the properties settable so we do not need to make any changes.  Some properties can have setters and others not.  It is all dependent upon what the configuration section needs to support.  The exception to the rule is collections.  By default a collection does not expose any methods to modify the collection elements.  It is necessary to manually add the appropriate methods if configuration collections can be modified.  Additionally the property IsReadOnly method must be overloaded to allow modifying the collection.

The following modifications need to be made to the test collection to support adding new tests, removing existing tests and clearing the collection.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 
{
   …
   
   public override bool IsReadOnly () 
   { return false; } 

   public void Add ( TestElement element ) 
   { BaseAdd(element); } 

   public void Clear () 
   { BaseClear(); } 

   public void Remove ( TestElement element ) 
   { BaseRemove(element.Name); } 

   public void Remove ( string name ) 
   { BaseRemove(name); } 
}

For test purposes the engine will be modified to generate a new test section (with a dummy test) if none can be found in the configuration file.

public void LoadTests () 

   //1 – Try and get the section
   m_Section = ConfigurationManager.GetSection(“tests”)
                   as TestsSection; 
   if ((m_Section == null) || 
       !m_Section.ElementInformation.IsPresent) 
   { 
      //2 – Open the configuration file 
      System.Configuration.Configuration cfg = 
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 

      //3 – Create the section if necessary
      if (cfg.Sections[“tests”] == null
         cfg.Sections.Add(“tests”new TestsSection()); 
      m_Section = cfg.GetSection(“tests”as TestsSection; 

      //4 – Add a dummy test 
      TestElement test = new TestElement(); 
      test.Name = “Dummy Test”
      test.Type = “DummyTest”
      m_Section.Tests.Add(test); 

      //5 – Save the changes 
      m_Section.SectionInformation.ForceSave = true
      cfg.Save(); 
   }; 
}

Let’s walk through the code.  The engine first tries to get the section (1).  If it fails to get the section then it will create a new one.  The configuration subsystem (contrary to documentation) seems to always return an instance of the section handler even if the actual section does not exist in the file.  The example code checks to determine if the section actually exists or not.

The subsystem uses the Configuration class (not the namespace) to represent a configuration file.  ConfigurationManager maintains an instance internally for the application configuration but this field is not exposed.  Instead it is necessary to explicitly open the configuration file and modify it.  Earlier it was mentioned that the data is only parsed once and that remains true.  However multiple instances of the section class are returned.  Changes made in one instance of a section are not visible in another. 

The engine next (2) opens the configuration file explicitly.  The engine then (3) creates a new section in the off chance that it did not exist yet.  Now the engine (4) creates a dummy test and adds it to the section.  Finally (5) the updated section is saved back to disk.

Temporarily comment out the tests element in the XML file and run the code.  Look at the XML file and confirm the new test was created.  What! It wasn’t?  Actually it was.  The problem is that the debugger is getting in the way.  By default the vshost process is used to run the program.  As a result the actual configuration file is <app>.vshost.exe.config.  Additionally this file is overwritten when debugging starts and ends.  Hence you are likely to miss the change.  Place a breakpoint at the end of the LoadTests method and run it again.  Now examine the configuration file to confirm the changes were made.

There are many more things that can be done to update the configuration file.  You can save the file elsewhere, save only some changes or even modify other files.  The preceding discussion should be sufficient to get you started though.

Dynamic Sections

The configuration subsystem is based upon deterministic parsing.  At any point if the subsystem cannot match an XML element/attribute to a configuration element/property it will throw an exception.  Configuration elements/sections expose two overridable methods (OnDeserializeUnrecognizedAttribute and OnDeserializeUnrecognizedElement) that are called if the parse finds an unknown element/attribute during parsing.  These methods can be used to support simple dynamic parsing.

For unknown attributes the method gets the name and value that was parsed.  If the method returns true then the subsystem assumes the attribute was handled otherwise an exception is thrown.  The following method (added to TestElement) silently ignores a legacy attribute applied to a test.  Notice that the element is compared using case sensitivity.  Since XML is case sensitive comparisons should be as well.

protected override bool OnDeserializeUnrecognizedAttribute ( 
         string
 name, string value ) 

   //Ignore legacy baseType attribute 
   if (String.Compare(name, “baseType”
           StringComparison.Ordinal) == 0
      return true

   return base.OnDeserializeUnrecognizedAttribute(name, value); 
}

For unknown elements the method must parse the XML manually and return true to avoid an exception.  The important thing to remember about this method is that all child elements must be parsed otherwise the subsystem will not recognize the element and call the method again.  The following method (added to TestElement) silently ignores a legacy child element that contained some initialization logic.  In this particular case the child elements are not important (or parsed) so they are skipped.

protected override bool OnDeserializeUnrecognizedElement ( 
           string
 elementName, System.Xml.XmlReader reader ) 

   //Ignore legacy initialize element and all its children 
   if (String.Compare(elementName, “initialize”
           StringComparison.Ordinal) == 0
   { 
      reader.Skip(); 
      return true
   }; 
  
   return base.OnDeserializeUnrecognizedElement(elementName, reader); 
}

A word of caution is in order when using collections.  If a collection’s item name properties have been modified (for example from add to test) then the method is called for each item.  The underlying collection overrides this method to handle the item name overrides.  Therefore do not assume that just because this method is called a truly unknown element has been found.

Before getting any wild ideas of how to get around the subsystem’s restrictions on element contents be aware that you cannot use the above methods to parse certain XML elements including CDECLs and element text.  These XML entities will always cause the subsystem to throw an exception.

Standard Sections

The v1.x subsystem supported several standard section types that continue to be useful.  They allow for storing custom settings without creating a custom section handler. The only downside is that they cannot be configured. 

DictionarySectionHandler can be used to store a set of key-value pairs in a section.  The following example demonstrates such a section.

<configSections>
         <section name=”settings” 
                  type
=”System.Configuration.DictionarySectionHandler” />
   </configSections>

   <settings>
         <add key=”Setting1” value=”1” />
         <add key=”Setting2” value=”2” />
         <add key=”Setting3” value=”3” />
   </settings>

Here is how it would be used.  Notice that the return value is Hashtable rather than a section handler instance.

Hashtable settings = ConfigurationManager.GetSection(“settings”as Hashtable;

The NameValueSectionHandler works identically to DictionarySectionHandler except the returned value is NameValueCollection.

The SingleTagSectionHandler is used to store a single element with attribute-value pairs.  The returned value is a Hashtable where the attribute names are the keys.

The three legacy section handlers can be used in lieu of creating custom section handlers when simple dictionaries or attribute-value pairs are needed.  As a tradeoff they do not support any of the advanced functionality of the subsystem including modification, validation or default values.