【问题标题】:Reloading Options with reloadOnChange in ASP.NET Core在 ASP.NET Core 中使用 reloadOnChange 重新加载选项
【发布时间】:2018-12-08 09:27:55
【问题描述】:

在我的 ASP.NET Core 应用程序中,我将 appsettings.json 绑定到一个强类型类 AppSettings

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

    Configuration = builder.Build();
}

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(Configuration);
    //...
}

在一个单例类中,我将这个 AppSettings 类包装成这样:

public class AppSettingsWrapper : IAppSettingsWrapper
{
    private readonly IOptions<AppSettings> _options;

    public AppSettingsAdapter(IOptions<AppSettings> options)
    {
        _options = options ?? throw new ArgumentNullException("Options cannot be null");
    }

    public SomeObject SomeConvenienceGetter()
    {
        //...
    }
}

现在,如果 json 文件发生更改,我正在努力重新加载 AppSettings。我在某处读到该类 IOptionsMonitor 可以检测到更改,但在我的情况下它不起作用。

出于测试目的,我尝试像这样调用 OnChange 事件:

public void Configure(IApplicationBuilder applicationBuilder, IOptionsMonitor<AppSettings> optionsMonitor)
{
    applicationBuilder.UseStaticFiles();
    applicationBuilder.UseMvc();

    optionsMonitor.OnChange<AppSettings>(vals => 
    {
        System.Diagnostics.Debug.WriteLine(vals);
    });
}

当我更改 json 文件时,该事件永远不会触发。有人知道我可以改变什么来让重新加载机制在我的场景中工作吗?

【问题讨论】:

  • 这感觉像是一个错误。如果您要添加 json 文件并希望在更改时重新加载调用,那么将更改保存到文件应调用任何更改处理程序并将 haschanged 设置为 true。

标签: c# asp.net-core asp.net-core-mvc asp.net-core-2.0 asp.net-core-webapi


【解决方案1】:

您可以做的就是像在 AppSettingsWrapper 中那样围绕配置类创建包装器类并注入 IOptionsMonitor。然后保留设置类的私有属性。该包装器可以作为单例注入,并且 IOptionsMonitor 将跟踪您的更改。

public class AppSettingsWrapper
{
    private AppSettings _settings;

    public AppSettingsWrapper(IOptionsMonitor<AppSettings> settings)
    {
        _settings = settings.CurrentValue;

        // Hook in on the OnChange event of the monitor
        settings.OnChange(Listener);
    }

    private void Listener(AppSettings settings)
    {
        _settings = settings;
    }

    // Example getter
    public string ExampleOtherApiUrl => _settings.ExampleOtherApiUrl;
}

然后将你的包装类注册为单例

services.AddSingleton(sp => new AppSettingsWrapper(sp.GetService<IOptionsMonitor<AppSettings>>()));

【讨论】:

    【解决方案2】:

    您需要注入 IOptionsSnapshot&lt;AppSettings&gt; 才能使重新加载工作。

    很遗憾,您无法将 IOptionsSnapshot 加载到 Singleton 服务中。 IOptionsSnapshot 是 Scoped 服务,因此您只能在 Scoped 或 Transient 注册类中引用它。

    但是,如果考虑一下,那是有道理的。这些设置在更改时需要重新加载,因此如果将它们注入到 Singleton 中,则该类将永远不会获得更新的设置,因为不会再次为 Singleton 调用构造函数。

    【讨论】:

    • 我想我也许可以在“OnChange”事件中将 AppSettingsWrapper 与新的 AppSettings 重新绑定。不幸的是,我必须更改包装类的生命周期,因为除了罕见的重新加载之外,它永远不会改变。
    • 除非你有一个非常令人信服的理由将其保留为单例,例如实时性能,切换到Scoped 服务应该会给你你想要的东西而不会受到太大的惩罚,毕竟你仍然注入同一个类,只是注册发生了变化。
    • 没有什么能阻止您使用 IServiceProvider.CreateScope 在单例中创建范围。
    • 如果 reloadOnChange 在配置源(在本例中为文件)更改时不 ping 更改句柄,它的目的是什么?
    • 您在回答中完全忽略了 IOptionsMonitor。根据文档,这个应该能够检测到单例的变化。完美的!会是......如果它有效......
    【解决方案3】:

    如果您想要一个单例IHostedService,例如注册为单例,并且Execute 方法由计时器重复触发 + 您希望在每个触发器上都有一个新的IOptionsSnapshot,您可以使用以下代码:

    public MyHostedService (IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    private void ExecutedByTimer ()
    {
        using var scope = _serviceProvider.CreateScope();
        var options = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<MyHostedServiceOptions>>().Value;
        // Voila, options is an up-to-date snapshot
    }
    

    【讨论】:

      猜你喜欢
      • 2021-10-27
      • 2019-07-25
      • 2016-10-28
      • 2021-04-02
      • 2013-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多