Config Builders
Photo by Scott Blake on Unsplash
I recently came across an addition to ASP.NET 4.7.1: configuration builders.
With the .NET work I do, I generally have to maintain configuration across
multiple environments, each with multiple sites. I was first introduced to
config builders when investigating Azure KeyVault; however, even as an
interim, using a JSON config file allows me to extract the
environment-specific values and sensitive values from the web.config
file
for each site. (In the past it’s been a case of storing these directly on
the server and/or using the config transforms – web.debug.config
etc. –
to translate values between environments. Neither is perfect, since if you
miss a value or build for the wrong environment, all hell can break loose.)
Config builders solve this problem rather nicely: I have a config.json
file (or several) in the app root in each environment, and use the config
transforms for build differences (e.g. actual debug vs release values) and
just read the requisite config file for each environment.
Lots of these values are either constant within an environment or for a particular site across environments, so breaking the config files down in this way means that separate environment-specific files can be symlinked, and site-specific is maintained for each site. (Sadly, but also wisely, you can’t go up from the app root to read config files…)
The issue I hit was with the numerous custom configuration sections in the
web.config
file; while the default config builders will allow for
basically anything of the format
<add key="myKey" value="myValue"/>
(with the notable addition of connection strings), most of these custom configuration sections use attributes to define values. So how do we handle these? Turns out, they already thought of it .
So I created an abstract attribute-based handler like so:
public abstract class AttributeSectionHandlerBase<T> : SectionHandler<T> where T : ConfigurationSection
{
public override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
// appropriated from the appSettings handler:
// https://github.com/aspnet/MicrosoftConfigurationBuilders/blob/main/src/Base/SectionHandler.cs#L97-103
var properties = ConfigSection.GetType().GetProperties();
foreach (var property in properties)
yield return new KeyValuePair<string, object>(property.Name, property.GetValue(ConfigSection));
}
public override void InsertOrUpdate(string newKey, string newValue, string oldKey = null, object oldItem = null)
{
if (newValue == null) return;
var properties = ConfigSection.GetType().GetProperties();
// the @ prefix is just a convention to distinguish the values in the config.json
var propertyName = newKey.Substring(1);
foreach (var property in properties)
{
if (property.Name == propertyName)
{
property.SetValue(ConfigSection, newValue);
}
}
}
}
Then we just need a concrete implementation for each custom section.
public MyCustomSectionHandler : AttributeSectionHandler<MyCustomSection>
{
// literally nothing else to do here...
}
The only thing worth pointing out here is that I deliberately chose to
prefix configuration which relied on attributes with @
:
// config.json
{
"MyCustomSection": {
"@MyAttribute": "Some value"
}
}
This is purely aesthetic so that I could easily distinguish between attribute- and element-based configuration. YMMV.