.net 如何将环境变量Map到dotnet IHostedService中的配置对象?

nnvyjq4y  于 2023-04-13  发布在  .NET
关注(0)|答案(3)|浏览(130)

我正在创建一个新的控制台应用程序,这是我第一次使用IHostedService。如果我想让应用程序可以使用appsettings.json中的值,现在正确的方法似乎是这样做:

public static async Task Main(string[] args)
{
    await Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<MyHostedService>();

                    services.Configure<MySettings(hostContext.Configuration.GetSection("MySettings"));
                    services.AddSingleton<MySettings>(container =>
                    {
                        return container.GetService<IOptions<MySettings>>().Value;
                    });
                })
                .RunConsoleAsync();
}

public class MyHostedService
{
    public MyHostedService(MySettings settings)
    {
        // values from MySettings should be available here
    }
}

public class MySettings
{
    public string ASetting {get; set;}
    public string AnotherSetting {get; set; }
}

// appsettings.json
{  
    "MySettings": {
        "ASetting": "a setting value",
        "AnotherSetting":  "another value"
  }
}

这是可行的,很好。但是,如果我想从环境变量而不是appsettings.json节中获取变量呢?我可以看到它们在hostContext.Configuration中可用,我可以通过Configuration.GetValue获取单个值。但我需要在MyHostedService中使用它们。
我尝试在本地创建它们(即在Windows中作为用户变量),使用双下划线格式,即MySettings_ASetting,但它们似乎不可用或覆盖appsettings.json值。
我想这意味着将它们Map到MySettings这样的对象,并以相同的方式通过DI传递它,但我不确定如何做到这一点,是否有一个等效的GetSection,或者是否需要以不同的方式命名我的变量以使它们被拾取?

jhkqcmku

jhkqcmku1#

如果您将环境变量声明为ASettingAnotherSetting,那么在ConfigureServices中,您需要添加一个绑定到保存环境变量的完整IConfiguration,而不是只绑定到具有MySettings节名称/路径的环境变量,因为该名称/路径也会被考虑用于查找相应的环境变量-请参阅下面的替代方法。
下面的扩展方法来自Microsoft.Extensions.DependencyInjection版本7.0.0,它在.NET 6上运行,请参阅documentation

services.AddOptions<MySettings>()
    .BindConfiguration("MySettings") // Binds to the appsettings section.
    .Bind(hostContext.Configuration); // Binds to e.g. the environment variables.

完整代码:

await Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<MyHostedService>();
        
        services.AddOptions<MySettings>()
            .BindConfiguration("MySettings")
            .Bind(hostContext.Configuration);

        services.AddSingleton<MySettings>(container =>
        {
            return container.GetService<IOptions<MySettings>>().Value;
        });
    })
    .RunConsoleAsync();

或者,您可以将这些环境变量声明为MySettings:AnotherSettingMySettings:AnotherSetting,在这种情况下,只需进行以下调用之一即可。

services.AddOptions<MySettings>().BindConfiguration("MySettings");

或者你已经拥有的代码,没有Bind(hostContext.Configuration)

services.Configure<MySettings>(hostContext.Configuration.GetSection("MySettings"));
j9per5c4

j9per5c42#

编辑1:

这段代码有问题:

services.AddSingleton<MySettings>(container =>
{
  return container.GetService<IOptions<MySettings>>().Value;
});

调用GetService将构建服务提供者,但随后您尝试向服务提供者添加单例。这将不起作用。您应该在服务中注入IOptions<MySettings>

编辑2:

根据我的经验,双下划线效果不好,如果可以的话,更喜欢使用冒号分隔键,如MySettings:AnotherValue
一个更现代的答案:

MySettings.cs

public class MySettings
{
    // Add default configuration path so it can be reused elsewhere
    public const string DefaultSectionName = "MySettings";

    public string AnotherSetting { get; set; } = string.Empty;

    public string ASetting { get; set; } = string.Empty;
}

MyHostedService.cs

public class MyHostedService : IHostedService
{
    private readonly MySettings settings;

    // IOptions<TOptions> or IOptionsMonitor<TOptions>
    // these interfaces add useful extensions features
    public MyHostedService(IOptions<MySettings> settings)
    {
        this.settings = settings.Value;
    }

    // IHostedService Implementation redacted
}

Progam.cs

// Use top-level statements, linear and fluent service declaration:
var builder = Host.CreateApplicationBuilder(args);
builder.Services

    // Declare MyHostedService as a HostedService in the DI engine
    .AddHostedService<MyHostedService>()

    // Declare IOption<MySettings> (and variants) in the DI engine
    .AddOptions<MySettings>()

    // Bind the options to the "MySettings" section of the config
    .BindConfiguration(MySettings.DefaultSectionName);

await builder.Build().RunAsync();

从CLI或环境读取设置

默认情况下,主机构建器支持从环境变量加载配置:

Properties/launchSettings.json

{
  "profiles": {
    "run": {
      "commandName": "Project",
      "environmentVariables": {
        "MySettings:AnotherSetting": "test-another"
      }
    }
  }
}

Host.CreateDefaultBuilder不同,Host.CreateApplicationBuilder也支持从CLI加载配置。您可以使用dotnet run --MySettings:AnotherSetting test覆盖appsettings.json文件的内容。请注意,在CLI上设置变量会覆盖环境变量值。

qybjjes1

qybjjes13#

如果您不想从appsettings.json部分而是从环境变量中获取变量,则可以将ConfigureAppConfiguration部分或/和ConfigureHostConfiguration添加到Host

await Host
    .CreateDefaultBuilder(args)
    .ConfigureHostConfiguration((config) =>
    {
        config.SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("hostsettings.json", optional: true)
        .AddEnvironmentVariables(prefix: "PREFIX_");
    })
    .ConfigureAppConfiguration((hostContext, configBuilder) =>
    {
     configBuilder
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json") 
    // or 
    .AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", 
                                                  optional: true, reloadOnChange: true) 
     //or just any name explicitly
    .AddJsonFile(@"C:\...\mySettings.json")
    .Build();
    })
    .ConfigureServices((hostContext, services) =>
    {
       services.Configure<MySettings>(hostContext.Configuration.GetSection("MySettings"));
       services.AddHostedService<MyHostedService>();
    })
    .RunConsoleAsync();

和服务

public class MyHostedService : IHostedService
{
        private readonly MySettings _settings;

    public MyHostedService(IOptions<MySettings> settings)
    {
        _settings = settings.Value;
    }
    public Task StartAsync(CancellationToken cancellationToken) {};

    public Task StopAsync(CancellationToken cancellationToken) {};
}

相关问题