如何在运行时稍后配置选项?

3

我有一个 .Net 5 Web Api 项目,想要用一些逻辑配置选项。这就是为什么我想把配置从 Startup.ConfigureServices 方法中移出来,并将它们放到它们自己的服务配置器中(在启动时运行)。所以我想到了这个:

class OptionsConfigurator : IHostedService
{
    private readonly IServiceCollection _serviceCollection;

    public StartupOptionsConfigurator(IServiceCollection serviceCollection)
    {
        _serviceCollection = serviceCollection;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        /*

            Build the ServiceProvider from the _serviceCollection
            Get the IConfiguration service from the ServiceProvider
            Read the configurationSection from the IConfiguration
            call _serviceCollection.Configure<MyOptions>(configurationSection);

        */

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

由于我无法将IServiceCollection注入到构造函数中,因此这种方法行不通。是否有一种方法可以在不需要 IServiceCollection 实例的情况下配置这些选项?

我可以提供另一个示例来说明问题:

您的 API 提供一个单一的端点来更新特定配置,以便在运行时更改它(例如,可能更改某些原因导致的数据库连接字符串)。

  • 创建一个新的 Web API 项目
  • 创建一个选项类

.

public class MyOptions
{
    public string Text { get; set; }
}
  • 创建一个控制器

.

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IOptionsMonitor<MyOptions> _myOptionsMonitor;
    private readonly IServiceCollection _serviceCollection;
    
    public MyController(IOptionsMonitor<MyOptions> myOptionsMonitor, IServiceCollection serviceCollection)
    {
        _myOptionsMonitor = myOptionsMonitor;
        _serviceCollection = serviceCollection;
    }

    [HttpPatch]
    public IActionResult UpdateOptions([FromBody] string text)
    {
        Console.WriteLine("Before: " + JsonSerializer.Serialize(_myOptionsMonitor.CurrentValue));

        _serviceCollection.Configure<MyOptions>(x => { x.Text = text; });

        string newConfig = JsonSerializer.Serialize(_myOptionsMonitor.CurrentValue);
        Console.WriteLine("After: " + newConfig);
        
        return Ok(newConfig);
    }
}

在请求正文中使用新的配置字符串调用补丁(endpoint)端点。如预期的那样,这也无法正常工作,注入IServiceCollection无法实现并引发异常。

是否有任何解决方案以后再配置选项?


嗯...您可以使用IConfigureOptions配置选项并将服务注入其中,但这仍然发生在第一次使用时。如果您正在使用Singleton选项(而不是可以重新加载最新值的Snapshot / Monitor),则可以使用新值更新IOptions.Value属性(尽管这只会影响当前应用程序,即如果复制它不会影响所有应用程序)。最后,您可以编写自己的配置提供程序,将其添加到Config构建器中,该提供程序可以从任何位置读取设置,并以您选择的方式进行更新(使用外部API或通过托管服务的帮助)。 - pinkfloydx33
答案取决于您真正想要做什么。您只是想使其能够更新连接字符串吗?还要注意,仅修改值可能不足够。许多服务仅注入IOptions..为了理解更改,他们至少需要注入IOptionsMonitor/IOptionsSnapshot,并构建以响应配置更新。 - pinkfloydx33
2个回答

3

在初始化后调用BuildServiceProvider函数之后,将无法使用IServiceCollection.Configure重新配置选项。程序初始化后,您无法在外部配置或添加任何服务。

要更改配置值,只需在UpdateOptions操作中更新appsettings.json文件,并在所有地方使用IOptionsMonitor

在配置配置时,您可以指定在更改时重新加载配置文件。在Program.CreateHostBuilder中:

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        })
        .ConfigureAppConfiguration((hostingConext, configurationBuilder) =>
        {
            configurationBuilder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            configurationBuilder.AddJsonFile($"appsettings.{hostingConext.HostingEnvironment.EnvironmentName}.json", optional: false, reloadOnChange: true);
            configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly(), true, true);
        });
}

通过这种方式,IOptionsMonitor<TOptions>.CurrentValue将始终返回存储在配置文件中的值,即使它发生更改。


IIS用于监视bin文件夹的更改。如果检测到更改,站点将被重新启动。如果您计划更改文件,请确保它不放置在bin文件夹中,并且不要计划更改web.config。这些事情适用于ASP.NET和IIS,但我不确定是否适用于您的环境。 - cly
1
ASP.NET Core可以在不重启应用程序的情况下监视JSON配置文件。这与.NET Framework ASP.NET有很大的区别,因为web.config中的更改会触发应用程序重新启动。 - Jesús López

3
听起来你应该实现自己的ConfigurationProvider。这将使您能够对配置进行更改并使用IOptionsMonitor(用于单例服务)或IOptionsSnapshot(用于瞬态或作用域服务)读取值。

有一个基于数据库的配置提供程序的示例实现在learn.microsoft.com上。将其更改为以内存(或任何您想要持久更改的地方)中存储值会相当容易,然后您可以将其公开给控制器。


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