DEV Community

Cover image for Custom .NET XML .config sections with SparkyTools.XmlConfig
Brian Schroer
Brian Schroer

Posted on • Edited on

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; }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

(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>
Enter fullscreen mode Exit fullscreen mode

...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>
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode
<FooList type="FooNamespace.Bar, FooAssemblyName">
    <Foo bar="bar1" baz="111.11" />
    <Foo bar="bar2" baz="222.22" />
</FooList>
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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!
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

...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!
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)