Model možností v .NET
Vzor možností používá třídy k zajištění přístupu silného typu ke skupinám souvisejících nastavení. Pokud jsou nastavení konfigurace izolované podle scénáře do samostatných tříd, aplikace dodržuje dva důležité principy softwarového inženýrství:
- Princip oddělení rozhraní (ISP) nebo Zapouzdření: Scénáře (třídy), které závisí na nastavení konfigurace, závisí pouze na nastavení konfigurace, které používají.
- Oddělení obav: Nastavení pro různé části aplikace nejsou závislá nebo vzájemně svázaná.
Možnosti také poskytují mechanismus pro ověření konfiguračních dat. Další informace najdete v části Možnosti ověření .
Vytvoření vazby hierarchické konfigurace
Upřednostňovaným způsobem čtení souvisejících hodnot konfigurace je použití vzoru možností. Vzor možností je možný prostřednictvím IOptions<TOptions> rozhraní, kde je parametr TOptions
obecného typu omezen na class
. Později ho IOptions<TOptions>
můžete poskytnout prostřednictvím injektáže závislostí. Další informace naleznete v tématu Injektáž závislostí v .NET.
Pokud chcete například číst zvýrazněné konfigurační hodnoty ze souboru appsettings.json :
{
"SecretKey": "Secret key value",
"TransientFaultHandlingOptions": {
"Enabled": true,
"AutoRetryDelay": "00:00:07"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Vytvořte následující třídu TransientFaultHandlingOptions
:
public sealed class TransientFaultHandlingOptions
{
public bool Enabled { get; set; }
public TimeSpan AutoRetryDelay { get; set; }
}
Při použití vzoru možností třída options:
- Nesmí být abstraktní s veřejným konstruktorem bez parametrů.
- Obsahují veřejné vlastnosti pro čtení i zápis pro vazbu (pole nejsou svázaná).
Následující kód je součástí souboru Program.cs C# a:
- Zavolá metodu ConfigurationBinder.Bind pro svázání třídy
TransientFaultHandlingOptions
s oddílem"TransientFaultHandlingOptions"
. - Zobrazí konfigurační data pole .
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ConsoleJson.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Configuration.Sources.Clear();
IHostEnvironment env = builder.Environment;
builder.Configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
TransientFaultHandlingOptions options = new();
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
.Bind(options);
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
// <Output>
// Sample output:
V předchozím kódu má konfigurační soubor JSON svůj "TransientFaultHandlingOptions"
oddíl svázaný s TransientFaultHandlingOptions
instancí. To hydratuje vlastnosti objektů jazyka C# s odpovídajícími hodnotami z konfigurace.
ConfigurationBinder.Get<T>
naváže a vrátí určený typ. ConfigurationBinder.Get<T>
může být pohodlnější než použití ConfigurationBinder.Bind
. Následující kód ukazuje, jak používat ConfigurationBinder.Get<T>
s třídou TransientFaultHandlingOptions
:
var options =
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
.Get<TransientFaultHandlingOptions>();
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
V předchozím kódu ConfigurationBinder.Get<T>
se používá k získání instance objektu TransientFaultHandlingOptions
s jeho hodnotami vlastností naplněnými z podkladové konfigurace.
Důležité
Třída ConfigurationBinder zveřejňuje několik rozhraní API, například .Bind(object instance)
a .Get<T>()
které nejsou omezeny na .class
Při použití některého z rozhraní Options musíte dodržovat výše uvedené možnosti omezení třídy.
Alternativním přístupem při použití vzoru možností je vytvořit vazbu oddílu "TransientFaultHandlingOptions"
a přidat ho do kontejneru služby injektáže závislostí. V následujícím kódu se TransientFaultHandlingOptions
přidá do kontejneru služby s příkazem Configure a sváže se s konfigurací:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.Configure<TransientFaultHandlingOptions>(
builder.Configuration.GetSection(
key: nameof(TransientFaultHandlingOptions)));
V builder
předchozím příkladu je instance HostApplicationBuilder.
Tip
Parametr key
je název oddílu konfigurace, který se má vyhledat. Nemusí odpovídat názvu typu, který ho představuje. Můžete mít například pojmenovaný "FaultHandling"
oddíl, který může být reprezentován TransientFaultHandlingOptions
třídou. V tomto případě byste funkci předali "FaultHandling"
GetSection . Operátor nameof
se používá jako pohodlí, když pojmenovaný oddíl odpovídá typu, který odpovídá.
Pomocí předchozího kódu přečte následující kód možnosti pozice:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ExampleService(IOptions<TransientFaultHandlingOptions> options)
{
private readonly TransientFaultHandlingOptions _options = options.Value;
public void DisplayValues()
{
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
}
}
V předchozím kódu se změny konfiguračního souboru JSON po spuštění aplikace nepřečtou. Pokud chcete číst změny po spuštění aplikace, použijte IOptionsSnapshot nebo IOptionsMonitor k monitorování změn, jak k nim dojde, a odpovídajícím způsobem reagovat.
Rozhraní možností
- Nepodporuje:
- Čtení konfiguračních dat po spuštění aplikace
- Pojmenované možnosti
- Je registrován jako Singleton a může být vložen do jakékoli životnosti služby.
- Je užitečné ve scénářích, kdy by se měly přepočítat možnosti pro každé rozlišení injektáže v rozsahu nebo přechodné životnosti. Další informace najdete v tématu Použití IOptionsSnapshot ke čtení aktualizovaných dat.
- Je registrován jako Obor, a proto nelze vložit do služby Singleton.
- Podporuje pojmenované možnosti.
- Slouží k načtení možností a správě oznámení možností pro
TOptions
instance. - Je registrován jako Singleton a může být vložen do jakékoli životnosti služby.
- Podporuje:
- Změna oznámení
- Pojmenované možnosti
- Znovu načístelná konfigurace
- Zrušení selektivních možností (IOptionsMonitorCache<TOptions>)
IOptionsFactory<TOptions> zodpovídá za vytváření nových instancí možností. Má jednu Create metodu. Výchozí implementace vezme všechny registrované IConfigureOptions<TOptions> a IPostConfigureOptions<TOptions> nejprve spustí všechny konfigurace, za kterými následuje po konfiguraci. Rozlišuje mezi IConfigureNamedOptions<TOptions> rozhraním a IConfigureOptions<TOptions> volá pouze příslušné rozhraní.
IOptionsMonitorCache<TOptions> používá IOptionsMonitor<TOptions> se k ukládání instancí do mezipaměti TOptions
. Instance IOptionsMonitorCache<TOptions> možností v monitorování zneplatní, aby byla hodnota přepočítána (TryRemove). Hodnoty lze ručně zavést s TryAdd. Metoda Clear se používá, když se všechny pojmenované instance mají znovu vytvořit na vyžádání.
IOptionsChangeTokenSource<TOptions> slouží k načtení IChangeToken sledování změn v podkladové TOptions
instanci. Další informace o primitivech tokenů změn najdete v tématu Oznámení o změnách.
Výhody rozhraní možností
Použití obecného typu obálky umožňuje oddělit životnost možnosti od kontejneru injektáže závislostí (DI). Rozhraní IOptions<TOptions>.Value poskytuje vrstvu abstrakce, včetně obecných omezení, pro váš typ možností. Přináší to následující výhody:
- Vyhodnocení
T
instance konfigurace se odloží na přístup IOptions<TOptions>.Valuek instanci , nikoli při vkládání. To je důležité, protože můžete využívatT
možnost z různých míst a zvolit sémantiku života beze změny čehokoli oT
. - Při registraci možností typu
T
nemusíte typ explicitně registrovatT
. To je pohodlí při vytváření knihovny s jednoduchými výchozími nastaveními a nechcete vynutit volajícímu registraci možností do kontejneru DI s určitou životností. - Z pohledu rozhraní API umožňuje omezení typu
T
(v tomto případěT
je omezena na typ odkazu).
Čtení aktualizovaných dat pomocí IOptionsSnapshot
Při použití IOptionsSnapshot<TOptions>se možnosti použijí jednou za požadavek při přístupu a po celou dobu platnosti požadavku se použijí v mezipaměti. Změny konfigurace se čtou po spuštění aplikace při použití zprostředkovatelů konfigurace, kteří podporují čtení aktualizovaných konfiguračních hodnot.
Rozdíl mezi IOptionsMonitor
a IOptionsSnapshot
je následující:
IOptionsMonitor
je jednoúčelová služba , která načítá aktuální hodnoty možností kdykoli, což je zvlášť užitečné v jednoúčelových závislostech.IOptionsSnapshot
je vymezená služba a poskytuje snímek možností v době vytváření objektuIOptionsSnapshot<T>
. Snímky možností jsou navržené pro použití s přechodnými a vymezenými závislostmi.
Následující kód používá IOptionsSnapshot<TOptions>.
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ScopedService(IOptionsSnapshot<TransientFaultHandlingOptions> options)
{
private readonly TransientFaultHandlingOptions _options = options.Value;
public void DisplayValues()
{
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
}
}
Následující kód zaregistruje instanci konfigurace, která TransientFaultHandlingOptions
se sváže s:
builder.Services
.Configure<TransientFaultHandlingOptions>(
configurationRoot.GetSection(
nameof(TransientFaultHandlingOptions)));
V předchozím kódu Configure<TOptions>
se metoda používá k registraci instance konfigurace, která TOptions
bude svázána s, a aktualizuje možnosti při změně konfigurace.
IOptionsMonitor
Typ IOptionsMonitor
podporuje oznámení o změnách a umožňuje scénáře, ve kterých může vaše aplikace dynamicky reagovat na změny zdroje konfigurace. To je užitečné, když potřebujete reagovat na změny konfiguračních dat po spuštění aplikace. Oznámení o změnách jsou podporována pouze pro poskytovatele konfigurace založené na souborech, jako jsou například následující:
- Microsoft.Extensions.Configuration.Ini
- Microsoft.Extensions.Configuration.Json
- Microsoft.Extensions.Configuration.KeyPerFile
- Microsoft.Extensions.Configuration.UserSecrets
- Microsoft.Extensions.Configuration.Xml
Pokud chcete použít monitorování možností, objekty možností se konfigurují stejným způsobem jako v části konfigurace.
builder.Services
.Configure<TransientFaultHandlingOptions>(
configurationRoot.GetSection(
nameof(TransientFaultHandlingOptions)));
Následující příklad používá IOptionsMonitor<TOptions>:
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class MonitorService(IOptionsMonitor<TransientFaultHandlingOptions> monitor)
{
public void DisplayValues()
{
TransientFaultHandlingOptions options = monitor.CurrentValue;
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
}
}
V předchozím kódu se změny konfiguračního souboru JSON po spuštění aplikace načtou.
Tip
Některé systémy souborů, jako jsou kontejnery Dockeru a sdílené síťové složky, nemusí spolehlivě odesílat oznámení o změnách. Při použití IOptionsMonitor<TOptions> rozhraní v těchto prostředích nastavte DOTNET_USE_POLLING_FILE_WATCHER
proměnnou prostředí na 1
systém souborů nebo true
dotazujte systém souborů na změny. Interval, ve kterém se změny dotazují, je každých čtyři sekundy a není konfigurovatelný.
Další informace o kontejnerech Dockeru najdete v tématu Kontejnerizace aplikace .NET.
Podpora pojmenovaných možností pomocí IConfigureNamedOptions
Pojmenované možnosti:
- Jsou užitečné, když se několik oddílů konfigurace sváže se stejnými vlastnostmi.
- Rozlišují se malá a velká písmena.
Zvažte následující appsettings.json soubor:
{
"Features": {
"Personalize": {
"Enabled": true,
"ApiKey": "aGEgaGEgeW91IHRob3VnaHQgdGhhdCB3YXMgcmVhbGx5IHNvbWV0aGluZw=="
},
"WeatherStation": {
"Enabled": true,
"ApiKey": "QXJlIHlvdSBhdHRlbXB0aW5nIHRvIGhhY2sgdXM/"
}
}
}
Místo vytvoření dvou tříd pro vytvoření vazby Features:Personalize
a Features:WeatherStation
pro každou část se používá následující třída:
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; }
}
Následující kód nakonfiguruje pojmenované možnosti:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Omitted for brevity...
builder.Services.Configure<Features>(
Features.Personalize,
builder.Configuration.GetSection("Features:Personalize"));
builder.Services.Configure<Features>(
Features.WeatherStation,
builder.Configuration.GetSection("Features:WeatherStation"));
Následující kód zobrazí pojmenované možnosti:
public sealed class Service
{
private readonly Features _personalizeFeature;
private readonly Features _weatherStationFeature;
public Service(IOptionsSnapshot<Features> namedOptionsAccessor)
{
_personalizeFeature = namedOptionsAccessor.Get(Features.Personalize);
_weatherStationFeature = namedOptionsAccessor.Get(Features.WeatherStation);
}
}
Všechny možnosti jsou pojmenované instance. IConfigureOptions<TOptions> instance jsou považovány za cíl instance Options.DefaultName
, což je string.Empty
. IConfigureNamedOptions<TOptions> implementuje IConfigureOptions<TOptions>také . Výchozí implementace má logiku IOptionsFactory<TOptions> , která se má použít odpovídajícím způsobem. Pojmenovaná null
možnost se používá k cílení na všechny pojmenované instance místo na konkrétní pojmenovanou instanci. ConfigureAll a PostConfigureAll používat tuto konvenci.
OptionsBuilder API
OptionsBuilder<TOptions> slouží ke konfiguraci TOptions
instancí. OptionsBuilder
zjednodušuje vytváření pojmenovaných možností, protože se jedná pouze o jediný parametr počátečního AddOptions<TOptions>(string optionsName)
volání místo toho, aby se zobrazoval ve všech následných voláních. Možnosti ověřování a ConfigureOptions
přetížení, která přijímají závislosti služby, jsou k dispozici pouze prostřednictvím OptionsBuilder
.
OptionsBuilder
se používá v části Ověřování možností.
Konfigurace možností pomocí služeb DI
Při konfiguraci možností můžete použít injektáž závislostí pro přístup k registrovaným službám a použít je ke konfiguraci možností. To je užitečné, když potřebujete přístup ke službám pro konfiguraci možností. Ke službám je možné přistupovat z DI a současně konfigurovat možnosti dvěma způsoby:
Předejte delegáta konfigurace pro konfiguraci v možnostech TOptions> nástroje OptionsBuilder<.
OptionsBuilder<TOptions>
poskytuje přetížení konfigurace , které umožňují použití až pěti služeb ke konfiguraci možností:builder.Services .AddOptions<MyOptions>("optionalName") .Configure<ExampleService, ScopedService, MonitorService>( (options, es, ss, ms) => options.Property = DoSomethingWith(es, ss, ms));
Vytvořte typ, který implementuje IConfigureOptions<TOptions> nebo IConfigureNamedOptions<TOptions> zaregistruje typ jako službu.
Doporučuje se předat delegáta konfigurace ke konfiguraci, protože vytvoření služby je složitější. Vytvoření typu odpovídá tomu, co architektura dělá při volání Konfigurace. Volání Konfigurace registruje přechodný obecný IConfigureNamedOptions<TOptions>, který má konstruktor, který přijímá obecné typy služby zadané.
Ověřování možností
Ověření možností umožňuje ověření hodnot možností.
Zvažte následující appsettings.json soubor:
{
"MyCustomSettingsSection": {
"SiteTitle": "Amazing docs from Awesome people!",
"Scale": 10,
"VerbosityLevel": 32
}
}
Následující třída vytvoří vazbu na "MyCustomSettingsSection"
oddíl konfigurace a použije několik DataAnnotations
pravidel:
using System.ComponentModel.DataAnnotations;
namespace ConsoleJson.Example;
public sealed class SettingsOptions
{
public const string ConfigurationSectionName = "MyCustomSettingsSection";
[Required]
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public required string SiteTitle { get; set; }
[Required]
[Range(0, 1_000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public required int Scale { get; set; }
[Required]
public required int VerbosityLevel { get; set; }
}
V předchozí SettingsOptions
třídě ConfigurationSectionName
obsahuje vlastnost název oddílu konfigurace, ke kterému se má vytvořit vazba. V tomto scénáři objekt options poskytuje název jeho konfiguračního oddílu.
Tip
Název oddílu konfigurace je nezávislý na objektu konfigurace, ke kterému je vázán. Jinými slovy, oddíl konfigurace pojmenovaný "FooBarOptions"
může být vázán na objekt možností s názvem ZedOptions
. I když může být běžné je pojmenovat stejně, není to nutné a může ve skutečnosti způsobit konflikty názvů.
Následující kód:
- Volání AddOptions pro získání OptionsBuilder<TOptions> , která je vázána na
SettingsOptions
třídu. - Volání ValidateDataAnnotations pro povolení ověřování pomocí
DataAnnotations
.
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations();
Metoda ValidateDataAnnotations
rozšíření je definována v balíčku NuGet Microsoft.Extensions.Options.DataAnnotations .
Následující kód zobrazí konfigurační hodnoty nebo hlásí chyby ověření:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
public sealed class ValidationService
{
private readonly ILogger<ValidationService> _logger;
private readonly IOptions<SettingsOptions> _config;
public ValidationService(
ILogger<ValidationService> logger,
IOptions<SettingsOptions> config)
{
_config = config;
_logger = logger;
try
{
SettingsOptions options = _config.Value;
}
catch (OptionsValidationException ex)
{
foreach (string failure in ex.Failures)
{
_logger.LogError("Validation error: {FailureMessage}", failure);
}
}
}
}
Následující kód používá složitější ověřovací pravidlo pomocí delegáta:
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Scale != 0)
{
return config.VerbosityLevel > config.Scale;
}
return true;
}, "VerbosityLevel must be > than Scale.");
Ověření probíhá za běhu, ale můžete ho nakonfigurovat tak, aby k němu došlo při spuštění, a to tak, že místo toho zřetězíte volání ValidateOnStart
na:
builder.Services
.AddOptions<SettingsOptions>()
.Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Scale != 0)
{
return config.VerbosityLevel > config.Scale;
}
return true;
}, "VerbosityLevel must be > than Scale.")
.ValidateOnStart();
Počínaje rozhraním .NET 8 můžete použít alternativní rozhraní API, AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String)které umožňuje ověřování při spuštění pro konkrétní typ možností:
builder.Services
.AddOptionsWithValidateOnStart<SettingsOptions>()
.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
pro komplexní ověřování
Následující třída implementuje IValidateOptions<TOptions>:
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace ConsoleJson.Example;
sealed partial class ValidateSettingsOptions(
IConfiguration config)
: IValidateOptions<SettingsOptions>
{
public SettingsOptions? Settings { get; private set; } =
config.GetSection(SettingsOptions.ConfigurationSectionName)
.Get<SettingsOptions>();
public ValidateOptionsResult Validate(string? name, SettingsOptions options)
{
StringBuilder? failure = null;
if (!ValidationRegex().IsMatch(options.SiteTitle))
{
(failure ??= new()).AppendLine($"{options.SiteTitle} doesn't match RegEx");
}
if (options.Scale is < 0 or > 1_000)
{
(failure ??= new()).AppendLine($"{options.Scale} isn't within Range 0 - 1000");
}
if (Settings is { Scale: 0 } && Settings.VerbosityLevel <= Settings.Scale)
{
(failure ??= new()).AppendLine("VerbosityLevel must be > than Scale.");
}
return failure is not null
? ValidateOptionsResult.Fail(failure.ToString())
: ValidateOptionsResult.Success;
}
[GeneratedRegex("^[a-zA-Z''-'\\s]{1,40}$")]
private static partial Regex ValidationRegex();
}
IValidateOptions
umožňuje přesun ověřovacího kódu do třídy.
Poznámka:
Tento ukázkový kód spoléhá na balíček NuGet Microsoft.Extensions.Configuration.Json .
Při konfiguraci služeb s následujícím kódem se povolí ověření pomocí předchozího kódu:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Omitted for brevity...
builder.Services.Configure<SettingsOptions>(
builder.Configuration.GetSection(
SettingsOptions.ConfigurationSectionName));
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton
<IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());
Možnosti po konfiguraci
Nastavte po konfiguraci pomocí parametru IPostConfigureOptions<TOptions>. Po dokončení konfigurace se spustí všechna IConfigureOptions<TOptions> konfigurace a může být užitečná ve scénářích, kdy potřebujete přepsat konfiguraci:
builder.Services.PostConfigure<CustomOptions>(customOptions =>
{
customOptions.Option1 = "post_configured_option1_value";
});
PostConfigure je k dispozici pro pokonfigurované pojmenované možnosti:
builder.Services.PostConfigure<CustomOptions>("named_options_1", customOptions =>
{
customOptions.Option1 = "post_configured_option1_value";
});
Slouží PostConfigureAll k následné konfiguraci všech instancí konfigurace:
builder.Services.PostConfigureAll<CustomOptions>(customOptions =>
{
customOptions.Option1 = "post_configured_option1_value";
});