File Base Configuration

Overview

Although it is not the best to store configuration in the file system, however, you might get to the point that you have no choice. 

ASP.NET Core has a perfect way to store configuration on files (JSON, XML, INI, ...). It has the cascading (overrides) mechanism too so that the latest in the order of AddProvider<>() will always win. 

This solution stays perfect until you need to change a piece of configuration at runtime.

In this case you will need to have a mechanism to get the file updated by the app itself.

Accessing the filesystem from the app has its own challenges and it requires a good security layer. In this document, we just look into a quick and easy way of implementing configuration overrides and the ability of changing configuration on the fly.

Implementation

Simple!

Overrides

Just add a json (or any other file that your app needs) as a new source of configuration. The following is the entire .NET 6 program.cs for a WebApi application:

var builder = WebApplication.CreateBuilder(args);
 
builder.Configuration.AddJsonFile("overrides.json", 
    optional: true, reloadOnChange: true);
 
builder.Services.AddControllers();
 
var app = builder.Build();
 
app.UseAuthorization();
app.MapControllers();
 
await app.RunAsync();        

"overrides.json" is the file that will be added to the end of ConfigurationProvider list. After appSettings.json, appSettings.{env}.json, environment variables and others. That means if you add any item to overrides.json, it will replace the item if it already in one of the previous sources.

Controller

From a controller, you can update the file by accessing to the file system. The following will do the read (GET) and write (POST) a single key in the configuration dictionary.


using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

[Route("api/[controller]")
[ApiController]
public class ConfigurationController : ControllerBase
{
    private readonly IWebHostEnvironment _environment;
    private readonly IConfiguration _configuration;
 
    const string FileName = "overrides.json";
 
    public ConfigurationController(IConfiguration configuration, IWebHostEnvironment environment)
    {
        _configuration = configuration;
        _environment = environment;
    }
 
    /// <summary>
    /// Gets the full configuration of the application instance
    /// </summary>
    [HttpGet]
    public Dictionary<string, string> Get()
    {
        return _configuration
            .AsEnumerable()
            .Where(pair => pair.Value != null)
            .OrderBy(_ => _.Key)
            .ToDictionary(_ => _.Key, _ => _.Value);
    }
 
    /// <summary>
    /// Gets a specific piece of configuration of the application instance.
    /// Note that the full key (segment:segment:key) should be provided.
    /// </summary>
    [HttpGet]
    [Route("{key}")]
    public ActionResult<string> GetByKey(string key)
    {
        var value = _configuration[key];
        if (value == null)
        {
            return NotFound();
        }
 
        var pair = new KeyValuePair<string, string>(key, value);
        return Ok(pair);
    }
 
    /// <summary>
    /// Sets a specific piece of configuration in the overrides file.
    /// Note that the full key (segment:segment:key) should be provided.
    /// </summary>
    [HttpPost]
    [Route("{key}")]
    public ActionResult SetValue(string key, [FromBody] string value)
    {
        try
        {
            var path = _environment.ContentRootPath;
 
            var filePath = Path.Combine(path, FileName);
 
            if (!System.IO.File.Exists(filePath))
            {
                return NotFound();
            }
 
            var content = System.IO.File.ReadAllText(filePath);
 
            var dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(content);
 
            if (dictionary is null)
            {
                return StatusCode(500, $"{FileName} cannot be deserialized to dictionary");
            }
 
            dictionary[key] = value;
 
            content = JsonConvert.SerializeObject(dictionary, Formatting.Indented);
 
            System.IO.File.WriteAllText(filePath, content);
 
            return Ok();
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"An error occurred: {ex.Message}");
        }
    }
}]        

We use JSON Serializer to make the operation easier.

Show Time

What you need to do is to get the configuration for a key:

GET http://localhost/api/configuration/my-key        

And to post a new value for a key or a completely new key-value pair:

POST http://localhost/api/configuration/my-key
Content-Type: application/json

"my-value"        


To view or add a comment, sign in

More articles by Dan B. Daneshgar

  • Copilot — Use Wildly / Use Wisely

    Purpose Encourage broad, fearless daily use of GitHub Copilot while creating an intentional feedback loop so value and…

    1 Comment
  • Controlled Background Services

    Overview BackgroundService is an awesome version of IHostedService in .NET.

  • Normalize and Denormalize .NET Core Configuration JSON

    .NET Core ConfigurationBuilder, simply combines all configuration sources introduced to it and creates a flattened…

  • Code Review - Architectural and Patterns

    Overview When it is the time to approve a change request (Pull Request / Merge Request) the following questions come to…

    1 Comment
  • Configure Better

    Overview Application configuration service is responsible to bootstrap all sort of configuration from different sources…

  • Log Better

    Overview Logging is one of the main pillars of an application development and application observatory. Although…

Others also viewed

Explore content categories