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.
Recommended by LinkedIn
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"