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
  • SparkyTools.XmlConfig: for .NET framework v4.6 and above
  • SparkyTools.XmlConfig.Fx: for .NET framework v4-4.5.1
  • 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.

    30

    This website collects cookies to deliver better user experience

    Custom .NET XML .config sections with SparkyTools.XmlConfig