Guys i'm really embaraced to ask for this, but i got stuck and i can't think for solution:
I found an IniParser library that i use for parsing settings from file. However the default approach is this:
FileIniDataParser parser = new FileIniDataParser();
//Parse the ini file
IniData parsedData = parser.LoadFile("TestIniFile.ini");
parsetData['some']['settings'];
In my project though, i don't want to call everywhere the parser, so i made a class that load up the file at start-up and all i have to do is just access the instance. This is what i want:
namespace my_project
{
public class settings
{
public IniData ini()
{
string login = "Settings.ini";
FileIniDataParser parser = new FileIniDataParser();
// Parse the ini file with the settings
IniData settings = parser.LoadFile(login);
//*strptr = settings;
return settings;
}
}
}
so i could access the settings like this:
settings.ini['some']['settings'];
Bit i get an error:
Error 329 'settings.ini()' is a
'method' but is used like a 'type'
I know its probably too noobish, but im currently trying to learn C# and experiments like this teach more than reading a 500 page book.
You defined "ini" as a method. In order to use it, as-is, you would need to type:
settings.ini()['some']['settings'];
You could change this to a property to work around this issue.
However, one thing to be aware of - the way you have this written, every time you call ini(), you're creating (and accessing) a new instance of your IniData class. If this is meant to be a single, shared instance, you may want to use lazy initialization with a static property:
public class Settings
{
private static IniData ini;
public static IniData Ini
{
get
{
if (ini == null)
{
string login = "Settings.ini";
FileIniDataParser parser = new FileIniDataParser();
ini = parser.LoadFile(login);
}
return ini;
}
}
}
This way, each call will use the same instance, and it will only create the IniData class one time. You could then write:
Settings.Ini['some']['settings'];
var foo = Settings.Data['some']['settings'];
// ..
public static class Settings
{
static Settings()
{
string login = "Settings.ini";
FileIniDataParser parser = new FileIniDataParser();
_data = parser.LoadFile(login);
}
private static readonly IniData _data;
public static IniData Data
{
get { return _data; }
}
}
Try making your ini method a property:
namespace my_project
{
public class settings
{
public IniData Ini
{
get
{
string login = "Settings.ini";
FileIniDataParser parser = new FileIniDataParser();
// Parse the ini file with the settings
IniData settings = parser.LoadFile(login);
//*strptr = settings;
return settings;
}
}
}
}
To access your instance as
settings.ini['some']['settings'];
You need to make ini a property:
public IniData ini
{
get
{
string login = "Settings.ini";
FileIniDataParser parser = new FileIniDataParser();
// Parse the ini file with the settings
IniData settings = parser.LoadFile(login);
return settings;
}
}
The way you have it now, you must access it as:
settings.ini()['some']['settings'];
Related
Using the below code, I'm trying to save app settings data in JSON format so it's easily readable and (in theory) easy to load back directly into data structures. Unfortunately it's not working out that way.
My general strategy is to ahve a series of lists representing different types of settings which I'll drop into one ListDictionary and then save as a single JSON object. Then, in theory, I load it back to a ListDictionary and recast the values into the lists they started as.
// Paths to pin to quick menu in Windows Explorer
public List<string> quickPaths = new List<string>();
public string diag = "";
public string settingsFile = System.AppDomain.CurrentDomain.BaseDirectory + "customizer_settings.json";
public Prefs()
{
ListDictionary prefs = LoadPrefs();
quickPaths = (List<string>)prefs["quickPaths"];
}
public ListDictionary LoadPrefs()
{
if (!File.Exists(settingsFile)) return new ListDictionary();
string json = File.ReadAllText(settingsFile);
return JsonSerializer.Deserialize<ListDictionary>(json);
}
public void SavePrefs()
{
ListDictionary toSave = new ListDictionary();
toSave["quickPaths"] = quickPaths;
File.WriteAllText(settingsFile, JsonSerializer.Serialize(toSave));
}
Instead, I'm getting the error in the title on the quickPaths assignment in the Prefs() constructor. I've looked it up and there's nothing else out there about this error specifically and no workarounds I've been able to find.
I've tried iterating over the prefs["quickPaths"] value and manually adding them one at a time to the List, but that's both inelegant and doesn't work anyway. Clearly I'm doing something wrong, but I don't know what. I thought I would get from deserialize exactly what I serialized, but it seems it doesn't work that way.
Here's what the output of the save function looks like:
{"quickPaths":["C:\\output","C:\\Users","C:\\Windows"]}
Try with the Newtonsoft like this
public class Prefs
{
public List<string> quickPaths = new List<string>();
public string diag = "";
public string settingsFile = System.AppDomain.CurrentDomain.BaseDirectory + "customizer_settings.json";
public Prefs()
{
ListDictionary prefs = LoadPrefs();
quickPaths = ((JArray)prefs["quickPaths"]).ToObject<List<string>>();
}
public ListDictionary LoadPrefs()
{
if (!File.Exists(settingsFile)) return new ListDictionary();
string json = File.ReadAllText(settingsFile);
return JsonConvert.DeserializeObject<ListDictionary>(json);
}
public void SavePrefs()
{
ListDictionary toSave = new ListDictionary();
toSave["quickPaths"] = quickPaths;
File.WriteAllText(settingsFile, JsonConvert.SerializeObject(toSave));
}
}
or you can deserialize one more time, like
quickPaths = JsonSerializer.Deserialize<List<string>>(((JsonElement)prefs["quickPaths"]).GetRawText());
I tried the Newtonsoft method including the deserializing of the sub-element and it didn't work (at least not how I implemented it).
Instead I re-evaluated my data structures and got rid of the ListDictionary in favor of Dictionary<string,List> since that's what I was doing anyway.
From there, I just needed to convert the comma-separated string to a list which can be done with built-in functions like so:
// Paths to pin to quick menu in Windows Explorer
public List<string> quickPaths = new List<string>();
public string diag = "";
public string settingsFile = System.AppDomain.CurrentDomain.BaseDirectory + "customizer_settings.json";
public Prefs()
{
Dictionary<string,List<string>> prefs;
prefs = LoadPrefs();
quickPaths = prefs["quickPaths"].ToList<string>();
}
public Dictionary<string,List<string>> LoadPrefs()
{
if (!File.Exists(settingsFile)) return new Dictionary<string,List<string>>();
string json = File.ReadAllText(settingsFile);
return JsonSerializer.Deserialize<Dictionary<string,List<string>>>(json);
}
public void SavePrefs()
{
Dictionary<string,List<string>> toSave = new Dictionary<string,List<string>>();
toSave["quickPaths"] = quickPaths;
File.WriteAllText(settingsFile, JsonSerializer.Serialize(toSave));
}
I am using the IOptions pattern as described in the official documentation.
This works fine when I am reading values from appsetting.json, but how do I update values and save changes back to appsetting.json?
In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.
At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options package that has functionality to write configuration values back to appsettings.json.
In one of my ASP.NET Core projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json, more precisly in an optional appsettings.custom.json file, that gets added to the configuration if present.
Like this...
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
I declared the IWritableOptions<T> interface that extends IOptions<T>; so I can just replace IOptions<T> by IWritableOptions<T> whenever I want to read and write settings.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
Also, I came up with IOptionsWriter, which is a component that is intended to be used by IWritableOptions<T> to update a configuration section. This is my implementation for the beforementioned interfaces...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
Since the writer is not aware about the file structure, I decided to handle sections as JObject objects. The accessor tries to find the requested section and deserializes it to an instance of T, uses the current value (if not found), or just creates a new instance of T, if the current value is null. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken instance that is going to replace the section...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
Finally, I implemented an extension method for IServicesCollection allowing me to easily configure a writable options accessor...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
Which can be used in ConfigureServices like...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
In my Controller class I can just demand an IWritableOptions<CustomizableOptions> instance, that has the same characteristics as IOptions<T>, but also allows to change and store configuration values.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
Simplified version of Matze's answer:
public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
Usage:
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"));
Then:
private readonly IWritableOptions<MyOptions> _options;
public MyClass(IWritableOptions<MyOptions> options)
{
_options = options;
}
To save the changes to the file:
_options.Update(opt => {
opt.Field1 = "value1";
opt.Field2 = "value2";
});
And you can pass a custom json file as optional parameter (it will use appsettings.json by default):
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"), "appsettings.custom.json");
public static void SetAppSettingValue(string key, string value, string appSettingsJsonFilePath = null) {
if (appSettingsJsonFilePath == null) {
appSettingsJsonFilePath = System.IO.Path.Combine(System.AppContext.BaseDirectory, "appsettings.json");
}
var json = System.IO.File.ReadAllText(appSettingsJsonFilePath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject < Newtonsoft.Json.Linq.JObject > (json);
jsonObj[key] = value;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(appSettingsJsonFilePath, output);
}
I see a lot of answers use Newtonsoft.Json package to update appsettings. I will provide some some solutions that use System.Text.Json package (built-in on .Net Core 3 and above).
OPTION 1
Before you start updating appsettings.json file dynamically, ask yourself a question, how comlex is that part of appsettings.json that needs to be updated. If the part that needs to be updated is not very complex, you can use appsettings transformation functionality just for that part that that needs to be updated. Here's an example:
Let's say my appsettings.json file looks like that:
{
"Username": "Bro300",
"Job": {
"Title": "Programmer",
"Type": "IT"
}
}
And let's say I need to update only Job section. Instead of updating appsettings.json directly I can create a smaller file appsettings.MyOverrides.json that will look like this:
{
"Job": {
"Title": "Farmer",
"Type": "Agriculture"
}
}
And then make sure that this new file is added in my .Net Core app, and .Net Core will figure out how to load the new updated settings.
Now the next step is to create a wrapper class that will hold values from appsettings.MyOverrides.json like this:
public class OverridableSettings
{
public JobSettings Job { get; set; }
}
public class JobSettings
{
public string Title { get; set; }
public string Type { get; set; }
}
And then I can create my updater class that will look like this (notice that it takes in OverridableSettings and completely overrides appsettings.MyOverrides.json file:
public class AppSettingsUpdater
{
public void UpdateSettings(OverridableSettings settings)
{
// instead of updating appsettings.json file directly I will just write the part I need to update to appsettings.MyOverrides.json
// .Net Core in turn will read my overrides from appsettings.MyOverrides.json file
const string SettinsgOverridesFileName = "appsettings.MyOverrides.json";
var newConfig = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettinsgOverridesFileName, newConfig);
}
}
Finally this is the code that demonstrates how to use it:
public static class Program
{
public static void Main()
{
// Notice that appsettings.MyOverrides.json will contain only the part that we need to update, other settings will live in appsettings.json
// Also appsettings.MyOverrides.json is optional so if it doesn't exist at the program start it's not a problem
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.MyOverrides.json", optional: true)
.Build();
// Here we read our current settings
var settings = configuration.Get<OverridableSettings>();
var settingsUpdater = new AppSettingsObjectUpdater();
settings.Job.Title = "Farmer";
settings.Job.Type = "Agriculture";
settingsUpdater.UpdateSettings(settings);
// Here we reload the settings so the new values from appsettings.MyOverrides.json will be read
configuration.Reload();
// and here we retrieve the new updated settings
var newJobSettings = configuration.GetSection("Job").Get<JobSettings>();
}
}
OPTION 2
If the appsetting transformation does not fit you case, and you have to update values only one level deep, you can use this simple implementation:
public void UpdateAppSetting(string key, string value)
{
var configJson = File.ReadAllText("appsettings.json");
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(configJson);
config[key] = value;
var updatedConfigJson = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("appsettings.json", updatedConfigJson);
}
OPTION 3
Finally, if you have some complex case and you need to update appsettings, multiple levels deep, here is another implementation, that expands on the previous option, and uses recursion to update the settings at any level:
public class AppSettingsUpdater
{
private const string EmptyJson = "{}";
public void UpdateAppSetting(string key, object value)
{
// Empty keys "" are allowed in json by the way
if (key == null)
{
throw new ArgumentException("Json property key cannot be null", nameof(key));
}
const string settinsgFileName = "appsettings.json";
// We will create a new file if appsettings.json doesn't exist or was deleted
if (!File.Exists(settinsgFileName))
{
File.WriteAllText(settinsgFileName, EmptyJson);
}
var config = File.ReadAllText(settinsgFileName);
var updatedConfigDict = UpdateJson(key, value, config);
// After receiving the dictionary with updated key value pair, we serialize it back into json.
var updatedJson = JsonSerializer.Serialize(updatedConfigDict, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(settinsgFileName, updatedJson);
}
// This method will recursively read json segments separated by semicolon (firstObject:nestedObject:someProperty)
// until it reaches the desired property that needs to be updated,
// it will update the property and return json document represented by dictonary of dictionaries of dictionaries and so on.
// This dictionary structure can be easily serialized back into json
private Dictionary<string, object> UpdateJson(string key, object value, string jsonSegment)
{
const char keySeparator = ':';
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonSegment);
var keyParts = key.Split(keySeparator);
var isKeyNested = keyParts.Length > 1;
if (isKeyNested)
{
var firstKeyPart = keyParts[0];
var remainingKey = string.Join(keySeparator, keyParts.Skip(1));
// If the key does not exist already, we will create a new key and append it to the json
var newJsonSegment = config.ContainsKey(firstKeyPart) && config[firstKeyPart] != null
? config[firstKeyPart].ToString()
: EmptyJson;
config[firstKeyPart] = UpdateJson(remainingKey, value, newJsonSegment);
}
else
{
config[key] = value;
}
return config;
}
}
You can use, like this:
var settingsUpdater = new AppSettingsUpdater();
settingsUpdater.UpdateAppSetting("OuterProperty:NestedProperty:PropertyToUpdate", "new value");
Update value through this code
it's simply run console application that reads application settings, adds a new setting, and updates an existing setting. and after update refresh the application on server without closed application.
For more information: See Microsoft .Net Docs, ConfigurationManager.AppSettings Property
static void AddUpdateAppSettings(string key, string value)
{
try
{
var configFile = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
var settings = configFile.AppSettings.Settings;
if (settings[key] == null)
{
settings.Add(key, value);
}
else
{
settings[key].Value = value;
}
configFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name);
}
catch (ConfigurationErrorsException ex)
{
Console.WriteLine("Error writing app settings. Error: "+ ex.Message);
}
}
While there is still not a way via the Options accessor, I'd like to preset a .NET 6 class that makes it quite easy to write back to the file. You can use the JsonNode class in the System.Text.Json.Nodes class. I'm using it to write back an encrypted connection string after reading a plain text one from appsettings.json.
There are examples of using Newtonsoft.Json.JsonConvert.DeserializeObject and deserializing into a dynamic type like #Alper suggested - but System.Text.Json could not do that. Well, now you sort of can :) (though not with a dynamic type).
In my example below, I tried to be minimalistic and simple. I used JsonNode to retrieve the value instead of a Dependency Injected IConfiguration. In a real web application, I'd be using the DI method. It really doesn't matter how you retrieve the setting, writing it back still means reconstructing the Json and updating the file on disk.
MS Link for JsonNode: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonnode?view=net-6.0
My appsettings.json sample:
{
"sampleSection": {
"someStringSetting": "Value One",
"deeperValues": {
"someIntSetting": 23,
"someBooleanSetting": true
}
}
}
C# .NET 6 console application:
using System.Text.Json;
using System.Text.Json.Nodes;
const string AppSettingsPath = #"<PathToYourAppSettings.JsonFile>>\appsettings.json";
string appSettingsJson = File.ReadAllText(AppSettingsPath);
var jsonNodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
var node = JsonNode.Parse(appSettingsJson, jsonNodeOptions);
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine("=========== Before ============");
Console.WriteLine(node.ToJsonString(options));
// Now you have access to all the structure using node["blah"] syntax
var stringSetting = (string) node["sampleSection"]["someStringSetting"];
var intSetting = (int) node["sampleSection"]["deeperValues"]["someIntSetting"];
var booleanSetting = (bool) node["sampleSection"]["deeperValues"]["someBooleanSetting"];
Console.WriteLine($"stringSetting: {stringSetting}, intSetting: {intSetting}, booleanSetting: {booleanSetting}");
// Now write new values back
node["sampleSection"]["someStringSetting"] = $"New setting at {DateTimeOffset.Now}";
node["sampleSection"]["deeperValues"]["someIntSetting"] = -6;
node["sampleSection"]["deeperValues"]["someBooleanSetting"] = false;
Console.WriteLine("=========== After ============");
Console.WriteLine(node.ToJsonString(options));
// Or, to actually write it to disk:
// File.WriteAllText(AppSettingsPath, node.ToJsonString(options));
I hope that my scenario covers your intent, I wanted to override the appsettings.json values if there are environment variables passed to the app at startup.
I made use of the ConfigureOptions method that is available in dotnet core 2.1.
Here is the Model that is used for the JSON from appsettings.json
public class Integration
{
public string FOO_API {get;set;}
}
For the services in the statup.cs:
var section = Configuration.GetSection ("integration");
services.Configure<Integration> (section);
services.ConfigureOptions<ConfigureIntegrationSettings>();
Here is the implemenation:
public class ConfigureIntegrationSettings : IConfigureOptions<Integration>
{
public void Configure(Integration options)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FOO")))
options.FOO_API = Environment.GetEnvironmentVariable("FOO_API");
}
}
so if there is no value set it falls back to the appsettings.json
I solved similar problem - I needed override appSettings like this:
For 'IConfigurationBuilder':
configurationBuilder
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environmentName}.json", false, true)
.AddConfigurationObject(TenantsTimeZoneConfigurationOverrides(configurationBuilder)); // Override Tenants TimeZone configuration due the OS platform (https://dejanstojanovic.net/aspnet/2018/july/differences-in-time-zones-in-net-core-on-windows-and-linux-host-os/)
private static Dictionary<string, string> TenantsTimeZoneConfigurationOverrides(IConfigurationBuilder configurationBuilder)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var overridesDictionary = new Dictionary<string, string>();
var configuration = configurationBuilder.Build() as IConfiguration;
var tenantsSection = configuration.GetSection(TenantsConfig.TenantsCollectionConfigSectionName).Get<Tenants>();
foreach (var tenant in tenantsSection)
{
if (!string.IsNullOrEmpty(tenant.Value.TimeZone))
{
overridesDictionary.Add($"Tenants:{tenant.Key}:TimeZone", GetSpecificTimeZoneDueOsPlatform(isWindows, tenant.Value.TimeZone));
}
}
return overridesDictionary;
}
private static string GetSpecificTimeZoneDueOsPlatform(bool isWindows, string timeZone)
{
return isWindows ? timeZone : TZConvert.WindowsToIana(timeZone);
}
Questions.
1. How to add, delete, update, read only one entry in a JSON file?
2. Is it possible to programmatically add, delete an entry from the file "SettingJson.json"?
Description.
I suggest using a JSON file to store application settings.
How to read and write the whole file, I figured it out.
If I understand correctly, then:
Delete:
1. Remove the property from the "Settings.cs" class;
2. Delete the property from the file "SettingJson.json";
Add:
1. Add a property from the "Settings.cs" class;
2. Add a property from the file "SettingJson.json";
Update:
???
Read:
???
Settings.cs
namespace ConsoleApp
{
public class Settings
{
public string NameSetting1 { get; set; }
public string NameSetting2 { get; set; }
public string NameSetting3 { get; set; }
}
}
Program.cs
//
using Newtonsoft.Json;
using System.IO;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Settings[] settings = new Settings[] { };
settings = FillArrayAll();
SaveDataArray(settings);
}
static Settings[] FillArrayAll()
{
Settings[] settings = new Settings[]
{
new Settings()
{
NameSetting1 = "ValueSetting_1",
NameSetting2 = "ValueSetting_2",
NameSetting3 = "ValueSetting_3"
}
};
return settings;
}
static Settings[] FillArrayOneItem()
{
Settings[] settings = new Settings[]
{
new Settings()
{
NameSetting2 = "ValueSetting_2",
}
};
return settings;
}
static void SaveDataArray(Settings[] settings)
{
string path = $"{Environment.CurrentDirectory}\\SettingsFolder\\SettingJson.json";
using (StreamWriter writer = File.CreateText(path))
{
string output = JsonConvert.SerializeObject(settings);
writer.Write(output);
}
}
public Settings[] ReadData()
{
string path = $"{Environment.CurrentDirectory}\\SettingsFolder\\SettingJson.json";
using (var reader = File.OpenText(path))
{
var fileText = reader.ReadToEnd();
return JsonConvert.DeserializeObject<Settings[]> (fileText);
}
}
}
}
Update-1
Based on materials from the answer Pavel Anikhouski - 2020-02-28 16: 16
How to make separate methods?
The "Write" method.
Gets:
key-value (one key or a collection of keys);
path to the file SettingJson.json.
Performs:
- writes data to SettingJson.json.
The Read Method.
Gets:
- key (one key or collection of keys);
- path to the file SettingJson.json.
Performs:
- reads data from SettingJson.json.
Returns:
- key value (values or a collection of values).
You can use Json.Linq for manipulation with json, e.g. create JObject from Settings using FromObject method and manipulate with it
var settings = FillArrayAll();
//create JObject from settings
var created = JObject.FromObject(new { settings });
//convert to json
var json = created.ToString();
//write json to a file
//…
//read it back
var updated = JObject.Parse(json);
//get value by key
var key = updated["settings"]?.FirstOrDefault()?["NameSetting1"];
//add new empty Settings to an array
(updated["settings"] as JArray)?.Add(JObject.FromObject(new Settings()));
//convert the updated object back to json
var resultJson = updated.ToString();
The comments near code should be pretty explanatory.
You can also find some details at Querying JSON with LINQ, Parsing JSON and Creating JSON
I have an input csv file that is read by C#. I want to write a piece of code that scans the file for all 'nulls' and replace with an empty string on the output.
so far I have wrote this. The public FileProcessor() doesnt seem to work
class FileProcessor
{
//File Objects
readonly Dictionary<string, FileInfo> _fileDict;
//DB Objects
private readonly EMIRDB _emirdb;
public FileProcessor()
{
_fileDict = new Dictionary<string, FileInfo>();
_emirdb = new EMIRDB();
}
public FileProcessor()
{
string replacenull = File.ReadAllText ("EMIR_VU_E_");
replacenull = replacenull.Replace("null", "");
File.WriteAllText("EMIR_VU_E_", replacenull);
}
You cannot have two constructors with the same set of parameters (in this case, zero parameters).
Thus:
public FileProcessor()
{
_fileDict = new Dictionary<string, FileInfo>();
_emirdb = new EMIRDB();
}
public FileProcessor()
{
string replacenull = File.ReadAllText ("EMIR_VU_E_");
replacenull = replacenull.Replace("null", "");
File.WriteAllText("EMIR_VU_E_", replacenull);
}
is not allowed.
You need to remove one of the constructors. Or move the code from one constructor into the other.
I am using the IOptions pattern as described in the official documentation.
This works fine when I am reading values from appsetting.json, but how do I update values and save changes back to appsetting.json?
In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.
At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options package that has functionality to write configuration values back to appsettings.json.
In one of my ASP.NET Core projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json, more precisly in an optional appsettings.custom.json file, that gets added to the configuration if present.
Like this...
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
I declared the IWritableOptions<T> interface that extends IOptions<T>; so I can just replace IOptions<T> by IWritableOptions<T> whenever I want to read and write settings.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
Also, I came up with IOptionsWriter, which is a component that is intended to be used by IWritableOptions<T> to update a configuration section. This is my implementation for the beforementioned interfaces...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
Since the writer is not aware about the file structure, I decided to handle sections as JObject objects. The accessor tries to find the requested section and deserializes it to an instance of T, uses the current value (if not found), or just creates a new instance of T, if the current value is null. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken instance that is going to replace the section...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
Finally, I implemented an extension method for IServicesCollection allowing me to easily configure a writable options accessor...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
Which can be used in ConfigureServices like...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
In my Controller class I can just demand an IWritableOptions<CustomizableOptions> instance, that has the same characteristics as IOptions<T>, but also allows to change and store configuration values.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
Simplified version of Matze's answer:
public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
Usage:
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"));
Then:
private readonly IWritableOptions<MyOptions> _options;
public MyClass(IWritableOptions<MyOptions> options)
{
_options = options;
}
To save the changes to the file:
_options.Update(opt => {
opt.Field1 = "value1";
opt.Field2 = "value2";
});
And you can pass a custom json file as optional parameter (it will use appsettings.json by default):
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"), "appsettings.custom.json");
public static void SetAppSettingValue(string key, string value, string appSettingsJsonFilePath = null) {
if (appSettingsJsonFilePath == null) {
appSettingsJsonFilePath = System.IO.Path.Combine(System.AppContext.BaseDirectory, "appsettings.json");
}
var json = System.IO.File.ReadAllText(appSettingsJsonFilePath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject < Newtonsoft.Json.Linq.JObject > (json);
jsonObj[key] = value;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(appSettingsJsonFilePath, output);
}
I see a lot of answers use Newtonsoft.Json package to update appsettings. I will provide some some solutions that use System.Text.Json package (built-in on .Net Core 3 and above).
OPTION 1
Before you start updating appsettings.json file dynamically, ask yourself a question, how comlex is that part of appsettings.json that needs to be updated. If the part that needs to be updated is not very complex, you can use appsettings transformation functionality just for that part that that needs to be updated. Here's an example:
Let's say my appsettings.json file looks like that:
{
"Username": "Bro300",
"Job": {
"Title": "Programmer",
"Type": "IT"
}
}
And let's say I need to update only Job section. Instead of updating appsettings.json directly I can create a smaller file appsettings.MyOverrides.json that will look like this:
{
"Job": {
"Title": "Farmer",
"Type": "Agriculture"
}
}
And then make sure that this new file is added in my .Net Core app, and .Net Core will figure out how to load the new updated settings.
Now the next step is to create a wrapper class that will hold values from appsettings.MyOverrides.json like this:
public class OverridableSettings
{
public JobSettings Job { get; set; }
}
public class JobSettings
{
public string Title { get; set; }
public string Type { get; set; }
}
And then I can create my updater class that will look like this (notice that it takes in OverridableSettings and completely overrides appsettings.MyOverrides.json file:
public class AppSettingsUpdater
{
public void UpdateSettings(OverridableSettings settings)
{
// instead of updating appsettings.json file directly I will just write the part I need to update to appsettings.MyOverrides.json
// .Net Core in turn will read my overrides from appsettings.MyOverrides.json file
const string SettinsgOverridesFileName = "appsettings.MyOverrides.json";
var newConfig = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettinsgOverridesFileName, newConfig);
}
}
Finally this is the code that demonstrates how to use it:
public static class Program
{
public static void Main()
{
// Notice that appsettings.MyOverrides.json will contain only the part that we need to update, other settings will live in appsettings.json
// Also appsettings.MyOverrides.json is optional so if it doesn't exist at the program start it's not a problem
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.MyOverrides.json", optional: true)
.Build();
// Here we read our current settings
var settings = configuration.Get<OverridableSettings>();
var settingsUpdater = new AppSettingsObjectUpdater();
settings.Job.Title = "Farmer";
settings.Job.Type = "Agriculture";
settingsUpdater.UpdateSettings(settings);
// Here we reload the settings so the new values from appsettings.MyOverrides.json will be read
configuration.Reload();
// and here we retrieve the new updated settings
var newJobSettings = configuration.GetSection("Job").Get<JobSettings>();
}
}
OPTION 2
If the appsetting transformation does not fit you case, and you have to update values only one level deep, you can use this simple implementation:
public void UpdateAppSetting(string key, string value)
{
var configJson = File.ReadAllText("appsettings.json");
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(configJson);
config[key] = value;
var updatedConfigJson = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("appsettings.json", updatedConfigJson);
}
OPTION 3
Finally, if you have some complex case and you need to update appsettings, multiple levels deep, here is another implementation, that expands on the previous option, and uses recursion to update the settings at any level:
public class AppSettingsUpdater
{
private const string EmptyJson = "{}";
public void UpdateAppSetting(string key, object value)
{
// Empty keys "" are allowed in json by the way
if (key == null)
{
throw new ArgumentException("Json property key cannot be null", nameof(key));
}
const string settinsgFileName = "appsettings.json";
// We will create a new file if appsettings.json doesn't exist or was deleted
if (!File.Exists(settinsgFileName))
{
File.WriteAllText(settinsgFileName, EmptyJson);
}
var config = File.ReadAllText(settinsgFileName);
var updatedConfigDict = UpdateJson(key, value, config);
// After receiving the dictionary with updated key value pair, we serialize it back into json.
var updatedJson = JsonSerializer.Serialize(updatedConfigDict, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(settinsgFileName, updatedJson);
}
// This method will recursively read json segments separated by semicolon (firstObject:nestedObject:someProperty)
// until it reaches the desired property that needs to be updated,
// it will update the property and return json document represented by dictonary of dictionaries of dictionaries and so on.
// This dictionary structure can be easily serialized back into json
private Dictionary<string, object> UpdateJson(string key, object value, string jsonSegment)
{
const char keySeparator = ':';
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonSegment);
var keyParts = key.Split(keySeparator);
var isKeyNested = keyParts.Length > 1;
if (isKeyNested)
{
var firstKeyPart = keyParts[0];
var remainingKey = string.Join(keySeparator, keyParts.Skip(1));
// If the key does not exist already, we will create a new key and append it to the json
var newJsonSegment = config.ContainsKey(firstKeyPart) && config[firstKeyPart] != null
? config[firstKeyPart].ToString()
: EmptyJson;
config[firstKeyPart] = UpdateJson(remainingKey, value, newJsonSegment);
}
else
{
config[key] = value;
}
return config;
}
}
You can use, like this:
var settingsUpdater = new AppSettingsUpdater();
settingsUpdater.UpdateAppSetting("OuterProperty:NestedProperty:PropertyToUpdate", "new value");
Update value through this code
it's simply run console application that reads application settings, adds a new setting, and updates an existing setting. and after update refresh the application on server without closed application.
For more information: See Microsoft .Net Docs, ConfigurationManager.AppSettings Property
static void AddUpdateAppSettings(string key, string value)
{
try
{
var configFile = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
var settings = configFile.AppSettings.Settings;
if (settings[key] == null)
{
settings.Add(key, value);
}
else
{
settings[key].Value = value;
}
configFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name);
}
catch (ConfigurationErrorsException ex)
{
Console.WriteLine("Error writing app settings. Error: "+ ex.Message);
}
}
While there is still not a way via the Options accessor, I'd like to preset a .NET 6 class that makes it quite easy to write back to the file. You can use the JsonNode class in the System.Text.Json.Nodes class. I'm using it to write back an encrypted connection string after reading a plain text one from appsettings.json.
There are examples of using Newtonsoft.Json.JsonConvert.DeserializeObject and deserializing into a dynamic type like #Alper suggested - but System.Text.Json could not do that. Well, now you sort of can :) (though not with a dynamic type).
In my example below, I tried to be minimalistic and simple. I used JsonNode to retrieve the value instead of a Dependency Injected IConfiguration. In a real web application, I'd be using the DI method. It really doesn't matter how you retrieve the setting, writing it back still means reconstructing the Json and updating the file on disk.
MS Link for JsonNode: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonnode?view=net-6.0
My appsettings.json sample:
{
"sampleSection": {
"someStringSetting": "Value One",
"deeperValues": {
"someIntSetting": 23,
"someBooleanSetting": true
}
}
}
C# .NET 6 console application:
using System.Text.Json;
using System.Text.Json.Nodes;
const string AppSettingsPath = #"<PathToYourAppSettings.JsonFile>>\appsettings.json";
string appSettingsJson = File.ReadAllText(AppSettingsPath);
var jsonNodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
var node = JsonNode.Parse(appSettingsJson, jsonNodeOptions);
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine("=========== Before ============");
Console.WriteLine(node.ToJsonString(options));
// Now you have access to all the structure using node["blah"] syntax
var stringSetting = (string) node["sampleSection"]["someStringSetting"];
var intSetting = (int) node["sampleSection"]["deeperValues"]["someIntSetting"];
var booleanSetting = (bool) node["sampleSection"]["deeperValues"]["someBooleanSetting"];
Console.WriteLine($"stringSetting: {stringSetting}, intSetting: {intSetting}, booleanSetting: {booleanSetting}");
// Now write new values back
node["sampleSection"]["someStringSetting"] = $"New setting at {DateTimeOffset.Now}";
node["sampleSection"]["deeperValues"]["someIntSetting"] = -6;
node["sampleSection"]["deeperValues"]["someBooleanSetting"] = false;
Console.WriteLine("=========== After ============");
Console.WriteLine(node.ToJsonString(options));
// Or, to actually write it to disk:
// File.WriteAllText(AppSettingsPath, node.ToJsonString(options));
I hope that my scenario covers your intent, I wanted to override the appsettings.json values if there are environment variables passed to the app at startup.
I made use of the ConfigureOptions method that is available in dotnet core 2.1.
Here is the Model that is used for the JSON from appsettings.json
public class Integration
{
public string FOO_API {get;set;}
}
For the services in the statup.cs:
var section = Configuration.GetSection ("integration");
services.Configure<Integration> (section);
services.ConfigureOptions<ConfigureIntegrationSettings>();
Here is the implemenation:
public class ConfigureIntegrationSettings : IConfigureOptions<Integration>
{
public void Configure(Integration options)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FOO")))
options.FOO_API = Environment.GetEnvironmentVariable("FOO_API");
}
}
so if there is no value set it falls back to the appsettings.json
I solved similar problem - I needed override appSettings like this:
For 'IConfigurationBuilder':
configurationBuilder
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environmentName}.json", false, true)
.AddConfigurationObject(TenantsTimeZoneConfigurationOverrides(configurationBuilder)); // Override Tenants TimeZone configuration due the OS platform (https://dejanstojanovic.net/aspnet/2018/july/differences-in-time-zones-in-net-core-on-windows-and-linux-host-os/)
private static Dictionary<string, string> TenantsTimeZoneConfigurationOverrides(IConfigurationBuilder configurationBuilder)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var overridesDictionary = new Dictionary<string, string>();
var configuration = configurationBuilder.Build() as IConfiguration;
var tenantsSection = configuration.GetSection(TenantsConfig.TenantsCollectionConfigSectionName).Get<Tenants>();
foreach (var tenant in tenantsSection)
{
if (!string.IsNullOrEmpty(tenant.Value.TimeZone))
{
overridesDictionary.Add($"Tenants:{tenant.Key}:TimeZone", GetSpecificTimeZoneDueOsPlatform(isWindows, tenant.Value.TimeZone));
}
}
return overridesDictionary;
}
private static string GetSpecificTimeZoneDueOsPlatform(bool isWindows, string timeZone)
{
return isWindows ? timeZone : TZConvert.WindowsToIana(timeZone);
}