Exotic .NET XML configuration file sections

The .NET framework makes application configuration really simple by supplying an out-of-the-box XML configuration file structure and associated classes to access the information contained within it.

By default, it allows configuration key-value pairs to be set and retrieved easily at run time. For example, the following configuration settings:

  <appSettings>
    <add key="ServerName" value="WinDB1"/>
    <add key="DatabaseName" value="MyData"/>
    <add key="EnableFeature" value="true"/>
  </appSettings>

…can be retrieved using:

var appSettingsReader = new AppSettingsReader();

var serverName = (string)appSettingsReader.GetValue("ServerName", typeof(string));
var databaseName = (string)appSettingsReader.GetValue("DatabaseName", typeof(string));
var enableFeature = (bool)appSettingsReader.GetValue("EnableFeature", typeof(bool));

…or (if you add a reference to System.Configuration):

var serverName = ConfigurationManager.AppSettings["ServerName"];
var databaseName = ConfigurationManager.AppSettings["DatabaseName"];
var enableFeature = bool.Parse(ConfigurationManager.AppSettings["EnableFeature"]);

…although as per a past post of mine, Encapsulated and strongly-typed access to .NET configuration files I don’t recommend that you litter your code with references to AppSettingsReader (or ConfigurationManager), but instead that you wrap all configuration up in a class so that settings are encapsulated away.

It is often necessary to hold more complex configurations though. Many developers I’ve worked with have tried to use the key-value model to store such settings, using a single string with various delimiters to hold separate different values. For instance, given a person construct with an ID, title, first name, last name and birth date attributes, some developers would be tempted to do something like this:

  <appSettings>
    <add key="Person1" value="I:1|T:Mr|F:Joe|L:Bloggs|D:1980-01-01"/>
  </appSettings>

…and then use string split functions to populate a Person class when reading information from the configuration file. While this approach works, it results in brittle, hard to read configuration files and complex string logic to take strings apart.

Instead, it is better to use the exotic configuration capabilities included in the framework. Here is an example demonstrating how you might store configuration information to populate the following Person class:

using System;

namespace ConfigurationExample
{
    public class Person
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    }
}

The configuration file to store such settings might look like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
  <configSections>
    <section name="people" type="ConfigurationExample.PeopleConfigurationSectionHandler, ConfigurationExample" />
  </configSections>
  
  <people>
    
    <person id="1">
      <title>Mr</title>
      <firstName>Joe</firstName>
      <lastName>Bloggs</lastName>
      <birthDate>
        <year>1980</year>
        <month>1</month>
        <day>1</day>
      </birthDate>
    </person>

    <person id="2">
      <title>Miss</title>
      <firstName>Jane</firstName>
      <lastName>Black</lastName>
      <birthDate>
        <year>1984</year>
        <month>2</month>
        <day>3</day>
      </birthDate>
    </person>
    
  </people>
  
</configuration>

A class implementing IConfigurationSectionHandler must be written to read this exotic configuration file structure; you will need a reference to System.Configuration in order to use this interface. The location of your class is given in the ‘configSections’ declaration at the top of the file. In this case the framework is being instructed to look for a class with name and namespace ‘ConfigurationExample.PeopleConfigurationSectionHandler’ in the ‘ConfigurationExample’ DLL/project in order to read the configuration section named ‘people’.

That class could look something like this:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Xml;

namespace ConfigurationExample
{
    public class PeopleConfigurationSectionHandler : IConfigurationSectionHandler
    {
        public object Create(object parent, object configContext, XmlNode section)
        {
            var people = new List<Person>();

            foreach (XmlNode node in section.ChildNodes)
            {
                if (node.NodeType == XmlNodeType.Comment)
                {
                    continue;
                }

                var id = GetAttributeInt(node, "id");
                var title = GetChildNodeString(node, "title");
                var firstName = GetChildNodeString(node, "firstName");
                var lastName = GetChildNodeString(node, "lastName");

                var birthDateNode = GetChildNode(node, "birthDate");

                var birthDateYear = GetChildNodeInt(birthDateNode, "year");
                var birthDateMonth = GetChildNodeInt(birthDateNode, "month");
                var birthDateDay = GetChildNodeInt(birthDateNode, "day");

                people.Add(new Person
                {
                    Id = id,
                    Title = title,
                    FirstName = firstName,
                    LastName = lastName,
                    BirthDate = new DateTime(birthDateYear, birthDateMonth, birthDateDay),
                });
            }

            return people;
        }

        private static string GetAttributeString(XmlNode node, string attributeName)
        {
            try
            {
                return node.Attributes[attributeName].Value;
            }
            catch
            {
                var message = string.Format("Could not read attribute named '{0}' in people section of configuration file", attributeName);
                throw new ConfigurationErrorsException(message);
            }
        }

        private static int GetAttributeInt(XmlNode node, string attributeName)
        {
            try
            {
                var value = GetAttributeString(node, attributeName);
                return int.Parse(value);
            }
            catch
            {
                var message = string.Format("Could not convert value stored in attribute named '{0}' to an integer", attributeName);
                throw new ConfigurationErrorsException(message);
            }
        }

        private static XmlNode GetChildNode(XmlNode parentNode, string nodeName)
        {
            try
            {
                return parentNode[nodeName];
            }
            catch
            {
                var message = string.Format("Could not find node named '{0}' in people section of configuration file", nodeName);
                throw new ConfigurationErrorsException(message);
            }
        }

        private static string GetChildNodeString(XmlNode parentNode, string nodeName)
        {
            try
            {
                return parentNode[nodeName].InnerText;
            }
            catch
            {
                var message = string.Format("Could not read node named '{0}' in people section of configuration file", nodeName);
                throw new ConfigurationErrorsException(message);
            }
        }

        private static int GetChildNodeInt(XmlNode parentNode, string nodeName)
        {
            try
            {
                var value = GetChildNodeString(parentNode, nodeName);
                return int.Parse(value);
            }
            catch
            {
                var message = string.Format("Could not convert value stored in node named '{0}' to an integer", nodeName);
                throw new ConfigurationErrorsException(message);
            }
        }
    }
}

Although there is quite a lot of code there, it’s all pretty simple. More importantly, it’s not too brittle and very simple to extend should you wish to add more attributes to the Person class.

The method required by the IConfigurationSectionHandler interface is Create:

public object Create(object parent, object configContext, XmlNode section)

This is what gets called when the framework asks for the contents of the ‘people’ section. The ‘section’ parameter contains the people node from the XML. In essence, the code creates a list in which Person objects are stored, and loops over the child nodes in order to extract the correct information.

There are two important points to note about the code:

1) The code that detects for the presence of XML comments:

if (node.NodeType == XmlNodeType.Comment)
{
    continue;
}

…ensures that developers don’t break the code by adding comments into the XML.

2) All the helper methods in the class (e.g. GetAttributeString, GetChildNodeString, etc…) must expect there to be issues with the XML and throw meaningful, self-explanatory exceptions when things do go wrong. When there are problems with these types of sections it is not always obvious what the problem is from the default exceptions .NET throws (particularly if you’re using this technique in the start up code of a Windows Service) so you will save yourself a lot of head scratching further down the line by writing good error messages. The chances are that you will have more than one section handler in your project so it’s a good idea to encapsulate such helper methods away in an abstract ConfigurationSectionHandler base class to cut down on duplication and to promote reuse.

Assuming you have registered your section handlers correctly in the ‘configSections’ declaration, you can retrieve the Person objects as follows:

var people = (IList<Person>)ConfigurationManager.GetSection("people");

Since the IConfigurationSectionHandler interface uses the ‘object’ type as its return value you need to cast the result to the correct type. It would have been nice if the interface was made generic, but I guess you can’t have everything!

If you’re following the advice in my previous post on configuration encapsulation, you’ll probably want to wrap this call up in your ConfigurationFile class, as follows:

using System.Collections.Generic;
using System.Configuration;

namespace ConfigurationExample
{
    public class ConfigurationFile
    {
        private readonly IList<Person> people;

        public ConfigurationFile()
        {
            people = (IList<Person>)ConfigurationManager.GetSection("people");
        }

        public IList<Person> People
        {
            get
            {
                return people;
            }
        }
    }
}

Note that a reference to the Person list is stored when the class constructs to save the running the cast each time you want to retrieve the contents of the configuration file section.

In summary, it’s a little more work to write exotic configuration file sections, but the advantages over brittle, difficult to extend key-value implementations is certainly worth the extra effort.

Encapsulated and strongly-typed access to .NET configuration files with dependency injection

This article builds on a previous post entitled Encapsulated and strongly-typed access to .NET configuration files. In this example I’ll be adding dependency injection using Castle Windsor.

As per my previous post, the code for this example is in GitHub, here:

https://github.com/stevebarker333/Injected-Encapsulated-Strongly-Typed-Configuration-Files

In this post I’ll assume that you’ve read the previous post, and that you have the existing code base as a starting point. The first step then, is to add Castle Windsor to the project using NuGet. This adds references to each project as follows:

ConfigurationDependencyInjection01

Since dependency injection works by resolving interfaces to types, each encapsulated ConfigurationFile will need to be given an interface by adding IConfigurationFile to each of the two UI projects:

ConfigurationDependencyInjection02

These new interfaces need contain only the properties that are unique to each UI. Access to core properties is achieved via the ICoreConfiguration, as follows:

UI1.Configuration.ConfigurationFile:

using System;
using API.Configuration;

namespace UI1.Configuration
{
    public interface IConfigurationFile : ICoreConfiguration
    {
        string UI1String { get; }
        int UI1Int { get; }
        Uri UI1Uri { get; }
    }
}

UI2.Configuration.ConfigurationFile

using System;
using API.Configuration;

namespace UI2.Configuration
{
    public interface IConfigurationFile : ICoreConfiguration
    {
        string UI2String { get; }
        int UI2Int { get; }
        Uri UI2Uri { get; }
    }
}

The definitions of the ConfigurationFile classes in each UI project need to be adjusted to include these new interfaces, as follows:

public class ConfigurationFile : 
    API.Configuration.ConfigurationFile, IConfigurationFile, 
    API.Configuration.ICoreConfiguration
{
    ...
}

Each UI then gets its own Injector class:

ConfigurationDependencyInjection03

…containing the following code:

UI1.DependencyInjection.Injector:

using API.Configuration;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Installer;
using UI1.Configuration;
using API;

namespace UI1.DependencyInjection
{
    public static class Injector
    {
        private static readonly object InstanceLock = new object();

        private static IWindsorContainer instance;

        public static IWindsorContainer Instance
        {
            get
            {
                lock (InstanceLock)
                {
                    return instance ?? (instance = GetInjector());
                }
            }
        }

        private static IWindsorContainer GetInjector()
        {
            var container = new WindsorContainer();

            container.Install(FromAssembly.This());

            RegisterInjector(container);
            RegisterConfiguration(container);
            RegisterWidget(container);

            return container;
        }

        private static void RegisterInjector(WindsorContainer container)
        {
            container.Register(
                Component.For<IWindsorContainer>()
                .Instance(container));
        }

        private static void RegisterConfiguration(WindsorContainer container)
        {
            container.Register(
                Component.For<IConfigurationFile, ICoreConfiguration>()
                .ImplementedBy(typeof(Configuration.ConfigurationFile))
                .LifeStyle.Singleton);
        }

        private static void RegisterWidget(WindsorContainer container)
        {
            container.Register(
                Component.For<IWidget>()
                .ImplementedBy(typeof(Widget))
                .LifeStyle.Singleton);
        }
    }
}

UI2.DependencyInjection.Injector:

using API.Configuration;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Installer;
using UI2.Configuration;
using API;

namespace UI2.DependencyInjection
{
    public static class Injector
    {
        private static readonly object InstanceLock = new object();

        private static IWindsorContainer instance;
        
        public static IWindsorContainer Instance
        {
            get
            {
                lock (InstanceLock)
                {
                    return instance ?? (instance = GetInjector());
                }
            }
        }

        private static IWindsorContainer GetInjector()
        {
            var container = new WindsorContainer();

            container.Install(FromAssembly.This());

            RegisterInjector(container);
            RegisterConfiguration(container);
            RegisterWidget(container);

            return container;
        }

        private static void RegisterInjector(WindsorContainer container)
        {
            container.Register(
                Component.For<IWindsorContainer>()
                .Instance(container));
        }

        private static void RegisterConfiguration(WindsorContainer container)
        {
            container.Register(
                Component.For<IConfigurationFile, ICoreConfiguration>()
                .ImplementedBy(typeof(Configuration.ConfigurationFile))
                .LifeStyle.Singleton);
        }

        private static void RegisterWidget(WindsorContainer container)
        {
            container.Register(
                Component.For<IWidget>()
                .ImplementedBy(typeof(Widget))
                .LifeStyle.Singleton);
        }
    }
}

Note that the technique described in my post entitled Allowing Castle Windsor to resolve two interfaces to the same instance allows the ConfigurationFile type to be accessed using both the API interface ICoreConfiguration and each of the UI interfaces, IConfigurationFile. Note also that the Widget is registered with the dependency injector. This requires some small changes to the Widget and the creation of an interface, IWidget:

using System;
using API.Configuration;

namespace API
{
    public class Widget : IWidget
    {
        public ICoreConfiguration Configuration { get; set; }

        public void DoSomething()
        {
            Console.WriteLine(Configuration.CoreString);
            Console.WriteLine(Configuration.CoreInt);
            Console.WriteLine(Configuration.CoreUri);
        }
    }
}
namespace API
{
    public interface IWidget
    {
        void DoSomething();
    }
}

Finally, we change the Program classes in the console applications to use the injector, as follows:

using System;
using UI1.Configuration;
using API;
using UI1.DependencyInjection;

namespace UI1
{
    public static class Program
    {
        public static void Main()
        {
            var configurationFile = Injector.Instance.Resolve<IConfigurationFile>();

            Console.WriteLine(configurationFile.UI1String);
            Console.WriteLine(configurationFile.UI1Int);
            Console.WriteLine(configurationFile.UI1Uri);

            var widget = Injector.Instance.Resolve<IWidget>();
            widget.DoSomething();

            Console.ReadLine();
        }
    }
}
using System;
using UI2.Configuration;
using API;
using UI2.DependencyInjection;

namespace UI2
{
    public static class Program
    {
        public static void Main()
        {
            var configurationFile = Injector.Instance.Resolve<IConfigurationFile>();

            Console.WriteLine(configurationFile.UI2String);
            Console.WriteLine(configurationFile.UI2Int);
            Console.WriteLine(configurationFile.UI2Uri);

            var widget = Injector.Instance.Resolve<IWidget>();
            widget.DoSomething();

            Console.ReadLine();
        }
    }
}

Since the Widget class contains a property called Configuration of type ICoreConfiguration, the dependency injection engine can simply load the correct configuration file object in at run-time, and everything “just works”. Running the application gives exactly the same outputs as the previous non-injected example which means the spirit of the original code has been kept the same:

ConfigurationDependencyInjection04

ConfigurationDependencyInjection05

So, as always, adding dependency injection involves a bit more work, but the benefits (non-brittle, highly testable code to name just two) far outweigh the extra time required.

Encapsulated and strongly-typed access to .NET configuration files

.NET is a strongly-typed object-oriented language, and it is my firm belief that everything we do within it should be strongly-typed and object-oriented in design. A trivial example would be to use the Uri class when dealing with URLs rather than simply passing strings around.

A central theme in object-orientation is encapsulation, which, in layman’s terms, I take to mean “when modelling a particular type of granular thing, the code for that thing should be contained in a single place”. There are a whole host of reasons why encapsulation is good practice, not least that it makes it really easy to change the way an aspect of the code works if changes only have to be made in one place. Better still, if things are encapsulated and strongly-typed, code changes that break other functionality can be found simply by recompiling, rather than having to execute the entire codebase looking for runtime errors.

In my opinion, interactions with standard .NET configuration files should be encapsulated and strongly-typed. Sure, it is possible to access configuration settings by littering code with calls to AppSettingsReader or ConfigurationManager, but this is neither encapsulated nor strongly-typed. If the return type for a setting appearing in many places in the code needed to change and AppSettingsReader/ConfigurationManager had been used throughout the code, this change would break existing code, and worst still, broken code would only be found at runtime. “Find and replace” could be used to find every instance of the setting in the code, but this is hardly ideal and may not find all instances if another coder had been clever and used constants for the key names. Similarly, if the way that configuration values were served needed to be changed, for instance by switching from the standard .NET configuration model to some other type of configuration provider such as a database you’d have to make changes throughout the code-base.

Keeping code to access the configuration file in one place feels like a much more elegant solution. So, how do I normally do this? Well, let’s assume an application with two UIs (User Interfaces) and an API layer (Application Programming Interface) as follows:

Encapsulated Configuration 01

Note that the example solution in this post has been included on GitHub here:

https://github.com/stevebarker333/Encapsulated-Strongly-Typed-Configuration-Files

In this example, both UIs are console applications, although in reailty these could be anything, including Windows Services, websites or Windows Applications. (Beware the .NET Framework 4 Client Profile issue if you are going to follow this example yourself!)

As configuration is owned at the level of the executing assembly, classes to encapsulate configuration settings should exist in the UI projects. However, these classes will share underlying functionality to interact with the configuration files, and so we create a base class in the API project from which all executable-level configuration file classes inherit:

using System;
using System.Configuration;

namespace API.Configuration
{
    public abstract class ConfigurationFile
    {
        private readonly AppSettingsReader appSettingsReader = new AppSettingsReader();

        protected string ReadString(string key)
        {
            try
            {
                return (string)appSettingsReader.GetValue(key, typeof(string));
            }
            catch (Exception exception)
            {
                var message = string.Format("Could not read key {0} from configuration file", key);
                throw new ConfigurationErrorsException(message, exception);
            }
        }

        protected int ReadInt(string key)
        {
            try
            {
                var value = this.ReadString(key);
                return int.Parse(value);
            }
            catch (Exception exception)
            {
                var message = string.Format("Could not read an int from key {0} in the configuration file", key);
                throw new ConfigurationErrorsException(message, exception);
            }
        }

        protected Uri ReadUri(string key)
        {
            try
            {
                var value = this.ReadString(key);
                return new Uri(value);
            }
            catch (Exception exception)
            {
                var message = string.Format("Could not read a Uri from key {0} in the configuration file", key);
                throw new ConfigurationErrorsException(message, exception);
            }
        }
    }
}

Note that all settings are first read as strings using the ReadString method. Other strongly-typed values are then derived from this method. I’ve included ReadInt and ReadUri functionality. More methods can be added to the base class as they are needed, but by the laws of YAGNI, don’t try and second guess every type your application will need up-front!

This class should be placed in a folder called Configuration to get the namespace shown. Additionally, a reference to System.Configuration will need to be added to access the exception classes from within our new base class:

Encapsulated Configuration 02

We can now start adding configuration at UI level. Here is my configuration file in UI1:

<?xml version="1.0"?>
<configuration>	
	<appSettings>
		<!-- UI1 Specific Settings -->
		<add key="UI1String" value="UI1.String"/>
		<add key="UI1Int" value="1"/>
		<add key="UI1Uri" value="http://www.ui1.com"/>
		<!-- Settings Required By API -->
		<add key="CoreString" value="Core.String"/>
		<add key="CoreInt" value="0"/>
		<add key="CoreUri" value="http://www.core.com"/>
	</appSettings>	
</configuration>

…and for UI2:

<?xml version="1.0"?>
<configuration>
	<appSettings>
		<!-- UI2 Specific Settings -->
		<add key="UI2String" value="UI2.String"/>
		<add key="UI2Int" value="2"/>
		<add key="UI2Uri" value="http://www.ui2.com"/>
		<!-- Settings Required By API -->
		<add key="CoreString" value="Core.String"/>
		<add key="CoreInt" value="0"/>
		<add key="CoreUri" value="http://www.core.com"/>
	</appSettings>
</configuration>

In order to simulate the kinds of scenarios that occur in enterprise solutions, I’ve included settings specific to the executing assembly and shared settings that are required by the API in each configuration file. An example of this would be a web-UI that needs a configuration setting as to how many search results to show on each page of paged search results, with an API responsible for performing the search which needs a configuration setting containing the database connection string to search against.

We can now write our strongly-typed encapsulated configuration file classes in each of the UIs. They have been added to each UI project as follows, along with references to the API project:

Encapsulated Configuration 03

Here is the class for UI1:

using System;

namespace UI1.Configuration
{
    public class ConfigurationFile : API.Configuration.ConfigurationFile
    {
        public string UI1String
        {
            get
            {
                return this.ReadString("UI1String");
            }
        }

        public int UI1Int
        {
            get
            {
                return this.ReadInt("UI1Int");
            }
        }

        public Uri UI1Uri
        {
            get
            {
                return this.ReadUri("UI1Uri");
            }
        }

        public string CoreString
        {
            get
            {
                return this.ReadString("CoreString");
            }
        }

        public int CoreInt
        {
            get
            {
                return this.ReadInt("CoreInt");
            }
        }

        public Uri CoreUri
        {
            get
            {
                return this.ReadUri("CoreUri");
            }
        }
    }
}

…and for UI2:

using System;

namespace UI2.Configuration
{
    public class ConfigurationFile : API.Configuration.ConfigurationFile
    {
        public string UI2String
        {
            get
            {
                return this.ReadString("UI2String");
            }
        }

        public int UI2Int
        {
            get
            {
                return this.ReadInt("UI2Int");
            }
        }

        public Uri UI2Uri
        {
            get
            {
                return this.ReadUri("UI2Uri");
            }
        }

        public string CoreString
        {
            get
            {
                return this.ReadString("CoreString");
            }
        }

        public int CoreInt
        {
            get
            {
                return this.ReadInt("CoreInt");
            }
        }

        public Uri CoreUri
        {
            get
            {
                return this.ReadUri("CoreUri");
            }
        }
    }
}

Note that each configuration file inherits from our shared base class so that it can access the ReadString (and similar) methods defined earlier. As the core/shared settings are shared with the API, then there is an argument for putting the property getters for these properties in the base configuration file class. However, I would argue against this approach as it means the API is dictating how the UI must organise its configuration file. If you’re comfortable with this approach then that’s fine; however I prefer to use an interface in the API to enforce the existence of shared settings in each UI. This allows the UI developer free choice to use any key names they deem fit for core settings. Let’s create this now in the following location:

Encapsulated Configuration 04

The interface is as follows:

using System;

namespace API.Configuration
{
    public interface ICoreConfiguration
    {
        string CoreString { get; }

        int CoreInt { get; }

        Uri CoreUri { get; }
    }
}

This interface is then applied to each of the configuration file classes in the UI:

using System;

namespace UI1.Configuration
{
    public class ConfigurationFile : API.Configuration.ConfigurationFile, API.Configuration.ICoreConfiguration
    {
	...        
    }
}
using System;

namespace UI2.Configuration
{
    public class ConfigurationFile : API.Configuration.ConfigurationFile, API.Configuration.ICoreConfiguration
    {
	...        
    }
}

The ground work has now all been done. All that remains is to put some example code into the UIs and API to demonstrate how our configuration classes and used, and that they do indeed pick up the correct settings.

I’ve added a class to the API to access the core settings:

Encapsulated Configuration 05

…which looks as follows:

using API.Configuration;

namespace API
{
    using System;

    public class Widget
    {
        private readonly ICoreConfiguration configuration;

        public Widget(ICoreConfiguration configuration)
        {
            this.configuration = configuration;    
        }

        public void DoSomething()
        {
            Console.WriteLine(configuration.CoreString);
            Console.WriteLine(configuration.CoreInt);
            Console.WriteLine(configuration.CoreUri);
        }
    }
}

Note that it is constructed with the type ICoreConfiguration so that it can use the core configuration settings without caring how they’ve be implemented. (Note also that this approach is still valid when using Dependency Injection, which I’ll cover in a future post.)

I’ve also included the following code in the Program class of UI1:

using System;
using UI1.Configuration;
using API;

namespace UI1
{
    public static class Program
    {
        public static void Main()
        {
            var configurationFile = new ConfigurationFile();

            Console.WriteLine(configurationFile.UI1String);
            Console.WriteLine(configurationFile.UI1Int);
            Console.WriteLine(configurationFile.UI1Uri);

            var widget = new Widget(configurationFile);
            widget.DoSomething();

            Console.ReadLine();
        }
    }
}

…and the following code in the Program class of UI2:

using System;
using UI2.Configuration;
using API;

namespace UI2
{
    public static class Program
    {
        public static void Main()
        {
            var configurationFile = new ConfigurationFile();

            Console.WriteLine(configurationFile.UI2String);
            Console.WriteLine(configurationFile.UI2Int);
            Console.WriteLine(configurationFile.UI2Uri);

            var widget = new Widget(configurationFile);
            widget.DoSomething();

            Console.ReadLine();
        }
    }
}

Running UI1 yields:

Encapsulated Configuration 06

…and running UI2 yields:

Encapsulated Configuration 07

…both as expected.

So, that concludes my exploration of strongly-typed encapsulated configuration files. Although it’s a bit more effort to implement than directly accessing the .NET configuration classes throughout code, it is more logical and far less brittle.

A final note: the .NET Framework automatically caches configuration file values in memory so there is no performance gain from storing configuration values in local class variables within each of our strongly-typed configuration file classes.

Update: As mentioned above, I’ve now extended the above by incorporating dependency injection. See my new post entitled Encapsulated and strongly-typed access to .NET configuration files with dependency injection.