Custom .NET XML .config sections with SparkyTools.XmlConfig

This article is about dying technology - .NET framework web.config and app.config XML files.

.NET Core overhauled application configuration, jettisoning .config XML files in favor of app settings JSON files, and .NET 5 brings those configuration changes back into "Framework" .NET.

For those of us who are still acting as caretakers for geriatric .NET framework apps using .config files though, here's a tool to make it a bit easier...

If you want to load configuration data for .NET application from a web.config or app.config section other than the built-in sections (e.g. appSettings, connectionStrings), you have to write a custom IConfigurationSectionHandler implementation to handle your custom .config section. It’s not hard to do, but it’s a fairly tedious coding exercise. What if you didn’t have to?...

SparkyTools.XmlConfig NuGet packages

The code in these packages was inspired by a blog post called "The Last Configuration Section Handler I'll Ever Need" by Craig Andera that I read way back in 2003 🤯. I've been using this technique since then, and finally "NuGet-ed" it a few years ago.

ConfigurationSectionDeserializer / ConfigurationSectionListDeserializer

These SparkyTools.XmlConfig classes makes it easy to load a strongly-typed object (or IList of objects) from a custom web.config or app.config file section without having to write a custom IConfigurationSectionHandler implementation.

For the code examples below, I'll be using this incredibly realistic C# class definition:

public class Foo
{
    public string Bar { get; set; }
    public decimal Baz { get; set; }
}

In the .config file, register each custom section with a type of “SparkyTools.XmlConfig.ConfigurationSectionDeserializer” or
“SparkyTools.XmlConfig.ConfigurationSectionListDeserializer”:

<configuration>
  <configSections>
    <section name="Foo" type="SparkyTools.XmlConfig.ConfigurationSectionDeserializer, SparkyTools.XmlConfig" />
    <section name="FooList" type="SparkyTools.XmlConfig.ConfigurationSectionListDeserializer, SparkyTools.XmlConfig" />
  </configSections>

(If you're using the SparkyTools.XmlConfig.Fx package, the types/namespaces will be "...XmlConfig.Fx.Config")

In each registered custom section, specify the object type via the type attribute. Here's an single instance section:

<Foo type="FooNamespace.Foo, FooAssemblyName">
    <Bar>bar</Bar>
    <Baz>123.45</Baz>
</Foo>

...and a “list” section: (Note that "type" is the "single" instance type, not "IList..."):

<FooList type="FooNamespace.Foo, FooAssemblyName">
    <Foo>
        <Bar>bar1</Bar>
        <Baz>111.11</Baz>
    </Foo>
    <Foo>
        <Bar>bar2</Bar>
        <Baz>222.22</Baz>
    </Foo>
</FooList>

You can use XmlAttribute attributes in your class definitions to tell the serializers to get properties from XML attributes rather than child XML elements:

public class Foo
{
    [XmlAttribute("bar")]
    public string Bar { get; set; }

    [XmlAttribute("baz")]
    public decimal Baz { get; set; }
}
<FooList type="FooNamespace.Bar, FooAssemblyName">
    <Foo bar="bar1" baz="111.11" />
    <Foo bar="bar2" baz="222.22" />
</FooList>

To read from your custom .config section, just call the ConfigurationSectionDeserializer or ConfigurationSectionListDeserializer Load method, specifying the object type and the .config section name:

Foo foo = 
    ConfigurationSectionDeserializer.Load<Foo>("Foo");

IList<Foo> fooList = 
    ConfigurationSectionListDeserializer.Load<Foo>("FooList");

Dependency Injection

My SparkyTools.DependencyProvider NuGet package can be used to create DependencyProvider<T> instances for constructing classes with dependencies that aren't easily mockable. DependencyProvider<T> just has one method, "GetValue()", which returns a T instance.

Here's a class with a dependency of type "Foo" injected via a DependencyProvider:

using SparkyTools.DependencyProvider;

public class Qux
{
    private readonly Foo _foo; 

    public Qux(IDependencyProvider<Foo> fooProvider)
    {
        _foo = fooProvider.GetValue();
    }
}

The static ConfigurationSectionDeserializer.DependencyProvider and ConfigurationSectionListDeserializer.DependencyProvider
methods create DependencyProviders for loading data from custom .config file sections.

Here's code for creating an instance of the Qux class shown above, injecting the Foo dependency from a custom .config file section:

using SparkyTools.XmlConfig;
. . .
var qux = new Qux(
  ConfigurationSectionDeserializer.DependencyProvider<Foo>("Foo"));

AppSettings.DependencyProvider

A bit off this post's topic of custom .config file sections, but still in the general XML configuration topic - The static AppSettings.DependencyProvider() method can be used to make classes that get values via ConfigurationManager.AppSettings more unit-testable...

Consider this class:

public class Quux
{
    public void DoThing()
    {
        if (ConfigurationManager.AppSettings["featureEnabled"] == "true")
        {
            // Do that thang!
        }
    }
}

If you want to unit test it, you'll have to either have an app.config file with an appSettings section in your test project or have your test code do a static method call to set the value, e.g.:

ConfigurationManager.AppSettings["serviceUrl"] = "http://fakeurl";

...and doing static things in unit tests is always a bad idea.

Instead of using ConfigurationManager.AppSettings directly you can abstract your class to say "I need a function that returns configuration values for given keys":

public class Quux
{
    private readonly Func<string, string> _getAppSetting;

    public Quux(IDependencyProvider<Func<string, string> appSettingsProvider)
    {
        _getAppSetting = appSettingsProvider.GetValue();
    }

    public void DoThing()
    {
        if (_getAppSetting("featureEnabled") == "true")
        {
            // Do that thang!
        }
    }
}

SparkyTools.XmlConfig.AppSettings.DependencyProvider() creates a DependencyProvider<Func<string, string>>) that "wraps" ConfigurationManager.AppSettings, so you can use it in your "real code":

using SparkyTools.XmlConfig;
...
    var qux = new Quux(AppSettings.DependencyProvider());

...and "mock" the appSettingProvider dependency in your unit tests.

21