Software Development

Application configuration in .NET Core – Part 1

.NET Core has a new way of working with configuration that is much more flexible than the way that previous versions of .NET have.

It allows you to:

  1. Pull configuration from multiple sources and bring it in to one place.
  2. Easily map that configuration information into classes to make access easier.
  3. Override configuration from previous sources so that you can import a base configuration then override settings on per-environment basis.

This post will be concerned with the first of these: Pulling configuration from multiple sources and bringing it together in to one place. We’ll discuss the second and third aspect in future posts.

Getting Started

To use it you need to add the Microsoft.Extensions.Configuration NuGet package to your application.

Microsoft.Extensions.Configuration 1.0.0 NuGet package
Microsoft.Extensions.Configuration NuGet package

Once you’ve imported the package your project.json will contain:

  "dependencies": {
    "Microsoft.Extensions.Configuration": "1.0.0",
    .... Other dependencies here ....
  }

From the basic configuration package you don’t really get much in the way of configuration sources, only the in-memory one is available. However, that’s just enough to show you the basic set up of the configuration in an application.

public class Program
{
    public static void Main(string[] args)
    {
        // Defines the sources of configuration information for the 
        // application.
        var builder = new ConfigurationBuilder()
            .AddInMemoryCollection(new []
            {
                new KeyValuePair<string, string>("the-key", "the-value"),
            });

        // Create the configuration object that the application will
        // use to retrieve configuration information.
        var configuration = builder.Build();

        // Retrieve the configuration information.
        var configValue = configuration["the-key"];
        Console.WriteLine($"The value for 'the-key' is '{configValue}'");

        Console.ReadLine();
    }
}

The builder is the thing that allows you to set up the sources of configuration information. Each provider adds extension methods so you can add them easily to the builder. The InMemoryCollection simply takes an IEnumerable of KeyValuePairs to initialise its values.

Once you have set up your configuration sources you can build all that into an actual object you can use in your application, by calling Build() on the builder object. From here on you can access configuration values with indexer notation.

Adding a JSON File Source

So far, what we have isn’t very useful. We need to pull configuration information from outside the application such as a JSON file. To do that, we need to add another NuGet package. This one provides a JSON provider and is called Microsoft.Extensions.Configuration.Json.

Microsoft.Extensions.Configuration.Json NuGet package
Microsoft.Extensions.Configuration.Json NuGet package

We can now extend the simple application above by adding an appsettings.json file and adding in the code to build it.

var builder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddInMemoryCollection(new []
    {
        new KeyValuePair("the-key", "the-value"),
    });

And the appsettings.json looks like this:

{
  "my-other-key": "my-other-value" 
}

And the value is retrieved like any other:

configValue = configuration["my-other-key"];
Console.WriteLine($"The value for 'my-other-key' is '{configValue}'");

However, while this looks like it should work, it won’t. When you added a settings file previously, Visual Studio would mark it for copying to the output folder so that the running application could find it. However, it doesn’t do that with .NET Core (yet – I do hope they add it).

Instead you get a FileNotFoundException, like this:

Exception Assistant showing a File Not Found Exception
An unhandled exception of type ‘System.IO.FileNotFoundException’ occurred in Microsoft.Extensions.Configuration.FileExtensions.dll Additional information: The configuration file ‘appsettings.json’ was not found and is not optional.

To get the appsettings.json file added to the output folder you are going to have to modify the project.json file.

In the buildOptions section add copyToOutput with the name of the file. If there is more than one file you can put in an array of files rather than just the one. The top of the project.json file now looks like this:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true,
    "copyToOutput": "appsettings.json"
  },
  .... The rest of the file goes here ....

The next time the project is run it will copy the appsettings.json file and you won’t get an exception to say that the file was not found.

Software Development

Previewing Config Transforms

Curiously, I didn’t know about this until recently and it is such a useful thing too.

You can preview the results of your configuration transformation from within Visual Studio.

First of all, right click the transform file and select “Preview Transform”

Then it will show you the differences between the original web.config file and the transformed file.

Software Development

aspnet_regiis “Could not load file or assembly ‘SimpleAuthentication.Core’ or one of its dependencies.”

I was recently following Jouni Heiknieme’s blog post on Encrypting connection strings in Windows Azure web applications when I stumbled across a problem.

The issue was that I wasn’t encrypting the connectionStrings section, I was encrypting a custom section (one provided by SimpleAuthentication). And in order to encrypt that section, aspnet_regiis needs access to the DLL that defines the config section. If it cannot find the DLL it needs it will respond with an error message:

C:\dev\Xander.HorribleCards\src\Xander.HorribleCards.UI.Web>aspnet_regiis -pef "authenticationProviders" . -prov "Pkcs12Provider" 
Microsoft (R) ASP.NET RegIIS version 4.0.30319.18408 
Administration utility to install and uninstall ASP.NET on the local machine. 
Copyright (C) Microsoft Corporation.  All rights reserved. 
Encrypting configuration section... 
An error occurred creating the configuration section handler for authenticationProviders: Could not load file or assembly 'SimpleAuthentication.Core' or one of 
its dependencies. The system cannot find the file specified. (C:\dev\Xander.HorribleCards\src\Xander.HorribleCards.UI.Web\web.config line 7) 
Could not load file or assembly 'SimpleAuthentication.Core' or one of its dependencies. The system cannot find the file specified. 
Failed!

And here is the relevant part of the web.config file

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
  <configSections> 
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> 
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> 
    </sectionGroup> 
    <section name="authenticationProviders" type="SimpleAuthentication.Core.Config.ProviderConfiguration, SimpleAuthentication.Core" /> 
  </configSections>

It took searching through a few forum posts before I eventually found the answer. Most were pointing in the right general direction. You either have to load the assembly that defines the config section into the GAC (not possible for me as it was a third party assembly that was not strong named) or put it where aspnet_regiis was looking for it.

All the non-GAC solutions that I found were hacky horrible things that put the assembly somewhere in the .NET folder.

My problem was that where everyone was saying to put it wasn’t working for me. So I loaded up Process Monitor to look to see where exactly the aspnet_regiis was looking. It turns out that because I was using the 64bit version of the command prompt I should be looking in C:\Windows\Microsoft.NET\Framework64\v4.0.30319

I put the assembly in that directory and the aspnet_regiis worked and the relevant section was encrypted, it was runnable and I could store it to source control without other people knowing what my secret keys are.

Round tripping the encryption/decryption

I also had some issues round tripping the encrypted and decrypted config file while developing. I kept getting the error message:

Decrypting the relevant config settings
Microsoft (R) ASP.NET RegIIS version 4.0.30319.18408
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Decrypting configuration section...
Failed to decrypt using provider 'Pkcs12Provider'. Error message from the provider: Keyset does not exist
 (C:\dev\Xander.HorribleCards\src\Xander.HorribleCards.UI.Web\web.config line 65)

Keyset does not exist

Failed!

It turned out to be a permissions issue on the private key. This post “Keyset does not exist” on Stack Overflow helped on how to resolve that.

Open Source Software, Software Development, Xander.PasswordValidator

Xander.PasswordValidator – The config file

Earlier in this series I introduced the config file, but I didn’t say much about it other that show an example. In this post I’ll go in to more detail.

Defining the config section

To define the section:

<configSections>
<!-- Set up other config sections here—>
   <sectionGroup name="passwordValidation">
      <section name="rules" type="Xander.PasswordValidator.Config.PasswordValidationSection, Xander.PasswordValidator, Version=0.1.0.0, Culture=neutral, PublicKeyToken=fe72000dffcf195f" allowLocation="true" allowDefinition="Everywhere"/>
   </sectionGroup> </configSections>

This defines the configuration section will will appear later in the config file.

An example of the config section itself:

<!-- The configuration section that describes the configuration for the password validation -->
<passwordValidation>
   <rules minimumPasswordLength="6" needsNumber="false" needsLetter="false" needsSymbol="false">
     <wordListProcessOptions checkForNumberSuffix="true" checkForDoubledUpWord="true" checkForReversedWord="true" />
     <standardWordLists>
       <add value="FemaleNames"/>
       <add value="MaleNames"/>
       <add value="MostCommon500Passwords"/>
       <add value="Surnames"/>
     </standardWordLists>
     <customWordLists>
       <add file="WordLists/MyCustomWordList.txt" />
       <add file="WordLists/MyOtherCustomWordList.txt" />
     </customWordLists>
   </rules> </passwordValidation>

The rules

The rules section defines the actual rules by which the passwords will be validated.

<rules minimumPasswordLength="13" needsNumber="true" needsLetter="true" needsSymbol="true">
  • minimumPasswordLength: a positive integer that defines the minimum number of characters needed for a valid password. It is optional and if missing will default to 8.
  • needsNumber: Boolean that indicates whether the password needs a number in it. It is optional and if missing will default to true.
  • needsLetter: Boolean that indicates whether the password needs a letter in it. It is optional and if missing will default to true.
  • needsSymbol: Boolean that indicates whether the password needs a symbol in it. It is optional and if missing will default to false.

Rules can have a number of child elements also.

  • wordListProcessOptions: A set of options for how the word lists are processed
  • standardWordLists: A collection of built in word lists to use to check the password against.
  • customWordLists: A collection of custom word lists to use to check the password against.

The word list process options

By default, checking the password against the word lists only checks to see if the password is in a word list. These are additional options for checking against the word lists.

<wordListProcessOptions checkForNumberSuffix="true" checkForDoubledUpWord="true" checkForReversedWord="true" />
  • checkForNumberSuffix: Indicates whether the password should be checked to see if it is simply in the word list with an additional digit appended. This is optional, and by default is false.
  • checkForDoubledUpWord: Indicates whether the password should be checked to see if it is the same sequence repeated over again, and if it is to see if the first half is in the word list. This is optional and the default value is false.
  • checkForReversedWord: Indicates the a reversed form of the password should be checked to see if it in the word list. This is optional and the default value is false.

Standard word lists

This element is a container for a collection of standard word list items.

     <standardwordlists>
       <add value="FemaleNames" />
       <add value="MaleNames" />
       <add value="MostCommon500Passwords" />
       <add value="Surnames" />
     </standardwordlists>

The valid words list are:

Custom word lists

This element is a container for a collection of file paths to plain text files that contain custom word lists to check against. A word list file is simply a plain text file with one word per line.

     <customWordLists>
       <add file="WordLists/MyCustomWordList.txt" />
       <add file="WordLists/MyOtherCustomWordList.txt" />
     </customWordLists>

The paths are relative to the working directory of the application in which the password validator is operating. In an ASP.NET web application the paths should be prefixed with the ~ to ensure they are correctly mapped on the server relative to the root of the web application.