ASP.NET 核心 appsettings.json 代码更新

ASP.NET Core appsettings.json update in code

我目前正在使用 asp.net 核心 v1.1 进行项目,在我的 appsettings.json 中我有:

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

我还有 class 用来存储它们:

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

然后在 Startup.cs:

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

有什么方法可以从 Controller 更改和保存 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");
 }

但它什么也没做。

这是一篇来自 Microsoft 的有关 .Net Core 应用程序配置设置的相关文章:

Asp.Net Core Configuration

该页面还有 sample code 也可能有帮助。

更新

我认为 In-memory provider and binding to a POCO class 可能会有一些用处,但没有像 OP 预期的那样工作。

下一个选项可以在添加配置文件时将 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 is only supported in ASP.NET Core 1.1 and higher.

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

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

问题在于,例如JsonConfigurationProvider 没有实现将配置保存到文件中。正如您在 source it does not override the Set method of ConfigurationProvider. (see source)

中看到的

您可以创建自己的提供程序并在那里实现保存。 Here (Basic sample of Entity Framework custom provider) 是如何操作的示例。

假设 appsettings.json 有一个 eureka 端口,并且想在 args (-p 5090) 中动态更改它。通过这样做,可以在创建许多服务时轻松地为 docker 更改端口。

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

        }
     }

在运行时更新 ASP.NET 核心中的 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");
    }
}
    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);
    }

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

关于这方面的文档还很不清楚。我不确定这是否是故意的。阅读此 in-depth discussion

我获取了 Qamar Zamans 的代码(谢谢)并对其进行了修改以允许编辑 more:than:one:layer:deep 的参数。

希望它能帮到一些人,惊讶的是这不是某处的图书馆功能。

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

有一个更简单的答案可以在运行时修改 appsettings.json。

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

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


    }
}

我解决这个问题的方法是添加一个“覆盖”属性 存储在内存缓存中。因此,例如,我的应用程序在“appSettings.json”文件中有一个“CacheEnabled”设置,用于确定是否缓存数据查询结果。在应用程序/数据库测试期间,有时需要将此 属性 设置为“false”。

通过管理员菜单,管理员可以覆盖“CacheEnabled”设置。确定缓存是否启用的逻辑首先检查覆盖。如果找不到覆盖值,则使用“appSettings.json”值。

考虑到实施它需要额外的基础设施,对于很多人来说这可能不是一个好的解决方案。但是,我的应用程序已经有一个缓存服务和一个管理员菜单,所以它很容易实现。

我看到大多数答案都使用 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);
}

在我的项目中,我以这种方式使用 Active Directory 设置:

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

因此,不使用 appsettings.json。


之后我可以从控制器更新此设置:

//...
    [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);
    }
//...

看起来对我有用

基于@Alper Ebicoglu 的回答

获取:

// ===== || GET || GET appsettings.js property =====================================================================
[HttpGet]
[Route("GetNotificationDays")]
public async Task<IActionResult> GetNotificationDays()
{   
    
        var path = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
        var json = await System.IO.File.ReadAllTextAsync(path);
        dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(json);
        return StatusCode(200, new { daysBefore = (int)jsonObj.InvoicementNotificationSettings.DaysBefore});
}

Exp:

(int)jsonObj.InvoicementNotificationSettings.DaysBefore =

    (int) = cast to int - depending on the property
    
    jsonObj = appsettings.js,
    
    InvoicementNotificationSettings = object in appsettings.js,
    
    DaysBefore = property in InvoicementNotificationSettings

更新:appsettings.js

 // ===== || PUT || UPDATE appsettings.js property =====================================================================
    [HttpPut]
    [Route("SetNotificationDays")]
    public async Task<IActionResult> SetNotificationDays(int notificationDays)
    {
        if (notificationDays != 0)
        {
                 var path = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
                 var json = await System.IO.File.ReadAllTextAsync(path);
                 dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(json);
                 jsonObj.InvoicementNotificationSettings.DaysBefore = notificationDays;
                 string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
                 await System.IO.File.WriteAllTextAsync(path, output);
                 return await GetNotificationDays();
        }
        return StatusCode(409);
    }

如果从内存中读取应用程序设置: 例如:int daysBefore = configuration.GetValue<int>("InvoicementNotificationSettings:DaysBefore");

比 Startup.js - 更新后自动重新加载 appsettings.js

 public class Startup
    {
        public static IConfiguration Configuration { get; set; }

        // Constructor -----------------------------------------------------------------------------------------------------------------------------
        public Startup(IConfiguration configuration, Microsoft.Extensions.Hosting.IHostEnvironment env)
        {
            Configuration = configuration;

           // To autoreload appsettings.js after update -------------------------      
            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();
        }

appsettings.js

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ItlCrmsDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  },
  "InvoicementNotificationSettings": {
    "DaysBefore": 4
  },
   
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}