Wiki

Clone wiki

.NET Settings Framework / Home

Overview

The .NET Settings Framework is a small adapter for application settings. It provides an extremely simple API for working with different types of application settings (e.g. App.config/Web.config, environment variables, and .NET Core configuration). It takes care of all the usual boilerplate for reading, converting, validating, and providing defaults for your application settings, while keeping your code testable.

It is also very easy to extend if you want to obtain settings from arbitrary sources (e.g. a database).

This page describes version 2.0+ of the .NET Settings Framework. If you're looking for version 1.x, take a look at the old documentation.

Motivation

I’ve seen a lot of production code that reads values from config keys in App.config that looks something like this:

#!c#
// set a default, just in case the key is not found or the conversion fails

int timeout = 3000;

// retrieve the value for the desired key

string timeoutStr = ConfigurationManager.AppSettings["timeoutInMilliseconds"];

// check whether the key was actually found; if not, the default value is retained

if (timeoutStr != null)
{
    // attempt to convert to the desired type
    //   -> if it succeeds, the default value is replaced with the retrieved value
    //   -> if it fails, the default value is retained

    bool converted = int.TryParse(timeoutStr, out timeout);
}

Aside from the bloat due to comments and braces (which were both necessary to make this example clear), you can see that we essentially have four lines of code just to read an integer setting from App.config.

What’s really bad is that there will essentially be four lines of code for every setting, all doing essentially the same thing for different settings. That isn’t very DRY.

On top of that, this kind of code does not lend itself well to unit testing. Having to explicitly use Configuration.AppSettings to look for settings in App.config is just as good as talking to a database or a REST API. That dependency needs to be abstracted so that it can be mocked out, and any logic can be tested in isolation.

My article, "A Framework for Application Settings" (15th February 2015), covers the original motivation and design for this library.

Prerequisites

The .NET Settings Framework supports any platform compatible with .NET Standard 1.2, so basically .NET Framework 4.5.1+ and .NET Core 1.0+. The AppSettingReader and EnvironmentVariableReader, however, are specific for the .NET Framework and will not work with .NET Core. For proper cross-platform support, use the .NET Core configuration model in combination with the ConfigurationReader.

Getting Started

First, install the NuGet package:

Install-Package Dandago.Settings -pre

Choose a reader depending on the source from which you'll get your settings, e.g. for classic App.config AppSettings:

#!c#
var settingReader = new AppSettingReader();

Pass that reader to the SettingProvider:

#!c#
var settingProvider = new SettingProvider(settingReader);

Use the provider to get your settings:

#!c#
int timeout = settingProvider.Get<int>("timeout");

If you want, you can set a default in case the setting does not exist or the value is invalid:

#!c#
int timeout = settingProvider.Get<int>("timeout", 5000);

Structure

settings-framework-2.0.png

Building Blocks

The .NET Settings Framework is made up of two simple building blocks:

  • Readers literally read settings from the source (e.g. App.config).
  • The SettingProvider uses a reader to obtain the setting, and then converts it to the desired type. If the key is not found, or if the value is not valid for the type, then a default value is returned. The default value may be specified as an optional parameter; otherwise the default for the type is returned.

Readers

  • The ConfigurationReader is used to read configuration built using the .NET Core configuration model. This may be built out of JSON files, XML files, environment variables, and just about any other arbitrary source.
  • The AppSettingReader reads application settings from a classic App.config or Web.config file.
  • The EnvironmentVariableReader reads environment variables.
  • The CompositeReader allows you to combine different sources into one. The last source takes precedence.

The AppSettingReader and EnvironmentVariableReader target the .NET Framework and will not work with .NET Core.

Features

  • Concise retrieval of settings
  • Strongly typed
  • Ability to specify default values, or use the default value of the type
  • Works nicely with Dependency Injection
  • Supports AppSettings, Environment Variables, and arbitrary sources
  • Supports basic types, enums, and nullables
  • Asynchronous retrieval
  • ConfigSection support

What's New in Version 2.0

  • Support for .NET Core
  • There is now only one Dandago.Settings package, and interfaces contain both synchronous and asynchronous methods
  • ConfigKeyProviders have been renamed to SettingProviders
  • ConfigKeyReaders have been renamed to SettingReaders
  • New Readers: ConfigurationReader and CompositeReader
  • ConfigSection support has been streamlined into the same building blocks as normal application settings

Usage

Make sure you have the NuGet package installed.

Install-Package Dandago.Settings -pre

Classic AppSettings (.NET Framework)

If you just want to work with App.config/Web.config AppSettings, all you need is:

#!c#
var reader = new AppSettingReader();
var provider = new SettingProvider(reader);

int timeout = provider.Get<int>("timeout", 5000);

The second parameter to Get<T>() is the default value you want to set if the setting is either missing or invalid. It is optional. If you leave it out, the default of the type is used (e.g. 0 for int, null for objects, etc).

Environment Variables (.NET Framework)

There's a reader for that.

#!c#
var reader = new EnvironmentVariableReader();
var provider = new SettingProvider(reader);

string tempDir = provider.Get<string>("TMP", @"C:\TEMP");

Note that if you're using .NET Core configuration (see below), you don't need this. .NET Core Configuration allows compositing of various setting sources, including environment variables.

.NET Core Configuration (any framework)

If you're working with the .NET Core configuration model, you will need to have set that up first, e.g.:

#!c#
var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");

var config = builder.Build();

Once you have that in place, you wrap it in a reader, and proceed as usual.

#!c#
var reader = new ConfigurationReader(config);
var provider = new SettingProvider(reader);

var gandalf =  provider.Get<string>("wizards:0:name");

Composite Configuration (any framework)

You can use the CompositeReader to use multiple readers:

#!c#
var reader1 = new AppSettingReader();
var reader2 = new EnvironmentVariableReader();
var reader = new CompositeReader(reader1, reader2);
var provider = new SettingProvider(reader);

string tempDir = provider.Get<string>("TMP", @"C:\TEMP");

Note that .NET Core configuration provides compositing of settings automatically. So if you're using that, you don't need the CompositeReader.

Configuration Sections (App/Web.config and .NET Core ConfigSections)

The SettingProvider provides a GetSection<T>() call that you can use to retrieve a strongly-typed configuration section. The call is the same for .NET Framework and .NET Core, although the underlying implementation is completely different.

For App.config/Web.config you use an AppSettingReader like before:

#!c#
var reader = new AppSettingReader();
var provider = new SettingProvider(reader);
var person = provider.GetSection<PersonConfig>("personConfig");

For .NET Core, you use the ConfigurationReader like before:

#!c#
var reader = new ConfigurationReader(config);
var provider = new SettingProvider(reader);
var section = provider.GetSection<Subsection>("subsection");

So the only thing that changes is the reader; you still call GetSection<T>() either way. Note that this method does not take a default value.

Async

Get<T>() and GetSection<T>() have corresponding asynchronous GetAsync<T>() and GetSectionAsync<T>() counterparts that you can use in situations where it makes sense (e.g. retrieving settings from a REST API or a database).

The simple readers provided in this library do not need to be async, so their async methods are merely wrappers for the synchronous version, and you will get no benefit from using them.

Custom Readers

If you want to get settings from an arbitrary source (e.g. a database) that is not covered by this library, you can write a custom reader. All you need to do is implement ISettingReader:

#!c#
    public interface ISettingReader
    {
        string Read(string key);
        Task<string> ReadAsync(string key);
        T ReadSection<T>(string configSectionName) where T : class, new();
        Task<T> ReadSectionAsync<T>(string configSectionName) where T : class, new();
    }

If the setting source does not support config sections (e.g. environment variables), then just return null. Don't throw an exception, as this would break the CompositeReader.

Dependency Injection

If you're using Depedency Injection, then just set up your IoC container such that:

  • Requests for ISettingReader return your reader;
  • Requests for ISettingProvider return your SettingProvider.

For example, using Ninject:

#!c#
kernel.Bind<ISettingReader>().To<AppSettingReader>();
kernel.Bind<ISettingProvider>().To<SettingProvider>();

Updated