![](/img/trans.png)
[英].NET Core 2.2 - Unable to get app configuration from Azure AppConfig
[英]How to let IOptionsMonitor<T> get the latest configuration value from a running .NET Core 2.2 app hosted on an Azure Windows Server VM?
所以我有一個 .NET Core 2.2 應用程序在帶有 Windows Server 2019 的 Azure VM 上運行,它具有以下磁盤配置:
紅框上的磁盤是 App 文件所在的位置。 以編程方式或手動方式更新配置文件時, IOptionsMonitor<T>
不會獲取更改。
如本鏈接所述:
如文檔中所述,只需啟用
reloadOnChange
然后注入IOptionsSnapshot<T>
而不是IOptions<T>
就足夠了。 不過,這需要您正確配置該類型T
我所做的,如此代碼所示:
private IConfiguration BuildConfig()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", false, reloadOnChange: true)
.Build();
}
public async Task MainAsync()
{
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
...
IServiceCollection services = ConfigureServices();
// Configures the writable options from https://github.com/Nongzhsh/Awesome.Net.WritableOptions
services.ConfigureWritableOptions<ConfigurationSettings>(_config.GetSection("configurationSettings"), "ConfigDev.json");
// ConfigurationSettings is the POCO representing the config.json contents.
services.Configure<ConfigurationSettings>(_config.GetSection("configurationSettings"));
...
}
我還沒有實現OnChange
方法,因為我假設一旦文件的內容發生變化,這些值應該自動更新。 我也試過將 .NET Core 的DOTNET_USE_POLLING_FILE_WATCHER
設置為true
但它沒有用。
這是我用於讀取和寫入配置文件的值的代碼:
public TimeService(
IServiceProvider provider,
IWritableOptions<ConfigurationSettings> writeOnlyOptions,
IOptionsMonitor<ConfigurationSettings> hotOptions)
{
_provider = provider;
_writeOnlyOptions = writeOnlyOptions;
_hotOptions = hotOptions;
}
private async Task EnsurePostedGameSchedules()
{
DateTime currentTime = DateTime.Now;
...
# region [WINDOWS ONLY] Lines for debugging.
// _hotOptions is the depency-injected IOptionsMonitor<T> object.
if (ConnectionState == ConnectionState.Connected)
{
await debugChannel.SendMessageAsync(
embed: RichInfoHelper.CreateEmbed(
"What's on the inside?",
$"Connection State: {ConnectionState}{Environment.NewLine}" +
$"Last Message ID: {_hotOptions.CurrentValue.LatestScheduleMessageID}{Environment.NewLine}" +
$"Last Message Timestamp (Local): {new ConfigurationSettings { LatestScheduleMessageID = Convert.ToUInt64(_hotOptions.CurrentValue.LatestScheduleMessageID) }.GetTimestampFromLastScheduleMessageID(true)}{Environment.NewLine}" +
$"Current Timestamp: {DateTime.Now}",
"").Build());
}
#endregion
if (new ConfigurationSettings { LatestScheduleMessageID = _hotOptions.CurrentValue.LatestScheduleMessageID }.GetTimestampFromLastScheduleMessageID(true).Date != currentTime.Date &&
currentTime.Hour >= 1)
{
...
try
{
...
if (gameScheds?.Count > 0)
{
if (gameSchedulesChannel != null)
{
// The line below updates the configuration file.
_writeOnlyOptions.Update(option =>
{
option.LatestScheduleMessageID = message?.Id ?? default;
});
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message + Environment.NewLine + e.StackTrace);
}
}
}
這是配置 POCO:
public class ConfigurationSettings
{
public string Token { get; set; }
public string PreviousVersion { get; set; }
public string CurrentVersion { get; set; }
public Dictionary<string, ulong> Guilds { get; set; }
public Dictionary<string, ulong> Channels { get; set; }
public ulong LatestScheduleMessageID { get; set; }
public string ConfigurationDirectory { get; set; }
public DateTime GetTimestampFromLastScheduleMessageID(bool toLocalTime = false) =>
toLocalTime ?
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000).ToLocalTime() :
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000);
}
為了讓IOptionsMonitor<T>
獲取配置文件中的配置更改,我還需要做什么嗎?
編輯:我忘了告訴我如何配置整個應用程序。 順便說一下,該程序是一個長時間運行的 .NET Core 控制台應用程序(不是 Web 應用程序),因此整個程序的配置方式如下:
using ...
namespace MyProject
{
public class Program
{
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
variables...
public async Task MainAsync()
{
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
_client = new DiscordSocketClient();
_config = BuildConfig();
IServiceCollection services = ConfigureServices();
services.ConfigureWritableOptions<ConfigurationSettings>(_config.GetSection("configurationSettings"), "Config.json");
services.Configure<ConfigurationSettings>(_config.GetSection("configurationSettings"));
IServiceProvider serviceProvider = ConfigureServiceProvider(services);
serviceProvider.GetRequiredService<LogService>();
await serviceProvider.GetRequiredService<CommandHandlingService>().InitializeAsync(_config.GetSection("configurationSettings"));
serviceProvider.GetRequiredService<TimeService>().Initialize(_config.GetSection("configurationSettings"));
await _client.LoginAsync(TokenType.Bot, _config.GetSection("configurationSettings")["token"]);
await _client.StartAsync();
_client.Ready += async () =>
{
...
};
await Task.Delay(-1);
}
private void ProcessExit(object sender, EventArgs e)
{
try
{
...
}
catch (Exception ex)
{
...
}
}
private IServiceCollection ConfigureServices()
{
return new ServiceCollection()
// Base Services.
.AddSingleton(_client)
.AddSingleton<CommandService>()
// Logging.
.AddLogging()
.AddSingleton<LogService>()
// Extras. Is there anything wrong with this?
.AddSingleton(_config)
// Command Handlers.
.AddSingleton<CommandHandlingService>()
// Add additional services here.
.AddSingleton<TimeService>()
.AddSingleton<StartupService>()
.AddTransient<ConfigurationService>();
}
public IServiceProvider ConfigureServiceProvider(IServiceCollection services) => services.BuildServiceProvider();
private IConfiguration BuildConfig()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", false, true)
.Build();
}
}
}
它現在無需添加任何內容即可工作。 當我讓我的項目面向 .NET Core 3.1 時,我只是讓應用程序使用編譯的可執行文件運行。 之前的應用程序面向 .NET Core 2.2 並通過 PowerShell 運行。 我不知道 PowerShell 有IOptionsMonitor<T>
。
根據我的測試,如果我們想使用IOptionsMonitor<T>
來獲取配置文件中的配置更改,請參考以下步驟我的 config.json
{
"configurationSettings": {
"Token": "...",
"PreviousVersion": "145.8.3",
"CurrentVersion": "145.23.4544",
"Guilds": {
"this setting": 4
},
"Channels": {
"announcements": 6
},
"LatestScheduleMessageID": 456,
"ConfigurationDirectory": "test"
}
}
我的POCO
public class MyOptions
{
public string Token { get; set; }
public string PreviousVersion { get; set; }
public string CurrentVersion { get; set; }
public Dictionary<string, ulong> Guilds { get; set; }
public Dictionary<string, ulong> Channels { get; set; }
public ulong LatestScheduleMessageID { get; set; }
public string ConfigurationDirectory { get; set; }
public DateTime GetTimestampFromLastScheduleMessageID(bool toLocalTime = false) =>
toLocalTime ?
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000).ToLocalTime() :
new DateTime(1970, 1, 1).AddMilliseconds((LatestScheduleMessageID >> 22) + 1420070400000);
}
public interface IWritableOptions<out T> : IOptions<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);
});
}
}
public void ConfigureServices(IServiceCollection services)
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("Config.json", optional: false, reloadOnChange:true);
var config = configBuilder.Build();
services.ConfigureWritable<MyOptions>(config.GetSection("configurationSettings"));
...
}
private readonly IWritableOptions<Locations> _writableLocations;
public OptionsController(IWritableOptions<Locations> writableLocations)
{
_writableLocations = writableLocations;
}
//Update LatestScheduleMessageID
public IActionResult Change(string value)
{
_writableLocations.Update(opt => {
opt.LatestScheduleMessageID = value;
});
return Ok("OK");
}
private readonly IOptionsMonitor<MyOptions> _options;
public HomeController(ILogger<HomeController> logger, IHostingEnvironment env, IOptionsMonitor<MyOptions> options)
{
_logger = logger;
_env = env;
_options = options;
}
public IActionResult Index()
{
var content= _env.ContentRootPath;
var web = _env.WebRootPath;
@ViewBag.Message = _options.CurrentValue.LatestScheduleMessageID;
return View();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.