ASP.NET Core应用程序中如何在代码中更新appsettings.json文件

73

我目前正在使用asp.net core v1.1开展项目工作,在我的appsettings.json文件中,有以下内容:

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

我还有一个用于存储它们的类:

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; }
}

并且启用 DI 后,可以在 Startup.cs 中使用它们:

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
有没有办法通过控制器更改并保存MaxPvPowerMaxWindPower

我尝试使用

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");
 }

但它什么也没做。


1
第一选项,先生。我所做的是将这两个设置移动到另一个文件 - installationsettings.json,并在启动类中使用reloadOnChange进行注册,在更新时修改文件,正如@Ankit今天早些时候建议的那样。 - Siemko
@Manuel:我打开文件,进行修改然后保存。 - Siemko
@Siemko,谢谢,这是我自己找到的唯一方法。 在IOptions中添加一个保存更改的方法会更好。 - Norcino
1
我喜欢这个链接:https://learn.microsoft.com/en-us/answers/questions/299791/saving-to-appsettingsjson.html,然而我们应该考虑一下……“我真的认为在服务用户下运行的应用程序中使用外部配置更改是一个好主意吗?” - Walter Verhoeven
1
@WalterVerhoeven 它可以应用于 MAUI 应用程序,这个功能会很有用。同意对于 Web 应用程序来说,最好避免使用它。 - alelom
显示剩余3条评论
16个回答

39

基本上,您可以像这样设置 IConfiguration 中的值:

IConfiguration configuration = ...
// ...
configuration["key"] = "value";
问题在于,例如 JsonConfigurationProvider 并未实现将配置保存到文件中。如您在源码中所看到的,它并未覆盖 ConfigurationProvider 的 Set 方法 (请参见源码)。 您可以创建自己的提供程序并在其中实现保存操作。这里有一个示例(Entity Framework 自定义提供程序的基本示例)

24

这里有一篇与 .Net Core 应用程序配置设置相关的微软文章:

Asp.Net Core Configuration

该页面还提供了示例代码,可能也会有所帮助。

更新

我认为内存提供程序和绑定到 POCO 类可能会有些用处,但并不符合原帖作者的期望。

下一个选项可以是在添加配置文件时将 AddJsonFilereloadOnChange 参数设置为 true,并手动解析 JSON 配置文件并进行所需更改。

    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 仅支持 ASP.NET Core 1.1 及以上版本。


感谢@Ankit的回答!这个解决方案完美地运行了,但只能在Startup类中使用,我无法从控制器中访问Configuration对象以使用它来更改配置文件。我需要两个变量,它们将像全局静态变量一样起作用,并通过内置依赖注入进行访问,但也可以从控制器中进行更改。 - Siemko
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Ankit
再次感谢您,这是我想避免的事情,但如果没有其他办法,我就必须尝试一下。 - Siemko
我尝试了所有的方法,但似乎它只保存在内存中。磁盘文件没有改变。 - alerya
1
多年过去了,当这个函数几乎构建完成时,设置的值仅在内存中持久存在。更改值不会改变文件中的值... - JayDee
显示剩余4条评论

21

在 ASP.NET Core 中运行时更新 appsettings.json 文件。

参考以下示例的 appsettings.json 文件:

{
  Config: {
     IsConfig: false
  }
}

这是将IsConfig属性更新为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");
    }
}

虽然我喜欢这种方法,但如果您的键没有“:”,代码将无法正常运行。我建议使用以下代码: ... var splittedKey = key.Split(":"); var sectionPath = splittedKey[0]; if (!string.IsNullOrEmpty(sectionPath) && splittedKey.Length > 1) { var keyPath = splittedKey[1]; ... - Frank Hintsch
请查看此答案,它使用多个嵌套层次改进了此代码。 - alelom

17
我采用了Qamar Zamans的代码(谢谢),并修改了它,使其能够编辑更多层次的参数。希望这对某些人有所帮助,我很惊讶这不是一个库特性。
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; 
        }
    }

2
谢谢,Alex:我对这段代码有一个建议的改进: 在递归调用之前添加以下行以防止在不存在某个部分的情况下出现NullRef异常。 jsonObj[currentSection] ??= new JObject(); - Henri Koelewijn

6

我看到大多数答案使用 Newtonsoft.Json 包来更新设置。如果您需要更新一层深度的设置,可以不使用 Newtonsoft.Json,而是使用 System.Text.Json(内置于 .Net Core 3.0 及以上版本)功能。这里是一个简单的实现:

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);
}

完美、简洁、优雅 - Shane.A
这个完美地运行了。 所有的例子都会抛出异常,原因是 System.Text.Json 的索引器有一些无效的参数,即“System.Text.Json.JsonElement.this[int] 的最佳重载方法匹配有一些无效的参数”。 - deathrace

6

根据Qamar Zaman和Alex Horlock的代码,我稍微改动了一下。

 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);
        }


    }
}

你改了什么,为什么改? - ΩmegaMan
@ΩmegaMan 我已经改成使用嵌套属性,我还添加了SetValueRecursively方法以递归使用它。 - Mahdi Farhani
1
请将这些信息放入你现有的文本中...仅仅通过代码很难看出你实现了什么。谢谢。 - ΩmegaMan

5
    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);
    }

0

我正在使用自己的配置部分和自己的强类型对象。我总是使用这个强类型对象注入IOptions。我能够在运行时更改配置。非常小心对象的作用域。请求范围对象会获取新的配置值。我使用构造函数注入。

尽管如此,关于此的文档非常不清楚... 我不确定这是否是意味着的。阅读深入讨论以了解更多信息。


0
我解决这个问题的方法是添加一个“override”属性存储在内存缓存中。例如,我的应用程序在“appSettings.json”文件中有一个“CacheEnabled”设置,确定是否缓存数据查询结果。在应用程序/数据库测试期间,有时希望将此属性设置为“false”。
通过管理员菜单,管理员可以覆盖“CacheEnabled”设置。确定缓存是否启用的逻辑首先检查覆盖。如果找不到覆盖值,则使用“appSettings.json”值。
对于许多人来说,这可能不是一个好的解决方案,因为需要额外的基础设施来实现它。然而,我的应用程序已经有了缓存服务和管理员菜单,所以实现起来非常容易。

0

它可以匹配不同的层和不同的环境。并且它使用Newtonsoft.Json。这里是代码。

/// <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;
}

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接