BasicAppSettings

Implements easy methods for reading sensitive and non-sensitive application settings from configuration files.


Keywords
.NET, configuration, encrypt, decrypt, settings
License
Apache-2.0
Install
Install-Package BasicAppSettings -Version 1.1.1

Documentation

BasicAppSettings

This .NET project illustrates how to keep your application configuration files nice and clean. It also suggests a better way to initialize sensitive and non-sensitive application settings.

Introduction

We often use configuration (app.config or web.config) files to store .NET application settings that are not likely to change. As a result, the configuration files get more bloated than they need to be. There must be a better way. And there is.

Overview

Here is the basic idea:

  • Keep the application settings that are not likely to change in the application source code, BUT
  • In the unlikely case that they do change before the application can be rebuilt, allow the application to read them from the configuration file.

This project illustrates how this can be done. It also shows how you can handle sensitive application settings, such as passwords, encryption keys, and so on.

IMPORTANT: NEVER KEEP SENSITIVE APPLICATION SETTINGS, SUCH AS PASSWORDS, ENCRIPTION KEYS, CONNECTION STRINGS, AND SO ON, HARD CODED IN THE APPLICATION SOURCE.

Code

This solution includes two projects:

  • BasicAppSettings is a .NET Standard class library that implements a couple of helper methods for initializing sensitive and non-sensitive application settings.
  • SampleConsoleApp illustrates how to use the BasicAppSettings class library.

BasicAppSettings project

This project implements the class library that exposes a single static class (Config) with just a couple of methods:

  • GetValue - returns either the configuration setting from the application configuration file or (if the setting is missing or unspecified) the default.
  • GetArray - returns either the configuration setting from the application configuration file or (if the setting is missing or unspecified) the default converted to a string array.
  • GetDictionary - returns either the configuration setting from the application configuration file or (if the setting is missing or unspecified) the default converted to a string dictionary.
  • GetSecret - can be used to get a value from the configuration file's encrypted section (it first checks if there is an unencrypted value in the appSettings section).

The library is built as a .NET Standard assembly, so you can use it from either .NET Core or traditional .NET apps.

SampleConsoleApp project

The sample project builds a console app that illustrates how to initialize sensitive and non-sensitive application settings.

Non-sensitive application settings

It makes sense to encapsulate initialization for the non-sensitive settings in a dedicated static class (AppConfig in this sample). As you see, the code calls the Config object's GetValue method passing the appropriate defaults. GetValue will check the specified key in the configuration file's appSettings section and if the key does not exist or holds an empty string, it will return the specified default. The method automatically converts the returned setting to the proper data type.

using System;
using BasicAppSettings;

namespace Sample
{
    /// <summary>
    /// Sets application settings using the defaults or the values specified in the configuration file.
    /// </summary>
    internal static class AppConfig
    {
        internal static char Code =
            Config.GetValue<char>("Code", 'C');

        internal static char Index =
            Config.GetValue<char>("Index", 'X');

        internal static bool Enabled =
            Config.GetValue<bool>("Enabled", true);

        internal static bool Enforce =
            Config.GetValue<bool>("Enforce", true);

        internal static int Max =
            Config.GetValue<int>("Max", 1000);

        internal static int Min =
            Config.GetValue<int>("Min", 5);

        internal static DateTime FirstDate =
            Config.GetValue<DateTime>("FirstDate", DateTime.UtcNow);

        internal static DateTime LastDate =
            Config.GetValue<DateTime>("LastDate", DateTime.UtcNow);

        internal static string[] Operations =
            Config.GetArray("Operations", "Create|Read|Update|Delete|Assign|Revoke|Enable|Disable", true, "|");

        internal static string[] Objects =
            Config.GetArray("Objects", "User|Group|Role", true, "|");

        internal static Dictionary<string, string> FirstPriority =
            Config.GetDictionary("FirstPriority", "User=1;Group=2;Role=3");

        internal static Dictionary<string, string> SecondPriority =
            Config.GetDictionary("SecondPriority", "User=1;Group=2;Role=3");
    }
}

To use a specific setting, the application can simply reference the corresponding static property of the AppConfig class:

Console.WriteLine(String.Format("{0}= {1}", "Code      ", AppConfig.Code));
Console.WriteLine(String.Format("{0}= {1}", "Index     ", AppConfig.Index));
Console.WriteLine(String.Format("{0}= {1}", "Max       ", AppConfig.Max));
Console.WriteLine(String.Format("{0}= {1}", "Min       ", AppConfig.Min));
Console.WriteLine(String.Format("{0}= {1}", "FirstDate ", AppConfig.FirstDate));
Console.WriteLine(String.Format("{0}= {1}", "LastDate  ", AppConfig.LastDate));
Console.WriteLine(String.Format("{0}= {1}", "Enabled   ", AppConfig.Enabled));
Console.WriteLine(String.Format("{0}= {1}", "Enforce   ", AppConfig.Enforce));

This is how you convert an app setting to a string array:

string[] operations = AppConfig.Operations;
Console.WriteLine(String.Format("\nOperations:"));
foreach (string operation in operations)
{
    Console.WriteLine(operation);
}

And this is how you convert an app setting to a string dictionary:

Dictionary<string, string> priority1 = AppConfig.FirstPriority;
Console.WriteLine(String.Format("\nFirst Priority:"));
foreach (string key in priority1.Keys)
{
    Console.WriteLine(String.Format("{0,-5} = {1}", key, priority1[key]));
}

Now, whenever you want to override the hard-coded defaults, simply update the app.config file's AppSettings section like:

<appSettings>
  <add key="Operations" value="Create|Read|Update|Delete|Assign|Revoke|Enable|Disable|Expire|nexpire"/>
  <add key="Code" value="A"/>
  <add key="Enabled" value="false"/>
  <add key="Max" value="999"/>
  <add key="FirstDate" value="1988-08-08T12:34:00.000"/>
  <add key="Secret1" value="From the 'appSettings' section."/>
  <add key="SecondPriority" value="User=3;Group=2;Role=1"/>
</appSettings>

Sensitive application settings

For managing sensitive application settings, the code relies on the standard aspnet_regiis.exe tool. The sample project comes with the modified version of the Crypt.config.bat script that takes care of the minutiae needed for the key creation and encryption. When you build the project, the post-build step will invoke the Crypt.config.bat file to create a key container and then encrypt a sensitive configuration section of the application configuration file in the output (Debug or Release) folder (notice that once the key container creation step successfully runs, it will keep failing because on repeated attempts it will try to create the container that already exists, so you can ignore this error).

In the sample, the sensitive settings are stored inside of the secureAppSettings section in the configuration file (one setting is stored in the appSettings section just to illustrate that you can do it for debugging purposes, but you should never keep sensitive settings unencrypted once the application is deployed). Here what they look like in plain text (once you build the project, the secureAppSettings section will be encrypted):

<configSections>
  <section name="secureAppSettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</configSections>
<configProtectedData defaultProvider="basicConfigurationSampleRsaProvider">
  <providers>
    <remove name="basicConfigurationSampleRsaProvider"/>
    <add name="basicConfigurationSampleRsaProvider" type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" keyContainerName="sampleRsaKey" useMachineContainer="true" />
  </providers>
</configProtectedData>
<appSettings>
  ...
  <add key="Secret1" value="From the 'appSettings' section."/>
</appSettings>
<secureAppSettings>
  <add key="Secret2" value="From the 'secureAppSettings' section."/>
  <add key="Secret3" value="From the 'secureAppSettings' section."/>
</secureAppSettings>

Notice that you can export the secure application setting section into an external file (this may make deployment easier) and it will still work.

Once you have the sensitive value defined (and encrypted), the application can retrieve it:

string[] names = new string[] { "Secret1", "Secret2", "Secret3" };

foreach (string name in names)
{
    string value = Config.GetSecret<string>(name);
    Console.WriteLine(String.Format("{0} = {1}", name, value));
}

Dependencies

For some bizarre reasons, when using the BasicAppSettings class library, you need to explicitly reference the System.Configuration.ConfigurationManager Nuget package from your project.

Usage

To include the BasicAppSettings class library in your project, add the following Nuget packages: