21
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: 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.
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");
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"));
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