选项模式 您所在的位置:网站首页 WPF5G 选项模式

选项模式

2023-05-15 04:29| 来源: 网络整理| 查看: 265

.NET 中的选项模式 项目 03/18/2023

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

接口分隔原则 (ISP) 或封装:依赖于配置设置的方案(类)仅依赖于其使用的配置设置。 关注点分离:应用的不同部件的设置不彼此依赖或相互耦合。

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

绑定分层配置

读取相关配置值的首选方法是使用选项模式。 选项模式可以通过 IOptions 接口实现,其中泛型类型参数 TOptions 被约束为 class。 以后可以通过依赖关系注入来提供 IOptions。 有关详细信息,请参阅 .NET 中的依赖关系注入。

例如,从 appsettings.json 文件中读取突出显示的配置值:

{ "SecretKey": "Secret key value", "TransientFaultHandlingOptions": { "Enabled": true, "AutoRetryDelay": "00:00:07" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } }

创建以下 TransientFaultHandlingOptions 类:

public sealed class TransientFaultHandlingOptions { public bool Enabled { get; set; } public TimeSpan AutoRetryDelay { get; set; } }

在使用选项模式时,选项类:

必须是非抽象类,有一个公共无参数构造函数 包含要绑定的公共读写属性(不绑定字段)

以下代码是 Program.cs C# 文件的一部分,并且:

调用 ConfigurationBinder.Bind 将 类绑定到 "TransientFaultHandlingOptions" 部分。 显示配置数据。 using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using ConsoleJson.Example; using IHost host = Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, configuration) => { configuration.Sources.Clear(); IHostEnvironment env = hostingContext.HostingEnvironment; configuration .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true); IConfigurationRoot configurationRoot = configuration.Build(); TransientFaultHandlingOptions options = new(); configurationRoot.GetSection(nameof(TransientFaultHandlingOptions)) .Bind(options); Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}"); Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}"); }) .Build(); // Application code should start here. await host.RunAsync();

在前面的代码中,JSON 配置文件的 "TransientFaultHandlingOptions" 节绑定到 实例 TransientFaultHandlingOptions 。 这会将 C# 对象属性与配置中的相应值水合在一起。

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

IConfigurationRoot configurationRoot = configuration.Build(); var options = configurationRoot.GetSection(nameof(TransientFaultHandlingOptions)) .Get(); Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}"); Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

在前面的代码中, ConfigurationBinder.Get 用于获取 对象的实例, TransientFaultHandlingOptions 其属性值是从基础配置填充的。

重要

ConfigurationBinder 类公开了几个 API,例如不被约束为 class 的 .Bind(object instance) 和 .Get()。ConfigurationBinder 使用任何一个选项接口时,都必须遵守前面提到的选项类约束。

使用选项模式时的另一种方法是绑定 "TransientFaultHandlingOptions" 部分,并将其添加到"TransientFaultHandlingOptions"中。 在以下代码中,TransientFaultHandlingOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

services.Configure( configurationRoot.GetSection( key: nameof(TransientFaultHandlingOptions)));

要访问 services 和 configurationRoot 对象,必须使用 ConfigureServices 方法 - IConfiguration 作为 HostBuilderContext.Configuration 属性提供。

Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { var configurationRoot = context.Configuration; services.Configure( configurationRoot.GetSection(nameof(TransientFaultHandlingOptions))); });

提示

key 参数是要搜索的配置部分的名称。 它不必与代表它的类型名称相匹配。 例如,你可以有一个名为 "FaultHandling" 的部分,该部分可以由 TransientFaultHandlingOptions 类来表示。 在这种情况下,可以改为将 "FaultHandling" 传递到 GetSection 函数。 在命名部分与其对应的类型相匹配时,为了方便,使用 nameof 运算符。

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

using Microsoft.Extensions.Options; namespace ConsoleJson.Example; public sealed class ExampleService { private readonly TransientFaultHandlingOptions _options; public ExampleService(IOptions options) => _options = options.Value; public void DisplayValues() { Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}"); Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}"); } }

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要在应用启动后读取更改,请使用 IOptionsSnapshot 或 IOptionsMonitor 监视发生更改,并做出相应的反应。

选项接口

IOptions:

不支持: 在应用启动后读取配置数据。 命名选项 注册为单一实例且可以注入到任何服务生存期。

IOptionsSnapshot:

对于在一定范围内或暂时生存期中应对每个注入解析重新计算选项的场景,它非常有用。 有关详细信息,请参阅使用 IOptionsSnapshot 读取已更新的数据。 注册为范围内,因此无法注入到单一实例服务。 支持命名选项

IOptionsMonitor:

用于检索选项并管理 TOptions 实例的选项通知。 注册为单一实例且可以注入到任何服务生存期。 支持: 更改通知 命名选项 可重载配置 选择性选项失效 (IOptionsMonitorCache)

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

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

IOptionsChangeTokenSource 用于提取 IChangeToken 跟踪对基础 TOptions 实例的更改的 。 有关更改令牌基元的详细信息,请参阅 更改通知。

选项接口优点

使用泛型包装器类型,可以将选项的生存期从 DI 容器中解耦出来。 IOptions.Value 接口对选项类型提供了一个抽象层,包括泛型约束。 这提供了以下好处:

T 配置实例的评估推迟到在访问 IOptions.Value 时进行,而不是在注入时进行。 这一点很重要,因为你可以从各种不同的位置使用 T 选项,并选择生存期语义,而无需更改关于 T 的任何内容。 注册 T 类型的选项时,不需要显式注册 T 类型。 如果你使用简单的默认值创建库,并且不想强制调用方将选项注册到具有特定生存期的 DI 容器中,这可以带来很多便利。 从 API 的角度来看,它允许对类型 T 进行约束(在本例中,T 被约束为引用类型)。 使用 IOptionsSnapshot 读取已更新的数据

当你使用 IOptionsSnapshot 时,在访问每个请求时会计算一次选项,并在请求的生存期内缓存这些选项。 当使用支持读取已更新的配置值的配置提供程序时,将在应用启动后读取对配置所做的更改。

IOptionsMonitor 和 IOptionsSnapshot 之间的区别在于:

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

以下代码使用 IOptionsSnapshot。

using Microsoft.Extensions.Options; namespace ConsoleJson.Example; public sealed class ScopedService { private readonly TransientFaultHandlingOptions _options; public ScopedService(IOptionsSnapshot options) => _options = options.Value; public void DisplayValues() { Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}"); Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}"); } }

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

services.Configure( configurationRoot.GetSection( nameof(TransientFaultHandlingOptions)));

在前面的代码中 Configure , 方法用于注册将绑定到的配置实例 TOptions ,并在配置更改时更新选项。

IOptionsMonitor

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

services.Configure( configurationRoot.GetSection( nameof(TransientFaultHandlingOptions)));

下面的示例使用 IOptionsMonitor:

using Microsoft.Extensions.Options; namespace ConsoleJson.Example; public sealed class MonitorService { private readonly IOptionsMonitor _monitor; public MonitorService(IOptionsMonitor monitor) => _monitor = monitor; public void DisplayValues() { TransientFaultHandlingOptions options = _monitor.CurrentValue; Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}"); Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}"); } }

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

提示

某些文件系统(例如 Docker 容器和网络共享)可能无法可靠地发送更改通知。 在这些环境中使用 IOptionsMonitor 接口时,请将 DOTNET_USE_POLLING_FILE_WATCHER 环境变量设置为 1 或 true,以便轮询文件系统的更改。 轮询更改的时间间隔是四秒,此间隔不可配置。

有关 Docker 容器的详细信息,请参阅容器化 .NET 应用。

命名选项支持使用 IConfigureNamedOptions

命名选项:

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

请考虑使用以下 appsettings.json 文件:

{ "Features": { "Personalize": { "Enabled": true, "ApiKey": "aGEgaGEgeW91IHRob3VnaHQgdGhhdCB3YXMgcmVhbGx5IHNvbWV0aGluZw==" }, "WeatherStation": { "Enabled": true, "ApiKey": "QXJlIHlvdSBhdHRlbXB0aW5nIHRvIGhhY2sgdXM/" } } }

下面的类用于每个节,而不是创建两个类来绑定 Features:Personalize 和 Features:WeatherStation:

public class Features { public const string Personalize = nameof(Personalize); public const string WeatherStation = nameof(WeatherStation); public bool Enabled { get; set; } public string ApiKey { get; set; } }

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

ConfigureServices(services => { services.Configure( Features.Personalize, Configuration.GetSection("Features:Personalize")); services.Configure( Features.WeatherStation, Configuration.GetSection("Features:WeatherStation")); });

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

public class Service { private readonly Features _personalizeFeature; private readonly Features _weatherStationFeature; public Service(IOptionsSnapshot namedOptionsAccessor) { _personalizeFeature = namedOptionsAccessor.Get(Features.Personalize); _weatherStationFeature = namedOptionsAccessor.Get(Features.WeatherStation); } }

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

OptionsBuilder API

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

OptionsBuilder 在选项验证部分中使用。

使用 DI 服务配置选项

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

将配置委托传递给 OptionsBuilderTOptions 上的 Configure。 OptionsBuilder 提供 OptionsBuilder 的重载,该重载允许使用最多五个服务来配置选项:

services.AddOptions("optionalName") .Configure( (options, es, ss, ms) => options.Property = DoSomethingWith(es, ss, ms));

创建实现 IConfigureOptions 或 IConfigureNamedOptions 的类型,并将该类型注册为服务。

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

选项验证

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

请考虑使用以下 appsettings.json 文件:

{ "MyCustomSettingsSection": { "SiteTitle": "Amazing docs from Awesome people!", "Scale": 10, "VerbosityLevel": 32 } }

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

using System.ComponentModel.DataAnnotations; namespace ConsoleJson.Example; public sealed class SettingsOptions { public const string ConfigurationSectionName = "MyCustomSettingsSection"; [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")] public required string SiteTitle { get; set; } = null!; [Range(0, 1000, ErrorMessage = "Value for {0} must be between {1} and {2}.")] public required int Scale { get; set; } public required int VerbosityLevel { get; set; } }

在前面的 SettingsOptions 类中,ConfigurationSectionName 属性包含要绑定到的配置部分的名称。 在此方案中,选项对象提供其配置部分的名称。

提示

配置部分名称独立于所绑定到的配置对象。 换句话说,名为 "FooBarOptions" 的配置部分可以绑定到名为 ZedOptions 的选项对象。 虽然为二者提供相同的名称很常见,但这并不是必要的,且可能会导致名称冲突。

下面的代码:

调用 AddOptions 以获取绑定到 SettingsOptions 类的 AddOptions。 调用 ValidateDataAnnotations 以使用 DataAnnotations 启用验证。 services.AddOptions() .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName)) .ValidateDataAnnotations();

ValidateDataAnnotations 扩展方法在 Microsoft.Extensions.Options.DataAnnotations NuGet 包中定义。

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

using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace ConsoleJson.Example; public sealed class ValidationService { private readonly ILogger _logger; private readonly IOptions _config; public ValidationService( ILogger logger, IOptions config) { _config = config; _logger = logger; try { SettingsOptions options = _config.Value; } catch (OptionsValidationException ex) { foreach (string failure in ex.Failures) { _logger.LogError(failure); } } } }

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

services.AddOptions() .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName)) .ValidateDataAnnotations() .Validate(config => { if (config.Scale != 0) { return config.VerbosityLevel > config.Scale; } return true; }, "VerbosityLevel must be > than Scale."); 用于复杂验证的 IValidateOptions

下面的类实现了 IValidateOptions:

using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; namespace ConsoleJson.Example; sealed partial class ValidateSettingsOptions : IValidateOptions { public SettingsOptions? _settings { get; private set; } public ValidateSettingsOptions(IConfiguration config) => _settings = config.GetSection(SettingsOptions.ConfigurationSectionName) .Get(); public ValidateOptionsResult Validate(string? name, SettingsOptions options) { StringBuilder failure = new(); Regex validationRegex = ValidationRegex(); Match match = validationRegex.Match(options.SiteTitle); if (string.IsNullOrEmpty(match.Value)) { failure.AppendLine($"{options.SiteTitle} doesn't match RegEx"); } if (options.Scale is < 0 or > 1_000) { failure.AppendLine($"{options.Scale} isn't within Range 0 - 1000"); } if (_settings is { Scale: 0 } && _settings.VerbosityLevel 0 ? ValidateOptionsResult.Fail(failure.ToString()) : ValidateOptionsResult.Success; } [GeneratedRegex("^[a-zA-Z''-'\\s]{1,40}$")] private static partial Regex ValidationRegex(); }

IValidateOptions 允许将验证代码移入类中。

注意

此示例代码依赖于 Microsoft.Extensions.Configuration.Json NuGet 包。

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

services.Configure( Configuration.GetSection( SettingsOptions.ConfigurationSectionName)); services.TryAddEnumerable( ServiceDescriptor.Singleton ()); 选项后期配置

使用 IPostConfigureOptions 设置后期配置。 后期配置在所有 IConfigureOptions 配置发生后运行,在需要重写配置的场景中非常有用:

services.PostConfigure(customOptions => { customOptions.Option1 = "post_configured_option1_value"; });

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

services.PostConfigure("named_options_1", customOptions => { customOptions.Option1 = "post_configured_option1_value"; });

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

services.PostConfigureAll(customOptions => { customOptions.Option1 = "post_configured_option1_value"; }); 另请参阅 .NET 中的配置 面向 .NET 库创建者的选项模式指南


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有