ASP.NET Core 中的选项模式

作者:Kirk LarkinRick Anderson

选项模式使用类来提供对相关设置组的强类型访问。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:

选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。

本主题介绍 ASP.NET Core 中的选项模式。 若要了解如何在控制台应用中使用选项模式,请参阅 .NET 中的选项模式

查看或下载示例代码如何下载

绑定分层配置

读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 不会绑定字段。 在上面的代码中,Position 未绑定。 由于使用了 Position 属性,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

下面的代码:

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

ConfigurationBinder.Get<T> 绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使用:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

使用选项模式时的替代方法是绑定 Position 部分并将它添加到依赖项注入服务容器。 在以下代码中,PositionOptions 已通过 <xref:Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure_> 被添加到了服务容器并已绑定到了配置:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

通过使用前面的代码,以下代码将读取位置选项:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot

选项接口

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

后期配置方案允许在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。

IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions> 且仅调用适当的接口。

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions> 用于缓存 TOptions 实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。

使用 IOptionsSnapshot 读取已更新的数据

通过使用 IOptionsSnapshot<TOptions>,针对请求生存期访问和缓存选项时,每个请求都会计算一次选项。 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改。

IOptionsMonitorIOptionsSnapshot 之间的区别在于:

  • IOptionsMonitor 是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。
  • IOptionsSnapshot 是一种作用域服务,并在构造 IOptionsSnapshot<T> 对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。

以下代码使用 IOptionsSnapshot<TOptions>

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

以下代码注册 MyOptions 绑定的配置实例:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

在上面的代码中,已读取在应用启动后对 JSON 配置文件所做的更改。

IOptionsMonitor

以下代码注册 MyOptions 绑定的配置实例。

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

下面的示例使用 IOptionsMonitor<TOptions>

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

命名选项支持使用 IConfigureNamedOptions

命名选项:

  • 当多个配置节绑定到同一属性时有用。
  • 区分大小写。

请考虑以下 appsettings.json 文件:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

下面的类用于每个节,而不是创建两个类来绑定 TopItem:MonthTopItem:Year

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; }
    public string Model { get; set; }
}

下面的代码将配置命名选项:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<TopItemSettings>(TopItemSettings.Month,
                                       Configuration.GetSection("TopItem:Month"));
    services.Configure<TopItemSettings>(TopItemSettings.Year,
                                        Configuration.GetSection("TopItem:Year"));

    services.AddRazorPages();
}

下面的代码将显示命名选项:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

所有选项都是命名实例。 IConfigureOptions<TOptions> 实例将被视为面向 Options.DefaultName 实例,即 string.EmptyIConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null 命名选项用于面向所有命名实例,而不是某一特定命名实例。 ConfigureAllPostConfigureAll 使用此约定。

OptionsBuilder API

OptionsBuilder<TOptions> 用于配置 TOptions 实例。 OptionsBuilder 简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName) 调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions 重载仅可通过 OptionsBuilder 获得。

OptionsBuilder选项验证部分中使用。

使用 DI 服务配置选项

在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:

建议将配置委托传递给 Configure,因为创建服务较复杂。 在调用 Configure 时,创建类型等效于框架执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。

选项验证

通过选项验证,可以验证选项值。

请考虑以下 appsettings.json 文件:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

下面的类绑定到 "MyConfig" 配置节,并应用若干 DataAnnotations 规则:

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

下面的代码:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<MyConfigOptions>()
            .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

        services.AddControllersWithViews();
    }

ValidateDataAnnotations 扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。 对于使用 Microsoft.NET.Sdk.Web SDK 的 Web 应用,通过共享框架隐式引用此包。

下面的代码显示配置值或验证错误:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;
           
        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
             msg = $"Key1: {_config.Value.Key1} \n" +
                   $"Key2: {_config.Value.Key2} \n" +
                   $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

下面的代码使用委托应用更复杂的验证规则:

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<MyConfigOptions>()
        .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
        .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

    services.AddControllersWithViews();
}

用于复杂验证的 IValidateOptions

下面的类实现了 IValidateOptions<TOptions>

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions 允许将验证代码移出 StartUp 并将其移入类中。

使用前面的代码,使用以下代码在 Startup.ConfigureServices 中启用验证:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

选项后期配置

使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure 可用于对命名选项进行后期配置:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

使用 PostConfigureAll 对所有配置实例进行后期配置:

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

在启动期间访问选项

IOptions<TOptions>IOptionsMonitor<TOptions> 可用于 Startup.Configure 中,因为在 Configure 方法执行之前已生成服务。

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

不使用 Startup.ConfigureServices 中的 IOptions<TOptions>IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。

ConfigurationExtensions NuGet 包

Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 应用中隐式引用。

选项模式使用类来表示相关设置的组。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:

选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。

查看或下载示例代码如何下载

先决条件

引用 Microsoft.AspNetCore.App 元包或将包引用添加到 Microsoft.Extensions.Options.ConfigurationExtensions 包。

选项接口

IOptionsMonitor<TOptions> 用于检索选项并管理 TOptions 实例的选项通知。 IOptionsMonitor<TOptions> 支持以下方案:

后期配置方案允许你在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。

IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions> 且仅调用适当的接口。

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions> 用于缓存 TOptions 实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。

IOptionsSnapshot<TOptions> 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅通过 IOptionsSnapshot 重新加载配置数据部分。

IOptions<TOptions> 可用于支持选项。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的现有框架和库中继续使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。

常规选项配置

常规选项配置已作为示例 1 在示例应用中进行了演示。

选项类必须为包含公共无参数构造函数的非抽象类。 以下类 MyOptions 具有两种属性:Option1Option2。 设置默认值为可选,但以下示例中的类构造函数设置了 Option1 的默认值。 Option2 具有通过直接初始化属性设置的默认值 (Models/MyOptions.cs):

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

MyOptions 类已通过 Configure 添加到服务容器并绑定到配置:

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

以下页面模型通过 IOptionsMonitor<TOptions> 使用构造函数依赖关系注入来访问设置 (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

示例的 appsettings.json 文件指定 option1option2 的值:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

运行应用时,页面模型的 OnGet 方法返回显示选项类值的字符串:

option1 = value1_from_json, option2 = -1

备注

使用自定义 ConfigurationBuilder 从设置文件加载选项配置时,请确认基路径设置正确:

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

通过 CreateDefaultBuilder 从设置文件加载选项配置时,不需要显式设置基路径。

通过委托配置简单选项

通过委托配置简单选项已作为示例 2 在示例应用中进行了演示。

使用委托设置选项值。 此示例应用使用 MyOptionsWithDelegateConfig 类 (Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

在以下代码中,已向服务容器添加第二个 IConfigureOptions<TOptions> 服务。 它通过 MyOptionsWithDelegateConfig 使用委托来配置绑定:

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

可添加多个配置提供程序。 配置提供程序可从 NuGet 包中获取,并按照注册的顺序应用。 有关详细信息,请参阅 ASP.NET Core 中的配置

每次调用 Configure 都会将 IConfigureOptions<TOptions> 服务添加到服务容器。 在前面的示例中,Option1Option2 的值同时在 appsettings.json 中指定,但 Option1Option2 的值被配置的委托替代。

当启用多个配置服务时,指定的最后一个配置源优于其他源,由其设置配置值。 运行应用时,页面模型的 OnGet 方法返回显示选项类值的字符串:

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

子选项配置

子选项配置已作为示例 3 在示例应用中进行了演示。

应用应创建适用于应用中特定方案组(类)的选项类。 需要配置值的部分应用应仅有权访问其使用的配置值。

将选项绑定到配置时,选项类型中的每个属性都将绑定到窗体 property[:sub-property:] 的配置键。 例如,MyOptions.Option1 属性将绑定到从 appsettings.json 中的 option1 属性读取的键 Option1

在以下代码中,已向服务容器添加第三个 IConfigureOptions<TOptions> 服务。 它将 MySubOptions 绑定到 appsettings.json 文件的部分 subsection

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

GetSection 方法需要 Microsoft.Extensions.Configuration 命名空间。

示例的 appsettings.json 文件定义具有 suboption1suboption2 的键的 subsection 成员:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

MySubOptions 类将属性 SubOption1SubOption2 定义为保留选项值 (Models/MySubOptions.cs):

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

页面模型的 OnGet 方法返回包含选项值的字符串 (Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

运行应用时,OnGet 方法返回显示子选项类值的字符串:

subOption1 = subvalue1_from_json, subOption2 = 200

选项注入

选项注入已作为示例 4 在示例应用中进行了演示。

IOptionsMonitor<TOptions> 注入:

  • 使用 @inject Razor 指令的 Razor 页面或 MVC 视图。
  • 页面或视图模型。

示例应用中的以下示例将 IOptionsMonitor<TOptions> 注入页面模型 (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

示例应用演示如何使用 @inject 指令注入 IOptionsMonitor<MyOptions>

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

当应用运行时,选项值显示在呈现的页面中:

选项值 Option1:value1_from_json 和 Option2:-1 从模型中加载并注入视图。

通过 IOptionsSnapshot 重新加载配置数据

通过 IOptionsSnapshot<TOptions> 重新加载配置数据已作为示例 5 在示例应用中进行了演示。

通过使用 IOptionsSnapshot<TOptions>,针对请求生存期访问和缓存选项时,每个请求都会计算一次选项。

IOptionsMonitorIOptionsSnapshot 之间的区别在于:

  • IOptionsMonitor 是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。
  • IOptionsSnapshot 是一种作用域服务,并在构造 IOptionsSnapshot<T> 对象时提供选项的快照。 选项快照旨在用于暂时性和有作用域的依赖项。

以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs) 后创建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加载配置之前,针对服务器的多个请求返回 appsettings.json 文件提供的常数值。

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

下图显示从 appsettings.json 文件加载的初始 option1option2 值:

snapshot option1 = value1_from_json, snapshot option2 = -1

将 appsettings.json 文件中的值更改为 value1_from_json UPDATED200。 保存 appsettings.json 文件。 刷新浏览器,查看更新的选项值:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

包含 IConfigureNamedOptions 的命名选项支持

包含 IConfigureNamedOptions<TOptions> 的命名选项支持已作为示例 6 在示例应用中进行了演示。

命名选项支持允许应用在命名选项配置之间进行区分。 在示例应用中,命名选项通过 OptionsServiceCollectionExtensions.Configure 进行声明,其调用 ConfigureNamedOptions<TOptions>.Configure 扩展方法。 命名选项区分大小写。

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

示例应用通过 Get (Pages/Index.cshtml.cs) 访问命名选项:

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

运行示例应用,将返回命名选项:

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

从配置中提供从 appsettings.json 文件中加载的 named_options_1 值。 通过以下内容提供 named_options_2 值:

  • 针对 Option1ConfigureServices 中的 named_options_2 委托。
  • MyOptions 类提供的 Option2 的默认值。

使用 ConfigureAll 方法配置所有选项

使用 ConfigureAll 方法配置所有选项实例。 以下代码将针对包含公共值的所有配置实例配置 Option1。 将以下代码手动添加到 Startup.ConfigureServices 方法:

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

添加代码后运行示例应用将产生以下结果:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

备注

所有选项都是命名实例。 现有 IConfigureOptions<TOptions> 实例将被视为面向 Options.DefaultName 实例,即 string.EmptyIConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null 命名选项用于面向所有命名实例而不是某一特定命名实例(ConfigureAllPostConfigureAll 使用此约定)。

OptionsBuilder API

OptionsBuilder<TOptions> 用于配置 TOptions 实例。 OptionsBuilder 简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName) 调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions 重载仅可通过 OptionsBuilder 获得。

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

使用 DI 服务配置选项

在配置选项时,可以通过以下两种方式通过依赖关系注入访问其他服务:

建议将配置委托传递给 Configure,因为创建服务较复杂。 在使用 Configure时,创建你自己的类型等效于框架为你执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。

选项验证

借助选项验证,可以验证已配置的选项。 使用验证方法调用 Validate。如果选项有效,方法返回 true;如果无效,方法返回 false

// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
    .Configure(o => { }) // Configure the options
    .Validate(o => YourValidationShouldReturnTrueIfValid(o), 
        "custom error");

// Consumption
var monitor = services.BuildServiceProvider()
    .GetService<IOptionsMonitor<MyOptions>>();

try
{
    var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e) 
{
   // e.OptionsName returns "optionalOptionsName"
   // e.OptionsType returns typeof(MyOptions)
   // e.Failures returns a list of errors, which would contain 
   //     "custom error"
}

上面的示例将命名选项实例设置为 optionalOptionsName。 默认选项实例为 Options.DefaultName

选项验证在选项实例创建后运行。 系统保证在选项实例首次获得访问时通过验证。

重要

选项验证无法防止在创建选项实例后发生选项修改。 例如,在首次访问选项时按请求创建并验证 IOptionsSnapshot 选项。 对于同一个请求,在后续访问尝试时不会再次验证 IOptionsSnapshot 选项。

Validate 方法接受 Func<TOptions, bool>。 若要完全自定义验证,请实现 IValidateOptions<TOptions>,它支持:

  • 验证多种类型的选项:class ValidateTwo : IValidateOptions<Option1>, IValidationOptions<Option2>
  • 验证取决于其他选项类型:public DependsOnAnotherOptionValidator(IOptionsMonitor<AnotherOption> options)

IValidateOptions 验证:

  • 特定的命名选项实例。
  • 所有选项(如果 namenull 的话)。

通过接口实现返回 ValidateOptionsResult

public interface IValidateOptions<TOptions> where TOptions : class
{
    ValidateOptionsResult Validate(string name, TOptions options);
}

通过调用 OptionsBuilder<TOptions> 上的 ValidateDataAnnotations 方法,可以从 Microsoft.Extensions.Options.DataAnnotations 包中获得基于数据注释的验证。 Microsoft.Extensions.Options.DataAnnotations 包含在 Microsoft.AspNetCore.App 元包中。

using Microsoft.Extensions.DependencyInjection;

private class AnnotatedOptions
{
    [Required]
    public string Required { get; set; }

    [StringLength(5, ErrorMessage = "Too long.")]
    public string StringLength { get; set; }

    [Range(-5, 5, ErrorMessage = "Out of range.")]
    public int IntRange { get; set; }
}

[Fact]
public void CanValidateDataAnnotations()
{
    var services = new ServiceCollection();
    services.AddOptions<AnnotatedOptions>()
        .Configure(o =>
        {
            o.StringLength = "111111";
            o.IntRange = 10;
            o.Custom = "nowhere";
        })
        .ValidateDataAnnotations();

    var sp = services.BuildServiceProvider();

    var error = Assert.Throws<OptionsValidationException>(() => 
        sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().CurrentValue);
    ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1,
        "DataAnnotation validation failed for members Required " +
            "with the error 'The Required field is required.'.",
        "DataAnnotation validation failed for members StringLength " +
            "with the error 'Too long.'.",
        "DataAnnotation validation failed for members IntRange " +
            "with the error 'Out of range.'.");
}

设计团队正在考虑为未来版本提供预先验证(在启动时快速失败)。

选项后期配置

使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure 可用于对命名选项进行后期配置:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

使用 PostConfigureAll 对所有配置实例进行后期配置:

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

在启动期间访问选项

IOptions<TOptions>IOptionsMonitor<TOptions> 可用于 Startup.Configure 中,因为在 Configure 方法执行之前已生成服务。

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

不使用 Startup.ConfigureServices 中的 IOptions<TOptions>IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。

选项模式使用类来表示相关设置的组。 当配置设置由方案隔离到单独的类时,应用遵循两个重要软件工程原则:

选项还提供验证配置数据的机制。 有关详细信息,请参阅选项验证部分。

查看或下载示例代码如何下载

先决条件

引用 Microsoft.AspNetCore.App 元包或将包引用添加到 Microsoft.Extensions.Options.ConfigurationExtensions 包。

选项接口

IOptionsMonitor<TOptions> 用于检索选项并管理 TOptions 实例的选项通知。 IOptionsMonitor<TOptions> 支持以下方案:

后期配置方案允许你在进行所有 IConfigureOptions<TOptions> 配置后设置或更改选项。

IOptionsFactory<TOptions> 负责新建选项实例。 它具有单个 Create 方法。 默认实现采用所有已注册 IConfigureOptions<TOptions>IPostConfigureOptions<TOptions> 并首先运行所有配置,然后才进行后期配置。 它区分 IConfigureNamedOptions<TOptions>IConfigureOptions<TOptions> 且仅调用适当的接口。

IOptionsMonitorCache<TOptions>IOptionsMonitor<TOptions> 用于缓存 TOptions 实例。 IOptionsMonitorCache<TOptions> 可使监视器中的选项实例无效,以便重新计算值 (TryRemove)。 可以通过 TryAdd 手动引入值。 在应按需重新创建所有命名实例时使用 Clear 方法。

IOptionsSnapshot<TOptions> 在每次请求时应重新计算选项的方案中有用。 有关详细信息,请参阅通过 IOptionsSnapshot 重新加载配置数据部分。

IOptions<TOptions> 可用于支持选项。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的现有框架和库中继续使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。

常规选项配置

常规选项配置已作为示例 1 在示例应用中进行了演示。

选项类必须为包含公共无参数构造函数的非抽象类。 以下类 MyOptions 具有两种属性:Option1Option2。 设置默认值为可选,但以下示例中的类构造函数设置了 Option1 的默认值。 Option2 具有通过直接初始化属性设置的默认值 (Models/MyOptions.cs):

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

MyOptions 类已通过 Configure 添加到服务容器并绑定到配置:

// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);

以下页面模型通过 IOptionsMonitor<TOptions> 使用构造函数依赖关系注入来访问设置 (Pages/Index.cshtml.cs):

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";

示例的 appsettings.json 文件指定 option1option2 的值:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

运行应用时,页面模型的 OnGet 方法返回显示选项类值的字符串:

option1 = value1_from_json, option2 = -1

备注

使用自定义 ConfigurationBuilder 从设置文件加载选项配置时,请确认基路径设置正确:

var configBuilder = new ConfigurationBuilder()
   .SetBasePath(Directory.GetCurrentDirectory())
   .AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();

services.Configure<MyOptions>(config);

通过 CreateDefaultBuilder 从设置文件加载选项配置时,不需要显式设置基路径。

通过委托配置简单选项

通过委托配置简单选项已作为示例 2 在示例应用中进行了演示。

使用委托设置选项值。 此示例应用使用 MyOptionsWithDelegateConfig 类 (Models/MyOptionsWithDelegateConfig.cs):

public class MyOptionsWithDelegateConfig
{
    public MyOptionsWithDelegateConfig()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

在以下代码中,已向服务容器添加第二个 IConfigureOptions<TOptions> 服务。 它通过 MyOptionsWithDelegateConfig 使用委托来配置绑定:

// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
    myOptions.Option1 = "value1_configured_by_delegate";
    myOptions.Option2 = 500;
});

Index.cshtml.cs:

private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig = 
    $"delegate_option1 = {delegate_config_option1}, " +
    $"delegate_option2 = {delegate_config_option2}";

可添加多个配置提供程序。 配置提供程序可从 NuGet 包中获取,并按照注册的顺序应用。 有关详细信息,请参阅 ASP.NET Core 中的配置

每次调用 Configure 都会将 IConfigureOptions<TOptions> 服务添加到服务容器。 在前面的示例中,Option1Option2 的值同时在 appsettings.json 中指定,但 Option1Option2 的值被配置的委托替代。

当启用多个配置服务时,指定的最后一个配置源优于其他源,由其设置配置值。 运行应用时,页面模型的 OnGet 方法返回显示选项类值的字符串:

delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500

子选项配置

子选项配置已作为示例 3 在示例应用中进行了演示。

应用应创建适用于应用中特定方案组(类)的选项类。 需要配置值的部分应用应仅有权访问其使用的配置值。

将选项绑定到配置时,选项类型中的每个属性都将绑定到窗体 property[:sub-property:] 的配置键。 例如,MyOptions.Option1 属性将绑定到从 appsettings.json 中的 option1 属性读取的键 Option1

在以下代码中,已向服务容器添加第三个 IConfigureOptions<TOptions> 服务。 它将 MySubOptions 绑定到 appsettings.json 文件的部分 subsection

// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

GetSection 方法需要 Microsoft.Extensions.Configuration 命名空间。

示例的 appsettings.json 文件定义具有 suboption1suboption2 的键的 subsection 成员:

{
  "option1": "value1_from_json",
  "option2": -1,
  "subsection": {
    "suboption1": "subvalue1_from_json",
    "suboption2": 200
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

MySubOptions 类将属性 SubOption1SubOption2 定义为保留选项值 (Models/MySubOptions.cs):

public class MySubOptions
{
    public MySubOptions()
    {
        // Set default values.
        SubOption1 = "value1_from_ctor";
        SubOption2 = 5;
    }
    
    public string SubOption1 { get; set; }
    public int SubOption2 { get; set; }
}

页面模型的 OnGet 方法返回包含选项值的字符串 (Pages/Index.cshtml.cs):

private readonly MySubOptions _subOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";

运行应用时,OnGet 方法返回显示子选项类值的字符串:

subOption1 = subvalue1_from_json, subOption2 = 200

视图模型或通过直接视图注入提供的选项

视图模型或通过直接视图注入提供的选项已作为示例 4 在示例应用中进行了演示。

可在视图模型中或通过将 IOptionsMonitor<TOptions> 直接注入到视图 (Pages/Index.cshtml.cs) 来提供选项:

private readonly MyOptions _options;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #4: Bind options directly to the page
MyOptions = _options;

示例应用演示如何使用 @inject 指令注入 IOptionsMonitor<MyOptions>

@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
    ViewData["Title"] = "Options Sample";
}

<h1>@ViewData["Title"]</h1>

当应用运行时,选项值显示在呈现的页面中:

选项值 Option1:value1_from_json 和 Option2:-1 从模型中加载并注入视图。

通过 IOptionsSnapshot 重新加载配置数据

通过 IOptionsSnapshot<TOptions> 重新加载配置数据已作为示例 5 在示例应用中进行了演示。

IOptionsSnapshot<TOptions> 支持包含最小处理开销的重新加载选项。

针对请求生存期访问和缓存选项时,每个请求只能计算一次选项。

以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs) 后创建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加载配置之前,针对服务器的多个请求返回 appsettings.json 文件提供的常数值。

private readonly MyOptions _snapshotOptions;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions = 
    $"snapshot option1 = {snapshotOption1}, " +
    $"snapshot option2 = {snapshotOption2}";

下图显示从 appsettings.json 文件加载的初始 option1option2 值:

snapshot option1 = value1_from_json, snapshot option2 = -1

将 appsettings.json 文件中的值更改为 value1_from_json UPDATED200。 保存 appsettings.json 文件。 刷新浏览器,查看更新的选项值:

snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200

包含 IConfigureNamedOptions 的命名选项支持

包含 IConfigureNamedOptions<TOptions> 的命名选项支持已作为示例 6 在示例应用中进行了演示。

命名选项支持允许应用在命名选项配置之间进行区分。 在示例应用中,命名选项通过 OptionsServiceCollectionExtensions.Configure 进行声明,其调用 ConfigureNamedOptions<TOptions>.Configure 扩展方法。 命名选项区分大小写。

// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);

// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
    myOptions.Option1 = "named_options_2_value1_from_action";
});

示例应用通过 Get (Pages/Index.cshtml.cs) 访问命名选项:

private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
public IndexModel(
    IOptionsMonitor<MyOptions> optionsAccessor, 
    IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig, 
    IOptionsMonitor<MySubOptions> subOptionsAccessor, 
    IOptionsSnapshot<MyOptions> snapshotOptionsAccessor, 
    IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
    _options = optionsAccessor.CurrentValue;
    _optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
    _subOptions = subOptionsAccessor.CurrentValue;
    _snapshotOptions = snapshotOptionsAccessor.Value;
    _named_options_1 = namedOptionsAccessor.Get("named_options_1");
    _named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
// Example #6: Named options
var named_options_1 = 
    $"named_options_1: option1 = {_named_options_1.Option1}, " +
    $"option2 = {_named_options_1.Option2}";
var named_options_2 = 
    $"named_options_2: option1 = {_named_options_2.Option1}, " +
    $"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";

运行示例应用,将返回命名选项:

named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5

从配置中提供从 appsettings.json 文件中加载的 named_options_1 值。 通过以下内容提供 named_options_2 值:

  • 针对 Option1ConfigureServices 中的 named_options_2 委托。
  • MyOptions 类提供的 Option2 的默认值。

使用 ConfigureAll 方法配置所有选项

使用 ConfigureAll 方法配置所有选项实例。 以下代码将针对包含公共值的所有配置实例配置 Option1。 将以下代码手动添加到 Startup.ConfigureServices 方法:

services.ConfigureAll<MyOptions>(myOptions => 
{
    myOptions.Option1 = "ConfigureAll replacement value";
});

添加代码后运行示例应用将产生以下结果:

named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5

备注

所有选项都是命名实例。 现有 IConfigureOptions<TOptions> 实例将被视为面向 Options.DefaultName 实例,即 string.EmptyIConfigureNamedOptions<TOptions> 还可实现 IConfigureOptions<TOptions>IOptionsFactory<TOptions> 的默认实现具有适当地使用每个实例的逻辑。 null 命名选项用于面向所有命名实例而不是某一特定命名实例(ConfigureAllPostConfigureAll 使用此约定)。

OptionsBuilder API

OptionsBuilder<TOptions> 用于配置 TOptions 实例。 OptionsBuilder 简化了创建命名选项的过程,因为它只是初始 AddOptions<TOptions>(string optionsName) 调用的单个参数,而不会出现在所有后续调用中。 选项验证和接受服务依赖关系的 ConfigureOptions 重载仅可通过 OptionsBuilder 获得。

// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");

services.AddOptions<MyOptions>("optionalName")
    .Configure(o => o.Property = "named");

使用 DI 服务配置选项

在配置选项时,可以通过以下两种方式通过依赖关系注入访问其他服务:

建议将配置委托传递给 Configure,因为创建服务较复杂。 在使用 Configure时,创建你自己的类型等效于框架为你执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服务类型的构造函数。

选项后期配置

使用 IPostConfigureOptions<TOptions> 设置后期配置。 进行所有 IConfigureOptions<TOptions> 配置后运行后期配置:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure 可用于对命名选项进行后期配置:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

使用 PostConfigureAll 对所有配置实例进行后期配置:

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

在启动期间访问选项

IOptions<TOptions>IOptionsMonitor<TOptions> 可用于 Startup.Configure 中,因为在 Configure 方法执行之前已生成服务。

public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

不使用 Startup.ConfigureServices 中的 IOptions<TOptions>IOptionsMonitor<TOptions>。 由于服务注册的顺序,可能存在不一致的选项状态。

其他资源