DEV Community

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

Posted on • Edited on

1 1

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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️