XML Configuration for .NET Applications

There are at least 2 options to consider for using user-specific configurations:

- Custom configuration mechanism: using technologies such as the XML Serializer.

- XML Configuration feature in .NET 2.0: This is the main topic of this blog entry.

One of the great features of .NET Framework (first appearing in V2.0) has been its XML configuration feature. It allows applications to load configuration settings which are stored in XML configuration files. The structure of these can be hierarchical allowing a developer to specify different config files at machine, executable, roaming user and user levels. The framework then automatically merges the settings if required.

The hierarchical structure is slightly different for web based applications. I will concentrate mainly on the executable context (not web applications). The diagram below shows the configuration hierarchy for the executable context:

The machine level configuration applies to all applications that run within CLR. It is also used by the .NET Framework itself for its configuration needs.

 

Each configuration level shown in this diagram is more specific than its parent and can override the settings defined higher up in the hierarchy. For instance a setting defined in Machine.config can be overridden by defining the same setting in User.config.

 

By default the configuration files are defined in the following paths:

 

         

Machine level config:
%SYSTEMROOT%\Microsoft.NET\Framework\v2.0.50727\CONFIG

Application level config: \[AppPath]\[ExeName].exe.config

Roaming user level config:
Windows XP:
C:\Documents and Settings\[User] \Application Data\[AppName]\[CodedPath]\[Version]\User.config
Windows Vista:
C:\Users\[User]\AppData\Roaming\[AppName]\[Vendor]\[CodedPath]\[Version]\User.config

User level config:
Windows XP:
C:\Documents and Settings\[User]\Local Settings\Application Data\[AppName]\[CodedPath]\[Version]\User.config
Windows Vista:
C:\Users\[User]\AppData\Local\[AppName]\[Vendor]\[CodedPath]\[Version]\User.config

The user level config is mainly associated to the profile of the logged on user and it makes it unsuitable for applications that require a different logon process. To accommodate for this type of scenario, it is possible to load different config files at each level of the hierarchy. This is done through the concept of “Configuration File Mapping”:

The ConfigurationManager class has 2 static methods which allow specific paths to .config files to be specified. These methods also automatically merge the config settings:

- OpenMappedMachineConfiguration

- OpenMappedExeConfiguration

When OpenMappedExeConfiguration is called, it requires an instance of the ExeConfigurationFileMap which allows you to specify exact paths to machine, executable, roaming user and local user config files..

The sample below demonstrates how to load a specific local user configuration file:

Debug.WriteLine(((CustomSection)ConfigurationManager.GetSection("CustomSection")).Text);

ExeConfigurationFileMap exeMap = new ExeConfigurationFileMap();

exeMap.ExeConfigFilename = "ConfigFileSample.exe.config";

exeMap.RoamingUserConfigFilename = "MyAppRoamingUser.config";

exeMap.LocalUserConfigFilename = "MyAppUser.config";

Configuration localUserConfig =

   ConfigurationManager.OpenMappedExeConfiguration(

     exeMap, ConfigurationUserLevel.PerUserRoamingAndLocal);

Console.WriteLine("Local user config path: " + localUserConfig.FilePath);

Debug.WriteLine(((CustomSection)localUserConfig.GetSection("CustomSection")).Text);

Debug.WriteLine(((CustomSection)ConfigurationManager.GetSection("CustomSection")).Text);

App.Config:

<configuration>

  <configSections>

    <section name="CustomSection"

             type="ConfigFileSample.CustomSection, ConfigFileSample"

             allowDefinition="Everywhere"

             allowExeDefinition="MachineToLocalUser"

  restartOnExternalChanges="true" />

  </configSections>

  <CustomSection text="Hello from app.config" />

</configuration>

MyAppUser.config:

<configuration>

  <CustomSection text="Hello from MyAppUser"/>

</configuration>

Output:

Hello from app.config

Hello from MyAppUser

Hello from app.config

See here for more information on how to load custom configurations.

With the XML Configuration capabilities of .NET 2.0, it is also possible to create custom configuration sections. The framework itself automatically takes care of XML parsing, validation, security and the population of the .NET object representing the custom configuration section. Here is a very simple custom configuration section:

// Define a custom section.

public sealed class CustomSection : ConfigurationSection

{

   // The collection (property bag) that conatains

  // the section properties.

   private static ConfigurationPropertyCollection _properties;

   // Internal flag to disable property setting.

   private static bool _readOnly;

   // The FileName property.

   private static readonly ConfigurationProperty _text =

       new ConfigurationProperty("text",

       typeof(string), string.Empty,

       ConfigurationPropertyOptions.IsRequired);

   // CustomSection constructor.

   public CustomSection()

   {

      _properties = new ConfigurationPropertyCollection();

      _properties.Add(_text);

   }

   // This is a key customisation.

   // It returns the initialised property bag.

   protected override ConfigurationPropertyCollection Properties

   {

      get { return _properties; }

   }

   private new bool IsReadOnly

   {

      get { return _readOnly; }

   }

   // Use this to disable property setting.

   private void ThrowIfReadOnly(string propertyName)

   {

      if (IsReadOnly)

         throw new ConfigurationErrorsException(

             "The property " + propertyName + " is read only.");

   }

   // Customizes the use of CustomSection by setting _readOnly to false.

   // Remember you must use it along with ThrowIfReadOnly.

   protected override object GetRuntimeObject()

   {

      _readOnly = true;

      return base.GetRuntimeObject();

   }

   [StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;'\"|\\",

       MinLength = 1, MaxLength = 60)]

   public string Text

   {

      get { return (string)this["text"]; }

      set

      {

         ThrowIfReadOnly("Text");

         this["text"] = value;

      }

   }

}

See https://www.codeproject.com/dotnet/mysteriesofconfiguration.asp and https://msdn2.microsoft.com/en-us/library/system.configuration.configurationsection(VS.80).aspx for more information on how to create custom sections and collections of these selections and elements.

Why use .NET 2.0 XML Configuration?

- Automatic inheritance and merge capabilities which allow developers to define common settings at higher levels of the hierarchy and override those if needed at lower levels.

- It is easily extendable using custom configuration settings and elements

- .NET 2.0 provides parsing, validation, security and population of the object model associated to the Configuration feature. For example it is possible to create a configuration section and validate its properties using Custom Attributes.

- Reuse of existing functionality

There is a lot more to be said about XML Configuration capabilities of .NET. However the objectives of this blog entry were to demonstrate how to load custom configuration files and to be an introduction to custom configuration sections.