Het optiespatroon maakt gebruik van klassen om sterk getypte toegang te bieden tot groepen gerelateerde instellingen. Wanneer configuratie-instellingen worden geïsoleerd door scenario's in afzonderlijke klassen, voldoet de app aan twee belangrijke principes voor software-engineering:
Scheiding van problemen: instellingen voor verschillende onderdelen van de app zijn niet afhankelijk of gekoppeld aan elkaar.
Opties bieden ook een mechanisme voor het valideren van configuratiegegevens. Zie de sectie Optiesvalidatie voor meer informatie.
Hiërarchische bindingsconfiguratie
De voorkeursmethode voor het lezen van gerelateerde configuratiewaarden is het gebruik van het optiespatroon. Het optiespatroon is mogelijk via de IOptions<TOptions> interface, waarbij de algemene typeparameter TOptions wordt beperkt tot een class. De IOptions<TOptions> kan later worden verstrekt via afhankelijkheidsinjectie. Zie Afhankelijkheidsinjectie in .NET voor meer informatie.
Als u bijvoorbeeld de gemarkeerde configuratiewaarden wilt lezen uit een appsettings.json-bestand :
Maak de volgende TransientFaultHandlingOptions klasse:
public sealed class TransientFaultHandlingOptions
{
public bool Enabled { get; set; }
public TimeSpan AutoRetryDelay { get; set; }
}
Wanneer u het optiespatroon gebruikt, wordt er een optiesklasse gebruikt:
Moet niet-abstract zijn met een openbare parameterloze constructor
Openbare eigenschappen voor lezen/schrijven bevatten om te binden (velden zijn niet gebonden)
De volgende code maakt deel uit van het bestand Program.cs C#en:
Roept ConfigurationBinder.Bind aan om de TransientFaultHandlingOptions klasse aan de "TransientFaultHandlingOptions" sectie te binden.
Geeft de configuratiegegevens weer.
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:
In de voorgaande code heeft het JSON-configuratiebestand de "TransientFaultHandlingOptions" sectie gebonden aan het TransientFaultHandlingOptions exemplaar. Hierdoor worden de eigenschappen van C#-objecten gehydrateerd met de bijbehorende waarden uit de configuratie.
ConfigurationBinder.Get<T> bindt en retourneert het opgegeven type. ConfigurationBinder.Get<T>kan handiger zijn dan het gebruik.ConfigurationBinder.Bind De volgende code laat zien hoe u deze kunt gebruiken ConfigurationBinder.Get<T> met de TransientFaultHandlingOptions klasse:
var options =
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
.Get<TransientFaultHandlingOptions>();
Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");
In de voorgaande code wordt het ConfigurationBinder.Get<T> gebruikt om een exemplaar van het TransientFaultHandlingOptions object te verkrijgen met de eigenschapswaarden die zijn gevuld met de onderliggende configuratie.
Belangrijk
De ConfigurationBinder klasse bevat verschillende API's, zoals .Bind(object instance) en .Get<T>() die niet zijn beperkt tot class. Wanneer u een van de optiesinterfaces gebruikt, moet u voldoen aan bovengenoemde beperkingen voor optiesklassen.
Een alternatieve benadering bij het gebruik van het optiespatroon is om de "TransientFaultHandlingOptions" sectie te binden en toe te voegen aan de container van de afhankelijkheidsinjectieservice. In de volgende code TransientFaultHandlingOptions wordt deze toegevoegd aan de servicecontainer met Configure en gebonden aan de configuratie:
De key parameter is de naam van de configuratiesectie die moet worden gezocht. Deze hoeft niet overeen te komen met de naam van het type dat het vertegenwoordigt. U kunt bijvoorbeeld een sectie met de naam "FaultHandling" hebben en deze kan worden vertegenwoordigd door de TransientFaultHandlingOptions klasse. In dit geval geeft u in plaats daarvan de GetSection functie door"FaultHandling". De nameof operator wordt gebruikt als gemak wanneer de benoemde sectie overeenkomt met het type waarmee deze overeenkomt.
Met behulp van de voorgaande code leest de volgende code de positieopties:
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}");
}
}
In de voorgaande code worden wijzigingen in het JSON-configuratiebestand nadat de app is gestart, niet gelezen. Als u wijzigingen wilt lezen nadat de app is gestart, gebruikt u IOptionsSnapshot of IOptionsMonitor om wijzigingen te controleren wanneer deze optreden en reageert u dienovereenkomstig.
IOptionsMonitorCache<TOptions> wordt gebruikt voor IOptionsMonitor<TOptions> het opslaan van exemplaren in de cache TOptions . De IOptionsMonitorCache<TOptions> opties in de monitor worden ongeldig, zodat de waarde opnieuw wordt gecomputeerd (TryRemove). Waarden kunnen handmatig worden geïntroduceerd met TryAdd. De Clear methode wordt gebruikt wanneer alle benoemde exemplaren op aanvraag opnieuw moeten worden gemaakt.
Met behulp van een algemeen wrappertype kunt u de levensduur van de optie loskoppelen van de afhankelijkheidsinjectiecontainer (DI). De IOptions<TOptions>.Value interface biedt een abstractielaag, inclusief algemene beperkingen, voor uw optiestype. Dit biedt de volgende voordelen:
De evaluatie van het T configuratie-exemplaar wordt uitgesteld tot het openen van IOptions<TOptions>.Value, in plaats van wanneer het wordt geïnjecteerd. Dit is belangrijk omdat u de T optie op verschillende plaatsen kunt gebruiken en de semantiek van de levensduur kunt kiezen zonder dat u iets hoeft te Twijzigen.
Bij het registreren van opties van het type Thoeft u het T type niet expliciet te registreren. Dit is handig wanneer u een bibliotheek met eenvoudige standaardinstellingen ontwerpt en u niet wilt afdwingen dat de aanroeper opties registreert in de DI-container met een specifieke levensduur.
Vanuit het perspectief van de API is het mogelijk beperkingen voor het type T toe te staan (in dit geval T is dit beperkt tot een verwijzingstype).
IOptionsSnapshot gebruiken om bijgewerkte gegevens te lezen
Wanneer u gebruikt IOptionsSnapshot<TOptions>, worden opties eenmaal per aanvraag berekend wanneer deze worden geopend en in de cache worden opgeslagen voor de levensduur van de aanvraag. Wijzigingen in de configuratie worden gelezen nadat de app wordt gestart bij het gebruik van configuratieproviders die ondersteuning bieden voor het lezen van bijgewerkte configuratiewaarden.
Het verschil tussen IOptionsMonitor en IOptionsSnapshot is dat:
IOptionsMonitor is een singleton-service waarmee de huidige optiewaarden op elk gewenst moment worden opgehaald, wat met name handig is in singleton-afhankelijkheden.
IOptionsSnapshot is een scoped service en biedt een momentopname van de opties op het moment dat het IOptionsSnapshot<T> object wordt samengesteld. Momentopnamen van opties zijn ontworpen voor gebruik met tijdelijke en bereikafhankelijkheden.
In de voorgaande code wordt de Configure<TOptions> methode gebruikt om een configuratie-exemplaar te registreren waarmee TOptions een verbinding wordt uitgevoerd en worden de opties bijgewerkt wanneer de configuratie wordt gewijzigd.
IOptionsMonitor
Het IOptionsMonitor type ondersteunt wijzigingsmeldingen en maakt scenario's mogelijk waarin uw app dynamisch moet reageren op wijzigingen in de configuratiebron. Dit is handig wanneer u moet reageren op wijzigingen in configuratiegegevens nadat de app is gestart. Wijzigingsmeldingen worden alleen ondersteund voor configuratieproviders op basis van een bestandssysteem, zoals de volgende:
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}");
}
}
In de voorgaande code worden wijzigingen in het JSON-configuratiebestand gewijzigd nadat de app is gestart.
Tip
Sommige bestandssystemen, zoals Docker-containers en netwerkshares, verzenden mogelijk niet betrouwbaar wijzigingsmeldingen. Wanneer u de IOptionsMonitor<TOptions> interface in deze omgevingen gebruikt, stelt u de DOTNET_USE_POLLING_FILE_WATCHER omgevingsvariabele 1 in op of true pollt u het bestandssysteem op wijzigingen. Het interval waarmee wijzigingen worden gecontroleerd, is elke vier seconden en kan niet worden geconfigureerd.
Zie Een .NET-app containeriseren voor meer informatie over Docker-containers.
Ondersteuning voor benoemde opties met IConfigureNamedOptions
Benoemde opties:
Dit is handig wanneer meerdere configuratiesecties verbinding maken met dezelfde eigenschappen.
Zijn hoofdlettergevoelig.
Houd rekening met het volgende appsettings.json-bestand :
In plaats van twee klassen te maken om te binden Features:Personalize en Features:WeatherStationwordt de volgende klasse gebruikt voor elke sectie:
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; }
}
Met de volgende code worden de benoemde opties geconfigureerd:
Met de volgende code worden de benoemde opties weergegeven:
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);
}
}
OptionsBuilder<TOptions> wordt gebruikt voor het configureren van TOptions exemplaren. OptionsBuilder stroomlijnt het maken van benoemde opties, omdat het slechts één parameter is voor de eerste AddOptions<TOptions>(string optionsName) aanroep in plaats van in alle volgende aanroepen weer te geven. Optiesvalidatie en de ConfigureOptions overbelastingen die serviceafhankelijkheden accepteren, zijn alleen beschikbaar via OptionsBuilder.
Wanneer u opties configureert, kunt u afhankelijkheidsinjectie gebruiken voor toegang tot geregistreerde services en deze gebruiken om opties te configureren. Dit is handig wanneer u toegang nodig hebt tot services om opties te configureren. Services kunnen op twee manieren worden geopend vanuit DI tijdens het configureren van opties:
Geef een configuratiedelegatie door om te configureren op OptionsBuilder<TOptions>.OptionsBuilder<TOptions> biedt overbelastingen van Configureren waarmee maximaal vijf services kunnen worden gebruikt om opties te configureren:
Het is raadzaam om een configuratie-gemachtigde door te geven aan Configureren, omdat het maken van een service complexer is. Het maken van een type is gelijk aan wat het framework doet bij het aanroepen van Configureren. Met Aanroepen configureren registreert een tijdelijke algemene IConfigureNamedOptions<TOptions>, die een constructor heeft die de algemene servicetypen accepteert die zijn opgegeven.
Validatie van opties
Met optiesvalidatie kunnen optiewaarden worden gevalideerd.
Houd rekening met het volgende appsettings.json-bestand :
De volgende klasse bindt aan de "MyCustomSettingsSection" configuratiesectie en past een aantal DataAnnotations regels toe:
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; }
}
In de voorgaande SettingsOptions klasse bevat de ConfigurationSectionName eigenschap de naam van de configuratiesectie waaraan moet worden gekoppeld. In dit scenario bevat het optiesobject de naam van de configuratiesectie.
Tip
De naam van de configuratiesectie is onafhankelijk van het configuratieobject waaraan het is gebonden. Met andere woorden, een configuratiesectie met de naam "FooBarOptions" kan worden gebonden aan een optiesobject met de naam ZedOptions. Hoewel het misschien gebruikelijk is om ze dezelfde naam te geven, is het niet nodig en kan het naamconflicten daadwerkelijk veroorzaken.
De volgende code geeft de configuratiewaarden weer of rapporteert validatiefouten:
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);
}
}
}
}
Met de volgende code wordt een complexere validatieregel toegepast met behulp van een gemachtigde:
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.");
De validatie vindt plaats tijdens runtime, maar u kunt deze zo configureren dat deze wordt uitgevoerd bij het opstarten door in plaats daarvan een aanroep te koppelen aan ValidateOnStart:
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();
Stel na de configuratie in met IPostConfigureOptions<TOptions>. Na de configuratie wordt uitgevoerd nadat alle IConfigureOptions<TOptions> configuraties zijn uitgevoerd en kan dit handig zijn in scenario's waarin u de configuratie moet overschrijven:
De bron voor deze inhoud vindt u op GitHub, waar u ook problemen en pull-aanvragen kunt maken en controleren. Bekijk onze gids voor inzenders voor meer informatie.
.NET-feedback
.NET is een open source project. Selecteer een koppeling om feedback te geven:
Afhankelijkheidsinjectie in een ASP.NET Core-app begrijpen en implementeren. Gebruik de ingebouwde servicecontainer van ASP.NET Core om afhankelijkheden te beheren. Registreer services bij de servicecontainer.