76

I am currently working on project using asp.net core v1.1, and in my appsettings.json I have:

"AppSettings": {
   "AzureConnectionKey": "***",
   "AzureContainerName": "**",
   "NumberOfTicks": 621355968000000000,
   "NumberOfMiliseconds": 10000,
   "SelectedPvInstalationIds": [ 13, 137, 126, 121, 68, 29 ],
   "MaxPvPower": 160,
   "MaxWindPower": 5745.35
},

I also have class that I use to store them:

public class AppSettings
{
    public string AzureConnectionKey { get; set; }
    public string AzureContainerName { get; set; }
    public long NumberOfTicks { get; set; }
    public long NumberOfMiliseconds { get; set; }
    public int[] SelectedPvInstalationIds { get; set; }
    public decimal MaxPvPower { get; set; }
    public decimal MaxWindPower { get; set; }
}

And DI enabled to use then in Startup.cs:

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

Is there any way to change and save MaxPvPower and MaxWindPower from Controller?

I tried using

private readonly AppSettings _settings;

public HomeController(IOptions<AppSettings> settings)
{
    _settings = settings.Value;
}

[Authorize(Policy = "AdminPolicy")]
 public IActionResult UpdateSettings(decimal pv, decimal wind)
 {
    _settings.MaxPvPower = pv;
    _settings.MaxWindPower = wind;

    return Redirect("Settings");
 }

But it did nothing.

8
  • It isn't clear what you mean by "change and save." Are you wanting the "save" 1. to update appsettings.json or 2. to save only its in-memory representation. Commented Jan 15, 2017 at 17:52
  • 3
    Option number 1, Sir. What I managed to do was to move those two settings to another file - installationsettings.json, register them with reloadOnChange in Startup class and when updating - modifying the file, as @Ankit suggested earlier today. Commented Jan 15, 2017 at 19:44
  • 1
    I like this, learn.microsoft.com/en-us/answers/questions/299791/… however one would should think .. "do I really think I like external configuration changes in my app running under a service user" Commented Nov 24, 2021 at 9:38
  • 1
    this is the best I have found: github.com/Nongzhsh/Awesome.Net.WritableOptions Commented May 3, 2022 at 21:15
  • 1
    @WalterVerhoeven it can apply to MAUI apps, where this feature would be useful. Agreed that for WebApps this is rather to be avoided. Commented Jan 28, 2023 at 20:06

17 Answers 17

43

Basically you can set the values in IConfiguration like this:

IConfiguration configuration = ...
// ...
configuration["key"] = "value";

The issue there is that e.g. the JsonConfigurationProvider does not implement the saving of the configuration into the file. As you can see in the source it does not override the Set method of ConfigurationProvider. (see source)

You can create your own provider and implement the saving there. Here (Basic sample of Entity Framework custom provider) is an example how to do it.

Sign up to request clarification or add additional context in comments.

Comments

25

Here is a relevant article from Microsoft regarding Configuration setup in .Net Core Apps:

Asp.Net Core Configuration

The page also has sample code which may also be helpful.

Update

I thought In-memory provider and binding to a POCO class might be of some use but does not work as OP expected.

The next option can be setting reloadOnChange parameter of AddJsonFile to true while adding the configuration file and manually parsing the JSON configuration file and making changes as intended.

public class Startup
{
    ...
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }
    ...
}

... reloadOnChange is only supported in ASP.NET Core 1.1 and higher.

9 Comments

Thank you @Ankit for your answer! This one works perfectly, but only in Startup class, I cannot access Configuration object from Controller to use it to change configuration files. And I need two variables that will act like global static and accessed by build-in dependency injection, but also changeable from controller.
@Siemko ok, I think you can trying parsing the configuration file yourself and modify the values directly and keep reloadOnChange value to true in startup.cs.
Thank you once again, that's something I wanted to avoid, but if there's no other way, I have to try that.
@Siemko You can access the Configuration object anywhere by registering it with DI. Could that work?
After many years, when this function is almost built in, setting values are persist only in memory. Changing values does not change values in the file...
|
25

Update appsettings.json file in ASP.NET Core at runtime.

Take this sample appsettings.json file:

{
  Config: {
     IsConfig: false
  }
}

This is the code to update IsConfig property to true:

Main()
{
    AddOrUpdateAppSetting("Config:IsConfig", true);
}

public static void AddOrUpdateAppSetting<T>(string key, T value) 
{
    try 
    {
        var filePath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
        string json = File.ReadAllText(filePath);
        dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
                
        var sectionPath = key.Split(":")[0];

        if (!string.IsNullOrEmpty(sectionPath)) 
        {
            var keyPath = key.Split(":")[1];
            jsonObj[sectionPath][keyPath] = value;
        }
        else 
        {
            jsonObj[sectionPath] = value; // if no sectionpath just set the value
        }

        string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
        File.WriteAllText(filePath, output);
    }
    catch (ConfigurationErrorsException) 
    {
        Console.WriteLine("Error writing app settings");
    }
}

3 Comments

Although I liked this approach the code won't function if you have a key without ":". I'd suggest ... var splittedKey = key.Split(":"); var sectionPath = splittedKey[0]; if (!string.IsNullOrEmpty(sectionPath) && splittedKey.Length > 1) { var keyPath = splittedKey[1]; ...
See this answer that improves this code with multiple nested layers.
@Qamar Please edit the spelling of appSettings.json to appsettings.json
19

I took Qamar Zamans code (thank you) and modified it to allow for editing parameters which are more:than:one:layer:deep.

Hope it helps someone out, surprised that this isn't a library feature somewhere.

public static class SettingsHelpers
{
    public static void AddOrUpdateAppSetting<T>(string sectionPathKey, T value)
    {
        try
        {
            var filePath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
            string json = File.ReadAllText(filePath);
            dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);

            SetValueRecursively(sectionPathKey, jsonObj, value);

            string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
            File.WriteAllText(filePath, output);

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error writing app settings | {0}", ex.Message);
        }
    }

    private static void SetValueRecursively<T>(string sectionPathKey, dynamic jsonObj, T value)
    {
        // split the string at the first ':' character
        var remainingSections = sectionPathKey.Split(":", 2);

        var currentSection = remainingSections[0];
        if (remainingSections.Length > 1)
        {
            // continue with the procress, moving down the tree
            var nextSection = remainingSections[1];
            SetValueRecursively(nextSection, jsonObj[currentSection], value);
        }
        else
        {
            // we've got to the end of the tree, set the value
            jsonObj[currentSection] = value; 
        }
    }

1 Comment

Thanks, Alex: I have a suggested improvement on this code: Add the following line just before the recursive call to prevent a NullRef exception in the case a section does not exist. jsonObj[currentSection] ??= new JObject();
9

I see most of the answers use Newtonsoft.Json package for updating the settings. If you need to update the settings that are one layer deep, you can go without Newtonsoft.Json and use System.Text.Json (built-in on .Net Core 3.0 and above) functionality. Here's a 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);
}

2 Comments

perfect, nice and simple
This worked perfectly. Above all examples throw exception for System.Text.Json indexer viz. "best overloaded method match for 'System.Text.Json.JsonElement.this[int]' has some invalid arguments".
7
    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);
    }

Comments

6

According to Qamar Zaman and Alex Horlock codes, I've changed it a little bit.

 public static class SettingsHelpers
 {
    public static void AddOrUpdateAppSetting<T>(T value, IWebHostEnvironment webHostEnvironment)
    {
        try
        {
            var settingFiles = new List<string> { "appsettings.json", $"appsettings.{webHostEnvironment.EnvironmentName}.json" };
            foreach (var item in settingFiles)
            {


                var filePath = Path.Combine(AppContext.BaseDirectory, item);
                string json = File.ReadAllText(filePath);
                dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);

                SetValueRecursively(jsonObj, value);

                string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
                File.WriteAllText(filePath, output);
            }
        }
        catch (Exception ex)
        {
            throw new Exception($"Error writing app settings | {ex.Message}", ex);
        }
    }



    private static void SetValueRecursively<T>(dynamic jsonObj, T value)
    {
        var properties = value.GetType().GetProperties();
        foreach (var property in properties)
        {
            var currentValue = property.GetValue(value);
            if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string) || property.PropertyType == typeof(decimal))
            {
                if (currentValue == null) continue;
                try
                {
                    jsonObj[property.Name].Value = currentValue;

                }
                catch (RuntimeBinderException)
                {
                    jsonObj[property.Name] = new JValue(currentValue);


                }
                continue;
            }
            try
            {
                if (jsonObj[property.Name] == null)
                {
                    jsonObj[property.Name] = new JObject();
                }

            }
            catch (RuntimeBinderException)
            {
                jsonObj[property.Name] = new JObject(new JProperty(property.Name));

            }
            SetValueRecursively(jsonObj[property.Name], currentValue);
        }


    }
}

3 Comments

What have you changed and why?
@ΩmegaMan I changed it to use nested property, I've added SetValueRecursively method to use it recursively
Put that info in the text you have...its hard to discern what is you have achieved by just looking at the code. thx.
2

With the code from @mykhailo-seniutovych as basis, I have build a System.Text.Json version that can go multiple levels deep and adds values if they don't exist yet.

public static void UpdateAppSetting(string path, string key, string value)
{
    var options = new JsonSerializerOptions
    {
        ReadCommentHandling = JsonCommentHandling.Skip
    };
    var configJson = File.ReadAllText(path);

    var config = JsonSerializer.Deserialize<Dictionary<string, object>>(configJson, options);
    if (config is null) return;
    config = UpdateAppSettingInternal(config, options, key, value);
    var updatedConfigJson = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
    File.WriteAllText(path, updatedConfigJson);
}

private static Dictionary<string, object> UpdateAppSettingInternal(Dictionary<string, object> config, JsonSerializerOptions options, string key, string value)
{
    if (key.Contains(":"))
    {
        var subKey = key.Split(':',2);
        if (config.ContainsKey(subKey[0]))
        {
            var subConfigString = config[subKey[0]].ToString();
            if (subConfigString is null)
                return config;
            var subConfig = JsonSerializer.Deserialize<Dictionary<string, object>>(subConfigString, options);
            if (subConfig is not null)
            {
                config[subKey[0]] = UpdateAppSettingInternal(subConfig, options, subKey[1], value);
                return config;
            }
        }
        else
        {
            config[subKey[0]] = UpdateAppSettingInternal(new Dictionary<string, object>(), options, subKey[1], value);
            return config;
        }
    }
    else
        config[key] = value;
    return config;
}

Comments

1

I'm using my own configuration section and my own strongly typed object. I'm always injecting IOptions with this strongly typed object. And I'm able to change configuration in runtime. Be very careful with scopes of objects. New configuration values are picked up by request scoped object. I'm using constructor injection.

Documentation on this is very unclear though .. I'm no sure if this is meant to be. Read this in-depth discussion

Comments

1

It match different layer and different environment. And it use Newtonsoft.Json. Here the code.

/// <summary>
/// update appsettings.{environment}.json
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="environment"></param>
public static void Update<T>(string key, T value, string? environment = null)
{
    var filePath = Path.Combine(Directory.GetCurrentDirectory(), $"appSettings.{(string.IsNullOrEmpty(environment) ? "" : $"{environment}.")}json");
    string json = File.ReadAllText(filePath);
    dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);

    var sectionPaths = key.Split(":").ToList();
    jsonObj = SetValue(jsonObj, sectionPaths, 0, value);

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

private static dynamic SetValue<T>(dynamic jsonObj, List<string> sectionPaths, int index, T value)
{
    if (sectionPaths.Count > index)
    {
        jsonObj[sectionPaths[index]] = SetValue(jsonObj[sectionPaths[index]], sectionPaths, ++index, value);
    }
    else
    {
        jsonObj = value;
    }
    return jsonObj;
}

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
1

Here is my solution where I use JObject to parse and update appsettings.json:

public static void UpdateAppSetting
(
    string key,
    string value,
    string environment = ""
)
{
    try
    {
        // I let the user provide the path with the double-dots for convenience.
        // Because any path has to passed to JObject with single dots
        key = key.Replace
        (
            ":",
            "."
        );

        // Get appsettings.json path according to the environment which can also be empty
        var filePath = Path.Combine
        (
            AppContext.BaseDirectory,
            environment.Any()
                ? $"appsettings.{environment}.json"
                : "appsettings.json"
        );

        // Read appsettings.json and parse it to a JObject
        string json = File.ReadAllText(filePath);
        var jObject = JObject.Parse(json);

        var keyParts = key.Split(".");

        // Get the path for the parent of the property we want to update
        var parentPath = key.Substring
        (
            0,
            key.LastIndexOf(".")
        );
        
        // Select the parent as JToken
        var parentToken = jObject.SelectToken(parentPath);

        // Along with the parent, now we pass the property we want to update and set the value
        parentToken[keyParts.Last()] = value;

        string output = JsonConvert.SerializeObject
        (
            jObject,
            Formatting.Indented
        );

        File.WriteAllText
        (
            filePath,
            output
        );
    }
    catch (Exception ex)
    {
        Console.WriteLine
        (
            "Error writing app settings | {0}",
            ex.Message
        );
    }
}

Usage:

Update simple property:

UpdateAppSetting
(
   "Parent:Property",
   "ValueToSet",
   Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
)

Update nested property:

UpdateAppSetting
(
   "Parent:Child:Property",
   "ValueToSet",
   Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
)

Update nested list property:

UpdateAppSetting
(
   "Parent:Child[1]:Property",
   "ValueToSet",
   Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
)

Cheers.

Comments

1

A new solution for .NET Core using :

  • JsonNode from System.Text.Json
  • Manage multiple depth levels
  • Add values if they don't exist

Usage

private void UpdateDefaultLogLevel(LogLevel logLevel)
{
    string path = "C:\\...\\appsettings.json";
    AppSettingsDocument doc = new(path);
    doc["Logging:LogLevel:Default"] = logLevel.ToString();
    doc.Save(path);
}

AppSettingsDocument

using System.IO;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace SO.Helpers;

public class AppSettingsDocument
{
    private JsonNode? _doc;

    public JsonNode? this[string key] { get => GetValue(key); set => SetValue(key, value); }

    public AppSettingsDocument()
    { }

    public AppSettingsDocument(string path)
    {
        using FileStream file = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);
        _doc = JsonNode.Parse(file,
            new() { PropertyNameCaseInsensitive = true },
            new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip });

    }

    private JsonNode? GetValue(string key)
    {
        return GetParentNode(key, false, out string targetProperty)?[targetProperty];
    }


    private void SetValue(string key, JsonNode? value)
    {
        GetParentNode(key, true, out string? targetProperty)![targetProperty] = value;
    }

    private JsonNode? GetParentNode(string key, bool create, out string targetProperty)
    {
        JsonNode? node;
        string[] props = key.Split(':');
        targetProperty = props[^1];
        _doc ??= new JsonObject();
        JsonNode parent = _doc.Root;
        for (int i = 0; i < props.Length - 1; i++)
        {
            node = parent[props[i]];
            if (node is null)
            {
                if (create)
                {
                    node = new JsonObject();
                    parent[props[i]] = node;
                }
                else
                {
                    return null;
                }
            }
            parent = node;
        }
        return parent;
    }

    public void Save(string path)
    {
        using FileStream file = new(path, FileMode.Create, FileAccess.Write, FileShare.None);
        using Utf8JsonWriter jsonWriter = new(file, new JsonWriterOptions() { Indented = true });
        _doc ??= new JsonObject();
        _doc.WriteTo(jsonWriter);
    }

}

Comments

1

As there are already so many solution posted here, I just want to point to this answer: https://stackoverflow.com/a/79328423/1797939 It defines a small Helper class
This solution should perfectly integrate into modern .net core apps that uses Dependency Injection IServiceProvider and IConfiguration
You can use the helper simple like that:

var configRoot = serviceProvider.GetRequiredService<IConfigurationRoot>();
var section = configRoot.GetSection("UserSettings");
section["LastFolderUsed"] = "C:\\Temp";
configRoot.SaveJsonProvider(); //this will save the changes to the json file, file is created if not exists

Comments

0

Suppose appsettings.json has an eureka port, and want to change it dynamically in args (-p 5090). By doing this, can make change to the port easily for docker when creating many services.

  "eureka": {
  "client": {
    "serviceUrl": "http://10.0.0.101:8761/eureka/",
    "shouldRegisterWithEureka": true,
    "shouldFetchRegistry": false 
  },
  "instance": {
    "port": 5000
  }
}


   public class Startup
   {
    public static string port = "5000";
    public Startup(IConfiguration configuration)
    {
        configuration["eureka:instance:port"] = port;

        Configuration = configuration;
    }


    public static void Main(string[] args)
    {
        int port = 5000;
        if (args.Length>1)
        {
            if (int.TryParse(args[1], out port))
            {
                Startup.port = port.ToString();
            }

        }
     }

Comments

0

The way I address this issue is by adding an "override" property is stored in a memory cache. So for example, my application has a "CacheEnabled" setting in the "appSettings.json" file that determines whether or not data query results are cached or not. During application / db testing, it is sometimes desirable to set this property to "false".

Through an administrator menu, an Administrator can override the "CacheEnabled" setting. The logic that determines whether or not the Cache is enabled, first checks for the override. If it doesn't find an override value, then it uses the "appSettings.json" value.

This probably isn't a good solution for a lot of people given the extra infrastructure needed to implement it. However, my application already had a Caching Service as well as an Administrator menu, so it was pretty easy to implement.

Comments

0

In my project I'm work with Active Directory Settings this way:

//...
public class Startup
{
    public void ConfigureServices(IServicesCollection services)
    {
        //...
        services.Configure<Ldap>(opts=> {
            opts.Url = "example.com";
            opts.UseSsl = true;
            opts.Port = 111;
            opts.BindDn = "CN=nn,OU=nn,OU=nn,DC=nn,DC=nn";
            opts.BindCredentials = "nn";
            opts.SearchBase = "DC=nn,DC=nn";
            opts.SearchFilter = "(&(objectClass=User){0})";
            opts.AdminCn = "CN=nn,OU=nn,OU=nn,DC=nn,DC=nn";
            opts.SearchGroupBase = "OU=nn,DC=nn,DC=nn";
        });
        //...
    }
}

So, without using appsettings.json.


After that I can update this settings from controller:

//...
    [HttpPost("setActiveDirectorySettings")]
    public ActionResult<IOptions<Ldap>> SetActiveDirectorySettings(ActiveDirectorySettings clientActiveDirectorySettings)
    {
        LdapOptions.Value.Url = clientActiveDirectorySettings.Url;
        LdapOptions.Value.UseSsl = clientActiveDirectorySettings.UseSsl;
        LdapOptions.Value.Port = clientActiveDirectorySettings.Port;
        LdapOptions.Value.BindDn = clientActiveDirectorySettings.BindDn;
        LdapOptions.Value.BindCredentials = clientActiveDirectorySettings.BindCredentials;
        LdapOptions.Value.SearchBase = clientActiveDirectorySettings.SearchBase;
        LdapOptions.Value.SearchFilter = clientActiveDirectorySettings.SearchFilter;
        LdapOptions.Value.AdminCn = clientActiveDirectorySettings.AdminCn;
        LdapOptions.Value.SearchGroupBase = clientActiveDirectorySettings.SearchGroupBase;
        return Ok(LdapOptions.Value);
    }
//...

Looks like it works for me

1 Comment

Please provide additional details in your answer. As it's currently written, it's hard to understand your solution.
-1

There is an esier answer to modify the appsettings.json at runtime.

Json File structure

var filePath = Path.Combine(System.AppContext.BaseDirectory, "appSettings.json");

string jsonString = System.IO.File.ReadAllText(filePath);

//use https://json2csharp.com/ to create the c# classes from your json
Root root = JsonSerializer.Deserialize<Root>(jsonString);

var dbtoadd = new Databas()
{
    Id = "myid",
    Name = "mynewdb",
    ConnectionString = ""
};

//add or change anything to this object like you do on any list
root.DatabaseSettings.Databases.Add(dbtoadd);

//serialize the new updated object to a string
string towrite = JsonSerializer.Serialize(root);

//overwrite the file and it wil contain the new data
System.IO.File.WriteAllText(filePath, towrite);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.