8

i was digging to find out the solution but didn't manage to find it, i bet that someone has encountered this problem, so what is the problem?.

For test i have created simple console application (solution will be used in asp.net core web api).

I have TestSetting.json configuration file with 'Copy Always' setuped.

{
  "setting1" : "value1" 
}

And Simple code

IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();

IConfigurationRoot configuration = configurationBuilder.AddJsonFile("TestSettings.json",false, reloadOnChange: true).Build();

Console.WriteLine(configuration.GetSection("setting1").Value); // Output: value1
//Change configuration manually in file while console is waiting
Console.ReadKey();

//Changed manually in file Value appears
Console.WriteLine(configuration.GetSection("setting1").Value); // Output: Whatever you have setuped
Console.ReadKey();

configuration.GetSection("setting1").Value = "changed from code";
//Changed in code value appear
Console.WriteLine(configuration.GetSection("setting1").Value); // Output: changed from code

I have 2 requirements, i want to make it possible to change value in json configuration file manually while application is running and application will see updated value during next get of setting Section and it is working.

Second requirement is that, i want to preserve some information, to be accurate a last execution time of task which should be executed once per setuped period ex. once a day, so some loop check last execution time value and determine if operation has to be executed. Someone would ask that what i have will work, but i need also to cover scenario when operation has been executed and application has been restarted (server error, user restart etc), and i need to save this information in a way which will allow me to read it after app startup.

Reading code sample we can see that after changing setting1 in code we see that this section has been changed while trying to output it to console.

configuration.GetSection("setting1").Value = "changed from code";
//Changed in code value appear
Console.WriteLine(configuration.GetSection("setting1").Value); // Output: changed from code

Here comes the question :). Is it possible that this settings section change will also affect actual value in json file? I don't want to manually change this file by some stream writers or whatever.

Actual result is that: after changing value in code, the new value is getable in runtime but when you will go to debug binaries you will se that value1 in TestSettings.json file hasnt been changed.

Paweł Górszczak
  • 524
  • 1
  • 5
  • 12

4 Answers4

12

Thank you "Matt Luccas Phaure Jensen" For this, information, i have't found any solution for this there. If any one wants to use IOptions here is an answer how to do it How to update values into appsetting.json?

I want to do it in the way i started, so i looked to the implementation of Microsoft.Extensions.Configuration.Json and i have prepared simple solution to allow writing and use base implementation. It probably has many limitations but it will work in simple scenarios.

Two implementations from above dll have to be extended. So lets do it.

Files to create

Implementation of WritableJsonConfigurationProvider with example save code of desired section.

Change values in JSON file (writing files)

public class WritableJsonConfigurationProvider : JsonConfigurationProvider
{
    public WritableJsonConfigurationProvider(JsonConfigurationSource source) : base(source)
    {
    }

    public override void Set(string key, string value)
    {
        base.Set(key,value);

        //Get Whole json file and change only passed key with passed value. It requires modification if you need to support change multi level json structure
        var fileFullPath = base.Source.FileProvider.GetFileInfo(base.Source.Path).PhysicalPath;
        string json = File.ReadAllText(fileFullPath);
        dynamic jsonObj = JsonConvert.DeserializeObject(json);
        jsonObj[key] = value;
        string output = JsonConvert.SerializeObject(jsonObj, Formatting.Indented);
        File.WriteAllText(fileFullPath, output);
    }
}

And implementation of WritableJsonConfigurationSource which is a extention of JsonConfigurationSource

public class WritableJsonConfigurationSource : JsonConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        this.EnsureDefaults(builder);
        return (IConfigurationProvider)new WritableJsonConfigurationProvider(this);
    }
}

and that's it, lets use it

IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
IConfigurationRoot configuration = configurationBuilder.Add<WritableJsonConfigurationSource>(
    (Action<WritableJsonConfigurationSource>)(s =>
                                                     {
                                                         s.FileProvider = null;
                                                         s.Path = "TestSettings.json";
                                                         s.Optional = false;
                                                         s.ReloadOnChange = true;
                                                         s.ResolveFileProvider();
                                                     })).Build();

Console.WriteLine(configuration.GetSection("setting1").Value); // Output: value1
Console.ReadKey();

configuration.GetSection("setting1").Value = "changed from codeeee";
Console.WriteLine(configuration.GetSection("setting1").Value); // Output: changed from codeeee

Console.ReadKey();

Value is being changed in memory as well in file. Bingo :).

The code could have problems, can be refactored etc, this is only sample quick solution.

Paweł Górszczak
  • 524
  • 1
  • 5
  • 12
  • Works! BUT not for sections. `var fromFolder = _configurationRoot.GetSection("AppSettings:FromFileFolder").Value` - this gets the correct value (although why GetSection? It gets a value) `_configurationRoot.GetSection("AppSettings:FromFileFolder").Value = "xxx"` - this creates a new value in the root of the config file called "AppSettings:FromFileFolder". Not what I want, unfortunately. – Kirk Hawley Jan 11 '21 at 21:59
5

It is not possible via the Microsoft.Extensions.Configuration package to save changes made to the configuration to disk. There is an issue about it on github here where they chose not to do it. It is possible to do, just not via the IConfiguration interface. https://github.com/aspnet/Configuration/issues/385

2

First of all thanks for the answer on this Pawel, your override method helped me a lot.

This is related to the answer from Pawel and the comment from Kirk Hawley

@Kirk Hawley

It works for sections you just need to adjust the override to match with your json pattern.

You can get the path from a section with ConfigurationSection.Path.

public static string KeyPairPath { get; set; }
MyPublicClass.KeyPairPath = mysection.Path;

As the key parameter of the override function only contains the last key and not the full key (with the parent section) if you use child sections, you can hand over the full path from the section as a static member of a class. For example:

public override void Set(string key, string value)
{
            string currentPath = MyPublicClass.KeyPairPath;

            string[] getParent = currentPath.Split(':');
            string parent = getParent[0];

            key = parent + ":" + key;

            string[] substantialKey = key.Split(":");
            
            base.Set(key, value);

            var fileFullPath = base.Source.FileProvider.GetFileInfo(base.Source.Path).PhysicalPath;
            string json = File.ReadAllText(fileFullPath);
            dynamic jsonObj = JsonConvert.DeserializeObject(json);
            jsonObj[parent][substantialKey[1]] = value;

            string output = JsonConvert.SerializeObject(jsonObj, Formatting.Indented);
            File.WriteAllText(fileFullPath, output);
}

This worked for me in order to update my appsettings.json.

Philip T.
  • 29
  • 6
0

I needed to be able to set a value on an object multiple levels down the configuration structure, so I wrote a little extension method to do it.

It uses JsonObject and JsonNode from System.Text.Json.Nodes but should be pretty easy to switch to JObject and JValue from the Newtonsoft Package.

I used the extension method to replace the line jsonObj[key] = value; from the answer by @Pawel

internal static bool SetValue(this JsonObject obj, string key, JsonNode? value, string[]? delimiters = null)
{
    var retVal = false;

    if (obj is not null)
    {
        if (delimiters is null)
        {
            // These are the default delimiters supported by the .Net Configuration
            delimiters = new string[] { ":", "__" };
        }

        var keyParts = key.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);

        JsonObject contextObj = obj;

        for (var i = 0; i < keyParts.Length - 1; i++)
        {
            JsonNode? nextContextObj = contextObj[keyParts[i]];
            if (nextContextObj is null)
            {
                nextContextObj = contextObj[keyParts[i]] = new JsonObject();
            }
            contextObj = nextContextObj.AsObject();
        }

        if (contextObj is not null)
        {
            contextObj[keyParts[keyParts.Length - 1]] = value;
            retVal = true;
        }
    }

    return retVal;
}
Troy
  • 557
  • 3
  • 15