Using Key-per-file configuration provider

Introduction Learn how to use a directory with files that represent a key per file and the file contents as the value. These files are used no differently than reading key values from appsettings.json. The benefit is that, when done correctly, protecting the directory from prying users provides a higher level of security. All applications in a team's portfolio need not include this information in their application appsettings.json file. Key-per-file configuration provider documentation The documentation instructions are for Docker hosting scenarios, while here, deviating from Docker, it shows how to use the Key-per-file configuration provider for Windows servers and Windows desktops. NET 9 Source code Step 1 Determine a secure location that will contain files for the key values, followed by placing the location in an environment variable or, as done here, in appsettings.json. To access key values, a model is needed as shown below. Note DirectoryOptions.Key is used later in Progam.cs to tell our ASP.NET Core project to know about this model. Step 2 Create a validator for use with ValidateOnStart that checks for the existence of the key value directory. If desired, this can be expanded to ensure key files exist. public class DirectoryValidator : IValidateOptions { public ValidateOptionsResult Validate(string? name, DirectoryOptions options) { if (string.IsNullOrEmpty(options.DirectoryPath)) { return ValidateOptionsResult.Fail("Directory path cannot be empty."); } return !Directory.Exists(options.DirectoryPath) ? ValidateOptionsResult.Fail($"Directory does not exist: {options.DirectoryPath}") : ValidateOptionsResult.Success; } } Step 3 In Program.cs add the following. builder.Services.Configure(builder.Configuration.GetSection(DirectoryOptions.Key)); builder.Services.AddSingleton(); // Enable validation on application startup builder.Services.AddOptions().ValidateOnStart(); var directoryOptions = builder.Configuration.GetSection(DirectoryOptions.Key) .Get(); var secretsPath = directoryOptions!.DirectoryPath; // Add Key-per-file configuration provider builder.Configuration.AddKeyPerFile(directoryPath: secretsPath, optional: true); builder.Services.Configure(builder.Configuration); builder.Services.Configure(builder.Configuration); The code above does the following. Register service for DirectoryOptions. Register the validator to validate the folder exists which is called n ValidateOnStart. Setup ValidateOnStart. Add the section DirectoryOptions so we have access to it for reading the key value directory from appsettings.json. Add Key-per-file configuration provider Add models which will store information from files in the key value folder. Step 4 This is to create files where the file name without an extension represents a property and the contents of the value. Microsoft documentation differs in the file name used. Double underscore (__) is used as a configuration key delimiter in file names, which does not work outside of docker. There are code snippets on the web that contradict this but do not work. Models The following models are used. public class HelpDesk { public string Phone { get; set; } public string Email { get; set; } } public class Connections { public string ConnectionString { get; set; } } Files for models Email for HelpDesk with the contents of ServiceDesk@SomeCompany.net Phone for HelpDesk with the contents of 555-555-1234 ConnectionString for Connections with a connection string In the provided source code in the project folder, copy the above files to the directory for the key value files. For a real application, these files would not be pushed to a GitHub repository and would not be part of the project. Index page code behind Makes use of primary constructor. OnGet sets required property values. public class IndexModel(IOptions helpDeskOptions, IOptions connections) : PageModel { private readonly HelpDesk _helpDesk = helpDeskOptions.Value; private readonly Connections _connections = connections.Value; public string Phone { get; private set; } public string Email { get; private set; } public string ConnectionString { get; private set; } public void OnGet() { Phone = _helpDesk.Phone; Email = _helpDesk.Email; ConnectionString = _connections.ConnectionString; } } Index page frontend Key-per-file configuration provider Phone: @Model.Phone Email: @Model.Email Connection string: @Model.ConnectionString Summary Information has been provided for using the Key-per-file configuration provider, which allows you to keep information nee

Mar 22, 2025 - 17:25
 0
Using Key-per-file configuration provider

Introduction

Learn how to use a directory with files that represent a key per file and the file contents as the value. These files are used no differently than reading key values from appsettings.json.

The benefit is that, when done correctly, protecting the directory from prying users provides a higher level of security. All applications in a team's portfolio need not include this information in their application appsettings.json file.

Key-per-file configuration provider documentation

The documentation instructions are for Docker hosting scenarios, while here, deviating from Docker, it shows how to use the Key-per-file configuration provider for Windows servers and Windows desktops.

NET 9 Source code

Step 1

Determine a secure location that will contain files for the key values, followed by placing the location in an environment variable or, as done here, in appsettings.json. To access key values, a model is needed as shown below. Note DirectoryOptions.Key is used later in Progam.cs to tell our ASP.NET Core project to know about this model.

shows appsettings.json with path to a directory for keys

Step 2

Create a validator for use with ValidateOnStart that checks for the existence of the key value directory. If desired, this can be expanded to ensure key files exist.

public class DirectoryValidator : IValidateOptions<DirectoryOptions>
{

    public ValidateOptionsResult Validate(string? name, DirectoryOptions options)
    {
        if (string.IsNullOrEmpty(options.DirectoryPath))
        {
            return ValidateOptionsResult.Fail("Directory path cannot be empty.");
        }

        return !Directory.Exists(options.DirectoryPath) ? 
            ValidateOptionsResult.Fail($"Directory does not exist: {options.DirectoryPath}") : 
            ValidateOptionsResult.Success;
    }
}

Step 3

In Program.cs add the following.

builder.Services.Configure<DirectoryOptions>(builder.Configuration.GetSection(DirectoryOptions.Key));
builder.Services.AddSingleton<IValidateOptions<DirectoryOptions>, DirectoryValidator>();

// Enable validation on application startup
builder.Services.AddOptions<DirectoryOptions>().ValidateOnStart();

var directoryOptions = builder.Configuration.GetSection(DirectoryOptions.Key)
    .Get<DirectoryOptions>();

var secretsPath = directoryOptions!.DirectoryPath;

// Add Key-per-file configuration provider
builder.Configuration.AddKeyPerFile(directoryPath: secretsPath, optional: true);

builder.Services.Configure<HelpDesk>(builder.Configuration);
builder.Services.Configure<Connections>(builder.Configuration);

The code above does the following.

  1. Register service for DirectoryOptions.
  2. Register the validator to validate the folder exists which is called n ValidateOnStart.
  3. Setup ValidateOnStart.
  4. Add the section DirectoryOptions so we have access to it for reading the key value directory from appsettings.json.
  5. Add Key-per-file configuration provider
  6. Add models which will store information from files in the key value folder.

Step 4

This is to create files where the file name without an extension represents a property and the contents of the value. Microsoft documentation differs in the file name used. Double underscore (__) is used as a configuration key delimiter in file names, which does not work outside of docker. There are code snippets on the web that contradict this but do not work.

Models

The following models are used.

public class HelpDesk
{
    public string Phone { get; set; }
    public string Email { get; set; }
}

public class Connections
{
    public string ConnectionString { get; set; }
}

Files for models

  • Email for HelpDesk with the contents of ServiceDesk@SomeCompany.net
  • Phone for HelpDesk with the contents of 555-555-1234
  • ConnectionString for Connections with a connection string

In the provided source code in the project folder, copy the above files to the directory for the key value files. For a real application, these files would not be pushed to a GitHub repository and would not be part of the project.

Index page code behind

Makes use of primary constructor. OnGet sets required property values.

public class IndexModel(IOptions<HelpDesk> helpDeskOptions, IOptions<Connections> connections) : PageModel
{
    private readonly HelpDesk _helpDesk = helpDeskOptions.Value;
    private readonly Connections _connections = connections.Value;
    public string Phone { get; private set; }
    public string Email { get; private set; }
    public string ConnectionString { get; private set; }

    public void OnGet()
    {

        Phone = _helpDesk.Phone;
        Email = _helpDesk.Email;

        ConnectionString = _connections.ConnectionString;

    }
}

Index page frontend

 class="container">

     class="container d-flex justify-content-center">
         class="card shadow mt-5" style="width: 48rem;">
             class="card-body">
                 class="card-title mb-2">Key-per-file configuration provider
                

class="fw-bold">Phone: @Model.Phone

class="fw-bold">Email: @Model.Email

class="fw-bold">Connection string: @Model.ConnectionString