30
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?...
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”:
“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.
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.
30