Optionsmuster in ASP.NET Core
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Von Rick Anderson
Das Optionsmuster verwendet Klassen, um stark typisierten Zugriff auf zusammengehörige Einstellungsgruppen zu ermöglichen. Wenn Konfigurationseinstellungen nach Szenario in separate Klassen isoliert werden, entspricht die Anwendung zwei wichtigen Prinzipien der Softwareentwicklung:
- Kapselung:
- Klassen, die von Konfigurationseinstellungen abhängen, sind nur von den Konfigurationseinstellungen abhängig, die sie verwenden.
- Separation of Concerns:
- Einstellungen für verschiedene App-Komponenten sind nicht voneinander abhängig und nicht aneinander gekoppelt.
Optionen bieten auch einen Mechanismus, um Konfigurationsdaten zu validieren. Weitere Informationen finden Sie im Abschnitt Optionsvalidierung.
Dieser Artikel enthält Informationen zu den Optionsmustern in ASP.NET Core. Informationen zur Verwendung des Optionsmusters in Konsolen-Apps finden Sie unter Optionsmuster in .NET.
Binden von hierarchischen Konfigurationsdaten
Die bevorzugte Methode für das Lesen zugehöriger Konfigurationswerte ist die Verwendung des Optionsmusters. Um z. B. die folgenden Konfigurationswerte zu lesen:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Erstellen Sie die folgende neue PositionOptions
-Klasse:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Eine Optionsklasse:
- Muss nicht abstrakt sein.
- Hat öffentliche Schreib-Lese-Eigenschaften des Typs, die entsprechende Elemente in config gebunden sind.
- Hat seine Lese-/Schreibeigenschaften, die an übereinstimmende Einträge in der Konfiguration gebunden sind.
- Es sind keine Felder gebunden. Im vorangehenden Code ist
Position
nicht gebunden. Das FeldPosition
wird verwendet, sodass die Zeichenfolge"Position"
nicht in der App hartcodiert werden muss, wenn die Klasse an einen Konfigurationsanbieter gebunden wird.
Der folgende Code
- Ruft ConfigurationBinder.Bind auf, um die
PositionOptions
-Klasse an denPosition
-Abschnitt zu binden. - Zeigt die
Position
-Konfigurationsdaten an.
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
ConfigurationBinder.Get<T>
bindet den angegebenen Typ und gibt ihn zurück. ConfigurationBinder.Get<T>
ist möglicherweise praktischer als die Verwendung von ConfigurationBinder.Bind
. Der folgende Code zeigt die Verwendung von ConfigurationBinder.Get<T>
mit der PositionOptions
-Klasse:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Bind ermöglicht auch die Verketten einer abstrakten Klasse. Beachten Sie den folgenden Code, der die abstrakte Klasse SomethingWithAName
verwendet:
namespace ConfigSample.Options;
public abstract class SomethingWithAName
{
public abstract string? Name { get; set; }
}
public class NameTitleOptions(int age) : SomethingWithAName
{
public const string NameTitle = "NameTitle";
public override string? Name { get; set; }
public string Title { get; set; } = string.Empty;
public int Age { get; set; } = age;
}
Der folgende Code zeigt die NameTitleOptions
Konfigurationswerte an:
public class Test33Model : PageModel
{
private readonly IConfiguration Configuration;
public Test33Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var nameTitleOptions = new NameTitleOptions(22);
Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
return Content($"Title: {nameTitleOptions.Title} \n" +
$"Name: {nameTitleOptions.Name} \n" +
$"Age: {nameTitleOptions.Age}"
);
}
}
Anrufe an Bind
sind weniger streng als Anrufe an Get<>
:
Bind
ermöglicht die Verständigung eines Abstraktums.Get<>
muss eine Instanz selbst erstellen.
Das Optionsmuster
Eine alternative Methode beim Verwenden des Optionsmusters besteht darin, den Abschnitt Position
zu binden und zum Dienstcontainer für die Abhängigkeitsinjektion hinzuzufügen. Im folgenden Code wird PositionOptions
mit Configure zum Dienstcontainer hinzugefügt und an die Konfiguration gebunden:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Mithilfe des vorangehenden Codes liest der folgende Code die Positionsoptionen:
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}");
}
}
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App nicht gelesen. Verwenden Sie IOptionsSnapshot, um Änderungen lesen zu können, nachdem die App gestartet wurde.
Optionenschnittstellen
- Unterstützt nicht:
- Das Lesen von Konfigurationsdaten, nachdem die App gestartet wurde
- Benannte Optionen
- Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Ist in Szenarios nützlich, in denen Optionen bei jeder Anforderung neu berechnet werden sollten. Weitere Informationen finden Sie unter Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten.
- Ist als Bereichsbezogen registriert und kann deshalb nicht in einen Singleton-Dienst eingefügt werden.
- Unterstützt benannten Optionen
- Wird verwendet, um Optionen abzurufen und Benachrichtigungen über Optionen für
TOptions
-Instanzen zu verwalten - Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Unterstützt:
- Änderungsbenachrichtigungen
- Benannte Optionen
- Erneut ladbare Konfiguration
- Selektive Optionsvalidierung (IOptionsMonitorCache<TOptions>)
Mit Postkonfigurationsszenarios können Sie Optionen festlegen oder ändern, nachdem alle IConfigureOptions<TOptions>-Konfigurationen durchgeführt wurden.
IOptionsFactory<TOptions> ist für das Erstellen neuer Optionsinstanzen zuständig. Es verfügt über eine einzelne Create-Methode. Die Standardimplementierung akzeptiert alle registrierten IConfigureOptions<TOptions> und IPostConfigureOptions<TOptions> und führt alle Konfigurationen zuerst und die Postkonfigurationen danach aus. Es wird zwischen IConfigureNamedOptions<TOptions> und IConfigureOptions<TOptions> unterschieden, und es werden nur die entsprechenden Schnittstellen aufgerufen.
IOptionsMonitorCache<TOptions> wird von IOptionsMonitor<TOptions> zum Zwischenspeichern der TOptions
-Instanzen verwendet. IOptionsMonitorCache<TOptions> erklärt Optionsinstanzen im Monitor für ungültig, sodass der Wert neu berechnet wird (TryRemove). Werte können manuell mit TryAdd eingeführt werden. Die Clear-Methode wird verwendet, wenn alle benannten Instanzen bei Bedarf neu erstellt werden sollen.
Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten
Verwenden von IOptionsSnapshot<TOptions>:
- Optionen werden einmal pro Anforderung berechnet. Dies geschieht, wenn auf sie zugegriffen wird und sie für die Dauer der Anforderung zwischengespeichert werden.
- Kann einen erheblichen Leistungsnachteil mit sich, da es sich um einen bereichsbezogenen Dienst handelt und pro Anforderung eine neue Berechnung durchgeführt wird. Weitere Informationen finden Sie in diesem GitHub-Problem und unter Improve the performance of configuration binding (Verbessern der Leistung der Konfigurationsbindung).
- Änderungen an der Konfiguration werden gelesen, nachdem die App gestartet wurde, wenn Konfigurationsanbieter verwendet werden, die das Lesen aktualisierter Konfigurationswerte unterstützen.
Der Unterschied zwischen IOptionsMonitor
und IOptionsSnapshot
ist folgender:
IOptionsMonitor
ist ein Singleton-Dienst, der zu jeder Zeit aktuelle Optionswerte empfängt, was insbesondere bei Singleton-Abhängigkeiten nützlich ist.IOptionsSnapshot
ist ein bereichsbezogener Dienst und bietet eine Momentaufnahme der Optionen zu dem Zeitpunkt, an dem dasIOptionsSnapshot<T>
-Objekt konstruiert wird. Momentaufnahmen von Optionen sind für die Verwendung mit vorübergehenden und bereichsbezogenen Abhängigkeiten bestimmt.
Der folgende Code verwendet 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}");
}
}
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App gelesen.
IOptionsMonitor
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Im folgenden Beispiel wird IOptionsMonitor<TOptions> verwendet:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Unterstützung für benannte Optionen mit IConfigureNamedOptions
Benannte Optionen...
- sind nützlich, wenn mehrere Konfigurationsabschnitte an die gleichen Eigenschaften gebunden werden.
- unterscheiden zwischen Groß-/Kleinschreibung.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anstatt zwei Klassen für die Bindung von TopItem:Month
und TopItem:Year
zu erstellen, wird folgende Klasse für jeden Abschnitt verwendet:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
Der folgende Code dient zum Konfigurieren der benannten Optionen:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
Der folgende Code zeigt die benannten Optionen an:
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" );
}
}
Alle Optionen sind benannte Instanzen. IConfigureOptions<TOptions>-Instanzen werden behandelt, als würden sie die Options.DefaultName
-Instanz anzielen. Diese ist string.Empty
. IConfigureNamedOptions<TOptions> implementiert auch IConfigureOptions<TOptions>. Die standardmäßige Implementierung von IOptionsFactory<TOptions> verfügt über eine Logik, um jede einzelne entsprechend zu verwenden. Die benannte Option null
wird verwendet, um auf alle benannten Instanzen anstelle einer bestimmten benannten Instanz abzuzielen. ConfigureAll und PostConfigureAll verwenden diese Konvention.
OptionsBuilder-API
OptionsBuilder<TOptions> dient zum Konfigurieren von TOptions
-Instanzen. OptionsBuilder
optimiert die Erstellung von benannten Optionen, da es sich nur um einen einzelnen Parameter für den ersten AddOptions<TOptions>(string optionsName)
-Aufruf handelt statt um alle nachfolgenden Aufrufe. Die Optionsvalidierung und die ConfigureOptions
-Überladungen, die Dienstabhängigkeiten akzeptieren, sind nur über OptionsBuilder
verfügbar.
OptionsBuilder
wird im Abschnitt Überprüfung von Optionen verwendet.
Informationen zum Hinzufügen eines benutzerdefinierten Repositorys finden Sie unter Verwenden von AddOptions zum Konfigurieren eines benutzerdefinierten Repositorys.
Verwenden von DI-Diensten zum Konfigurieren von Optionen
Der Zugriff auf Dienste ist über die Dependency Injection möglich, während Optionen auf zwei Arten konfiguriert werden:
Übergeben Sie einen Konfigurationsdelegaten an Configure für OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
stellt Überladungen von Configure zur Verfügung, die es ermöglichen, bis zu fünf Dienste zu verwenden, um Optionen zu konfigurieren:builder.Services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Erstellen Sie einen Typ, der IConfigureOptions<TOptions> oder IConfigureNamedOptions<TOptions> implementiert, und registrieren Sie den Typ als Dienst.
Es empfiehlt sich das Übergeben eines Konfigurationsdelegaten an Configure, da das Erstellen eines Diensts etwas komplexer ist. Das Erstellen eines Typs entspricht den Aktionen des Frameworks beim Aufruf von Configure. Beim Aufrufen von Configure wird eine vorübergehende generische IConfigureNamedOptions<TOptions> registriert, die über einen Konstruktor verfügt, der die angegebenen generischen Diensttypen akzeptiert.
Überprüfung von Optionen
Bei der Überprüfung der Optionen können Optionswerte überprüft werden.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
Die folgende Klasse wird verwendet, um eine Bindung zum "MyConfig"
-Konfigurationsabschnitt zu erstellen, und wendet einige DataAnnotations
-Regeln an:
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; }
}
Der folgende Code
- Ruft AddOptions auf, um eine OptionsBuilder<TOptions> zu erhalten, die an die Klasse
MyConfigOptions
gebunden wird. - Mit dem Code wird ValidateDataAnnotations abgerufen, um die Validierung mithilfe von
DataAnnotations
zu aktivieren.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
Die ValidateDataAnnotations
-Erweiterungsmethode wird im NuGet-Paket Microsoft.Extensions.Options.DataAnnotations definiert. Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf dieses Paket implizit über das geteilte Framework verwiesen.
Der folgende Code zeigt die Konfigurationswerte oder die Überprüfungsfehler an:
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);
}
Der folgende Code wendet mithilfe eines Delegaten eine komplexere Überprüfungsregel an:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.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.
var app = builder.Build();
IValidateOptions<TOptions>
und IValidatableObject
Die folgende Klasse implementiert 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
ermöglicht das Verschieben des Überprüfungscodes aus Program.cs
und in eine Klasse.
Mit dem vorangehenden Code wird die Überprüfung in Program.cs
aktiviert:
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
Die Optionsüberprüfung unterstützt auch IValidatableObject. So führen Sie die Überprüfung einer Klasse auf Klassenebene innerhalb der Klasse selbst aus:
- Implementieren Sie die
IValidatableObject
-Schnittstelle und die Validate-Methode innerhalb der Klasse. - Rufen Sie
Program.cs
in ValidateDataAnnotations auf.
ValidateOnStart
Die Optionsüberprüfung wird beim ersten Erstellen einer TOption
Instanz ausgeführt. Das bedeutet beispielsweise, wenn der erste Zugriff auf IOptionsSnapshot<TOptions>.Value
eine Anforderungspipeline erfolgt oder wenn einstellungen IOptionsMonitor<TOptions>.Get(string)
vorhanden sind. Nachdem die Einstellungen neu geladen wurden, wird die Überprüfung erneut ausgeführt. Die ASP.NET Core-Runtime verwendet OptionsCache<TOptions> , um die Optionsinstanz zwischenzuspeichern, sobald sie erstellt wurde.
Um die Optionsüberprüfung beim Start der App sofort auszuführen, rufen Sie ValidateOnStart<TOptions>(OptionsBuilder<TOptions>) in Program.cs
auf:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Optionen für die Postkonfiguration
Legen Sie die Postkonfiguration mit IPostConfigureOptions<TOptions> fest. Die Postkonfiguration erfolgt, nachdem die gesamte IConfigureOptions<TOptions>-Konfiguration abgeschlossen ist:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
PostConfigure ist nach dem Konfigurieren der benannten Optionen verfügbar:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
Verwenden Sie PostConfigureAll zur Postkonfiguration aller Konfigurationsinstanzen:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Zugriffsoptionen in Program.cs
Um auf IOptions<TOptions> oder IOptionsMonitor<TOptions> in Program.cs
zuzugreifen, rufen Sie GetRequiredService für WebApplication.Services auf:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Zusätzliche Ressourcen
Von Kirk Larkin und Rick Anderson
Das Optionsmuster verwendet Klassen, um stark typisierten Zugriff auf zusammengehörige Einstellungsgruppen zu ermöglichen. Wenn Konfigurationseinstellungen nach Szenario in separate Klassen isoliert werden, entspricht die Anwendung zwei wichtigen Prinzipien der Softwareentwicklung:
- Kapselung:
- Klassen, die von Konfigurationseinstellungen abhängen, sind nur von den Konfigurationseinstellungen abhängig, die sie verwenden.
- Separation of Concerns:
- Einstellungen für verschiedene App-Komponenten sind nicht voneinander abhängig und nicht aneinander gekoppelt.
Optionen bieten auch einen Mechanismus, um Konfigurationsdaten zu validieren. Weitere Informationen finden Sie im Abschnitt Optionsvalidierung.
Dieser Artikel enthält Informationen zu den Optionsmustern in ASP.NET Core. Informationen zur Verwendung des Optionsmusters in Konsolen-Apps finden Sie unter Optionsmuster in .NET.
Binden von hierarchischen Konfigurationsdaten
Die bevorzugte Methode für das Lesen zugehöriger Konfigurationswerte ist die Verwendung des Optionsmusters. Um z. B. die folgenden Konfigurationswerte zu lesen:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Erstellen Sie die folgende neue PositionOptions
-Klasse:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Eine Optionsklasse:
- Eine Optionsklasse muss nicht abstrakt sein und über einen öffentlichen parameterlosen Konstruktor verfügen.
- Alle öffentlichen Lese-/Schreibeigenschaften des Typs sind gebunden.
- Felder sind nicht gebunden. Im vorangehenden Code ist
Position
nicht gebunden. Das FeldPosition
wird verwendet, sodass die Zeichenfolge"Position"
nicht in der App hartcodiert werden muss, wenn die Klasse an einen Konfigurationsanbieter gebunden wird.
Der folgende Code
- Ruft ConfigurationBinder.Bind auf, um die
PositionOptions
-Klasse an denPosition
-Abschnitt zu binden. - Zeigt die
Position
-Konfigurationsdaten an.
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
ConfigurationBinder.Get<T>
bindet den angegebenen Typ und gibt ihn zurück. ConfigurationBinder.Get<T>
ist möglicherweise praktischer als die Verwendung von ConfigurationBinder.Bind
. Der folgende Code zeigt die Verwendung von ConfigurationBinder.Get<T>
mit der PositionOptions
-Klasse:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Eine alternative Methode beim Verwenden des Optionsmusters besteht darin, den Abschnitt Position
zu binden und zum Dienstcontainer für die Abhängigkeitsinjektion hinzuzufügen. Im folgenden Code wird PositionOptions
mit Configure zum Dienstcontainer hinzugefügt und an die Konfiguration gebunden:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Mithilfe des vorangehenden Codes liest der folgende Code die Positionsoptionen:
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}");
}
}
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App nicht gelesen. Verwenden Sie IOptionsSnapshot, um Änderungen lesen zu können, nachdem die App gestartet wurde.
Optionenschnittstellen
- Unterstützt nicht:
- Das Lesen von Konfigurationsdaten, nachdem die App gestartet wurde
- Benannte Optionen
- Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Ist in Szenarios nützlich, in denen Optionen bei jeder Anforderung neu berechnet werden sollten. Weitere Informationen finden Sie unter Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten.
- Ist als Bereichsbezogen registriert und kann deshalb nicht in einen Singleton-Dienst eingefügt werden.
- Unterstützt benannten Optionen
- Wird verwendet, um Optionen abzurufen und Benachrichtigungen über Optionen für
TOptions
-Instanzen zu verwalten - Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Unterstützt:
- Änderungsbenachrichtigungen
- Benannte Optionen
- Erneut ladbare Konfiguration
- Selektive Optionsvalidierung (IOptionsMonitorCache<TOptions>)
Mit Postkonfigurationsszenarios können Sie Optionen festlegen oder ändern, nachdem alle IConfigureOptions<TOptions>-Konfigurationen durchgeführt wurden.
IOptionsFactory<TOptions> ist für das Erstellen neuer Optionsinstanzen zuständig. Es verfügt über eine einzelne Create-Methode. Die Standardimplementierung akzeptiert alle registrierten IConfigureOptions<TOptions> und IPostConfigureOptions<TOptions> und führt alle Konfigurationen zuerst und die Postkonfigurationen danach aus. Es wird zwischen IConfigureNamedOptions<TOptions> und IConfigureOptions<TOptions> unterschieden, und es werden nur die entsprechenden Schnittstellen aufgerufen.
IOptionsMonitorCache<TOptions> wird von IOptionsMonitor<TOptions> zum Zwischenspeichern der TOptions
-Instanzen verwendet. IOptionsMonitorCache<TOptions> erklärt Optionsinstanzen im Monitor für ungültig, sodass der Wert neu berechnet wird (TryRemove). Werte können manuell mit TryAdd eingeführt werden. Die Clear-Methode wird verwendet, wenn alle benannten Instanzen bei Bedarf neu erstellt werden sollen.
Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten
Verwenden von IOptionsSnapshot<TOptions>:
- Optionen werden einmal pro Anforderung berechnet. Dies geschieht, wenn auf sie zugegriffen wird und sie für die Dauer der Anforderung zwischengespeichert werden.
- Kann einen erheblichen Leistungsnachteil mit sich, da es sich um einen bereichsbezogenen Dienst handelt und pro Anforderung eine neue Berechnung durchgeführt wird. Weitere Informationen finden Sie in diesem GitHub-Problem und unter Improve the performance of configuration binding (Verbessern der Leistung der Konfigurationsbindung).
- Änderungen an der Konfiguration werden gelesen, nachdem die App gestartet wurde, wenn Konfigurationsanbieter verwendet werden, die das Lesen aktualisierter Konfigurationswerte unterstützen.
Der Unterschied zwischen IOptionsMonitor
und IOptionsSnapshot
ist folgender:
IOptionsMonitor
ist ein Singleton-Dienst, der zu jeder Zeit aktuelle Optionswerte empfängt, was insbesondere bei Singleton-Abhängigkeiten nützlich ist.IOptionsSnapshot
ist ein bereichsbezogener Dienst und bietet eine Momentaufnahme der Optionen zu dem Zeitpunkt, an dem dasIOptionsSnapshot<T>
-Objekt konstruiert wird. Momentaufnahmen von Optionen sind für die Verwendung mit vorübergehenden und bereichsbezogenen Abhängigkeiten bestimmt.
Der folgende Code verwendet 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}");
}
}
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App gelesen.
IOptionsMonitor
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Im folgenden Beispiel wird IOptionsMonitor<TOptions> verwendet:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Unterstützung für benannte Optionen mit IConfigureNamedOptions
Benannte Optionen...
- sind nützlich, wenn mehrere Konfigurationsabschnitte an die gleichen Eigenschaften gebunden werden.
- unterscheiden zwischen Groß-/Kleinschreibung.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anstatt zwei Klassen für die Bindung von TopItem:Month
und TopItem:Year
zu erstellen, wird folgende Klasse für jeden Abschnitt verwendet:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
Der folgende Code dient zum Konfigurieren der benannten Optionen:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
Der folgende Code zeigt die benannten Optionen an:
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" );
}
}
Alle Optionen sind benannte Instanzen. IConfigureOptions<TOptions>-Instanzen werden behandelt, als würden sie die Options.DefaultName
-Instanz anzielen. Diese ist string.Empty
. IConfigureNamedOptions<TOptions> implementiert auch IConfigureOptions<TOptions>. Die standardmäßige Implementierung von IOptionsFactory<TOptions> verfügt über eine Logik, um jede einzelne entsprechend zu verwenden. Die benannte Option null
wird verwendet, um auf alle benannten Instanzen anstelle einer bestimmten benannten Instanz abzuzielen. ConfigureAll und PostConfigureAll verwenden diese Konvention.
OptionsBuilder-API
OptionsBuilder<TOptions> dient zum Konfigurieren von TOptions
-Instanzen. OptionsBuilder
optimiert die Erstellung von benannten Optionen, da es sich nur um einen einzelnen Parameter für den ersten AddOptions<TOptions>(string optionsName)
-Aufruf handelt statt um alle nachfolgenden Aufrufe. Die Optionsvalidierung und die ConfigureOptions
-Überladungen, die Dienstabhängigkeiten akzeptieren, sind nur über OptionsBuilder
verfügbar.
OptionsBuilder
wird im Abschnitt Überprüfung von Optionen verwendet.
Informationen zum Hinzufügen eines benutzerdefinierten Repositorys finden Sie unter Verwenden von AddOptions zum Konfigurieren eines benutzerdefinierten Repositorys.
Verwenden von DI-Diensten zum Konfigurieren von Optionen
Der Zugriff auf Dienste ist über die Dependency Injection möglich, während Optionen auf zwei Arten konfiguriert werden:
Übergeben Sie einen Konfigurationsdelegaten an Configure für OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
stellt Überladungen von Configure zur Verfügung, die es ermöglichen, bis zu fünf Dienste zu verwenden, um Optionen zu konfigurieren:builder.Services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Erstellen Sie einen Typ, der IConfigureOptions<TOptions> oder IConfigureNamedOptions<TOptions> implementiert, und registrieren Sie den Typ als Dienst.
Es empfiehlt sich das Übergeben eines Konfigurationsdelegaten an Configure, da das Erstellen eines Diensts etwas komplexer ist. Das Erstellen eines Typs entspricht den Aktionen des Frameworks beim Aufruf von Configure. Beim Aufrufen von Configure wird eine vorübergehende generische IConfigureNamedOptions<TOptions> registriert, die über einen Konstruktor verfügt, der die angegebenen generischen Diensttypen akzeptiert.
Überprüfung von Optionen
Bei der Überprüfung der Optionen können Optionswerte überprüft werden.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
Die folgende Klasse wird verwendet, um eine Bindung zum "MyConfig"
-Konfigurationsabschnitt zu erstellen, und wendet einige DataAnnotations
-Regeln an:
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; }
}
Der folgende Code
- Ruft AddOptions auf, um eine OptionsBuilder<TOptions> zu erhalten, die an die Klasse
MyConfigOptions
gebunden wird. - Mit dem Code wird ValidateDataAnnotations abgerufen, um die Validierung mithilfe von
DataAnnotations
zu aktivieren.
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
Die ValidateDataAnnotations
-Erweiterungsmethode wird im NuGet-Paket Microsoft.Extensions.Options.DataAnnotations definiert. Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf dieses Paket implizit über das geteilte Framework verwiesen.
Der folgende Code zeigt die Konfigurationswerte oder die Überprüfungsfehler an:
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);
}
Der folgende Code wendet mithilfe eines Delegaten eine komplexere Überprüfungsregel an:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.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.
var app = builder.Build();
IValidateOptions<TOptions>
und IValidatableObject
Die folgende Klasse implementiert 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
ermöglicht das Verschieben des Überprüfungscodes aus Program.cs
und in eine Klasse.
Mit dem vorangehenden Code wird die Überprüfung in Program.cs
aktiviert:
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
Die Optionsüberprüfung unterstützt auch IValidatableObject. So führen Sie die Überprüfung einer Klasse auf Klassenebene innerhalb der Klasse selbst aus:
- Implementieren Sie die
IValidatableObject
-Schnittstelle und die Validate-Methode innerhalb der Klasse. - Rufen Sie
Program.cs
in ValidateDataAnnotations auf.
ValidateOnStart
Die Optionsüberprüfung wird zum ersten Mal ausgeführt, wenn eine IOptions<TOptions>-, IOptionsSnapshot<TOptions>- oder IOptionsMonitor<TOptions>-Implementierung erstellt wird. Um die Optionsüberprüfung beim Start der App sofort auszuführen, rufen Sie ValidateOnStart in Program.cs
auf:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Optionen für die Postkonfiguration
Legen Sie die Postkonfiguration mit IPostConfigureOptions<TOptions> fest. Die Postkonfiguration erfolgt, nachdem die gesamte IConfigureOptions<TOptions>-Konfiguration abgeschlossen ist:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
PostConfigure ist nach dem Konfigurieren der benannten Optionen verfügbar:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
Verwenden Sie PostConfigureAll zur Postkonfiguration aller Konfigurationsinstanzen:
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Zugriffsoptionen in Program.cs
Um auf IOptions<TOptions> oder IOptionsMonitor<TOptions> in Program.cs
zuzugreifen, rufen Sie GetRequiredService für WebApplication.Services auf:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Zusätzliche Ressourcen
Von Kirk Larkin und Rick Anderson
Das Optionsmuster verwendet Klassen, um stark typisierten Zugriff auf zusammengehörige Einstellungsgruppen zu ermöglichen. Wenn Konfigurationseinstellungen nach Szenario in separate Klassen isoliert werden, entspricht die Anwendung zwei wichtigen Prinzipien der Softwareentwicklung:
- Kapselung:
- Klassen, die von Konfigurationseinstellungen abhängen, sind nur von den Konfigurationseinstellungen abhängig, die sie verwenden.
- Separation of Concerns:
- Einstellungen für verschiedene App-Komponenten sind nicht voneinander abhängig und nicht aneinander gekoppelt.
Optionen bieten auch einen Mechanismus, um Konfigurationsdaten zu validieren. Weitere Informationen finden Sie im Abschnitt Optionsvalidierung.
Dieses Thema enthält Informationen zu den Optionsmustern in ASP.NET Core. Informationen zur Verwendung des Optionsmusters in Konsolen-Apps finden Sie unter Optionsmuster in .NET.
Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Binden von hierarchischen Konfigurationsdaten
Die bevorzugte Methode für das Lesen zugehöriger Konfigurationswerte ist die Verwendung des Optionsmusters. Um z. B. die folgenden Konfigurationswerte zu lesen:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Erstellen Sie die folgende neue PositionOptions
-Klasse:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
Eine Optionsklasse:
- Eine Optionsklasse muss nicht abstrakt sein und über einen öffentlichen parameterlosen Konstruktor verfügen.
- Alle öffentlichen Lese-/Schreibeigenschaften des Typs sind gebunden.
- Felder sind nicht gebunden. Im vorangehenden Code ist
Position
nicht gebunden. DiePosition
-Eigenschaft wird verwendet, sodass die Zeichenfolge"Position"
nicht in der App hartcodiert werden muss, wenn die Klasse an einen Konfigurationsanbieter gebunden wird.
Der folgende Code
- Ruft ConfigurationBinder.Bind auf, um die
PositionOptions
-Klasse an denPosition
-Abschnitt zu binden. - Zeigt die
Position
-Konfigurationsdaten an.
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
ConfigurationBinder.Get<T>
bindet den angegebenen Typ und gibt ihn zurück. ConfigurationBinder.Get<T>
ist möglicherweise praktischer als die Verwendung von ConfigurationBinder.Bind
. Der folgende Code zeigt die Verwendung von ConfigurationBinder.Get<T>
mit der PositionOptions
-Klasse:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Eine alternative Methode beim Verwenden des Optionsmusters besteht darin, den Abschnitt Position
zu binden und zum Dienstcontainer für die Abhängigkeitsinjektion hinzuzufügen. Im folgenden Code wird PositionOptions
mit Configure zum Dienstcontainer hinzugefügt und an die Konfiguration gebunden:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
Mithilfe des vorangehenden Codes liest der folgende Code die Positionsoptionen:
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}");
}
}
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App nicht gelesen. Verwenden Sie IOptionsSnapshot, um Änderungen lesen zu können, nachdem die App gestartet wurde.
Optionenschnittstellen
- Unterstützt nicht:
- Das Lesen von Konfigurationsdaten, nachdem die App gestartet wurde
- Benannte Optionen
- Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Ist in Szenarios nützlich, in denen Optionen bei jeder Anforderung neu berechnet werden sollten. Weitere Informationen finden Sie unter Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten.
- Ist als Bereichsbezogen registriert und kann deshalb nicht in einen Singleton-Dienst eingefügt werden.
- Unterstützt benannten Optionen
- Wird verwendet, um Optionen abzurufen und Benachrichtigungen über Optionen für
TOptions
-Instanzen zu verwalten - Ist als Singleton registriert und kann in eine beliebige Dienstlebensdauer eingefügt werden
- Unterstützt:
- Änderungsbenachrichtigungen
- Benannte Optionen
- Erneut ladbare Konfiguration
- Selektive Optionsvalidierung (IOptionsMonitorCache<TOptions>)
Mit Postkonfigurationsszenarios können Sie Optionen festlegen oder ändern, nachdem alle IConfigureOptions<TOptions>-Konfigurationen durchgeführt wurden.
IOptionsFactory<TOptions> ist für das Erstellen neuer Optionsinstanzen zuständig. Es verfügt über eine einzelne Create-Methode. Die Standardimplementierung akzeptiert alle registrierten IConfigureOptions<TOptions> und IPostConfigureOptions<TOptions> und führt alle Konfigurationen zuerst und die Postkonfigurationen danach aus. Es wird zwischen IConfigureNamedOptions<TOptions> und IConfigureOptions<TOptions> unterschieden, und es werden nur die entsprechenden Schnittstellen aufgerufen.
IOptionsMonitorCache<TOptions> wird von IOptionsMonitor<TOptions> zum Zwischenspeichern der TOptions
-Instanzen verwendet. IOptionsMonitorCache<TOptions> erklärt Optionsinstanzen im Monitor für ungültig, sodass der Wert neu berechnet wird (TryRemove). Werte können manuell mit TryAdd eingeführt werden. Die Clear-Methode wird verwendet, wenn alle benannten Instanzen bei Bedarf neu erstellt werden sollen.
Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten
Mithilfe von IOptionsSnapshot<TOptions> werden Optionen einmal pro Anforderung berechnet. Dies geschieht, wenn auf sie zugegriffen wird und sie für die Dauer der Anforderung zwischengespeichert werden. Änderungen an der Konfiguration werden gelesen, nachdem die App gestartet wurde, wenn Konfigurationsanbieter verwendet werden, die das Lesen aktualisierter Konfigurationswerte unterstützen.
Der Unterschied zwischen IOptionsMonitor
und IOptionsSnapshot
ist folgender:
IOptionsMonitor
ist ein Singleton-Dienst, der zu jeder Zeit aktuelle Optionswerte empfängt, was insbesondere bei Singleton-Abhängigkeiten nützlich ist.IOptionsSnapshot
ist ein bereichsbezogener Dienst und bietet eine Momentaufnahme der Optionen zu dem Zeitpunkt, an dem dasIOptionsSnapshot<T>
-Objekt konstruiert wird. Momentaufnahmen von Optionen sind für die Verwendung mit vorübergehenden und bereichsbezogenen Abhängigkeiten bestimmt.
Der folgende Code verwendet 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}");
}
}
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App gelesen.
IOptionsMonitor
Der folgende Code registriert eine Konfigurationsinstanz, mit der MyOptions
eine Bindung herstellt.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
Im folgenden Beispiel wird IOptionsMonitor<TOptions> verwendet:
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}");
}
}
Im vorangehenden Code werden standardmäßig Änderungen an der JSON-Konfigurationsdatei gelesen, nachdem die App gestartet wurde.
Unterstützung für benannte Optionen mit IConfigureNamedOptions
Benannte Optionen...
- sind nützlich, wenn mehrere Konfigurationsabschnitte an die gleichen Eigenschaften gebunden werden.
- unterscheiden zwischen Groß-/Kleinschreibung.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anstatt zwei Klassen für die Bindung von TopItem:Month
und TopItem:Year
zu erstellen, wird folgende Klasse für jeden Abschnitt verwendet:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
Der folgende Code dient zum Konfigurieren der benannten Optionen:
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();
}
Der folgende Code zeigt die benannten Optionen an:
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" );
}
}
Alle Optionen sind benannte Instanzen. IConfigureOptions<TOptions>-Instanzen werden behandelt, als würden sie die Options.DefaultName
-Instanz anzielen. Diese ist string.Empty
. IConfigureNamedOptions<TOptions> implementiert auch IConfigureOptions<TOptions>. Die standardmäßige Implementierung von IOptionsFactory<TOptions> verfügt über eine Logik, um jede einzelne entsprechend zu verwenden. Die benannte Option null
wird verwendet, um auf alle benannten Instanzen anstelle einer bestimmten benannten Instanz abzuzielen. ConfigureAll und PostConfigureAll verwenden diese Konvention.
OptionsBuilder-API
OptionsBuilder<TOptions> dient zum Konfigurieren von TOptions
-Instanzen. OptionsBuilder
optimiert die Erstellung von benannten Optionen, da es sich nur um einen einzelnen Parameter für den ersten AddOptions<TOptions>(string optionsName)
-Aufruf handelt statt um alle nachfolgenden Aufrufe. Die Optionsvalidierung und die ConfigureOptions
-Überladungen, die Dienstabhängigkeiten akzeptieren, sind nur über OptionsBuilder
verfügbar.
OptionsBuilder
wird im Abschnitt Überprüfung von Optionen verwendet.
Informationen zum Hinzufügen eines benutzerdefinierten Repositorys finden Sie unter Verwenden von AddOptions zum Konfigurieren eines benutzerdefinierten Repositorys.
Verwenden von DI-Diensten zum Konfigurieren von Optionen
Der Zugriff auf Dienste ist über die Dependency Injection möglich, während Optionen auf zwei Arten konfiguriert werden:
Übergeben Sie einen Konfigurationsdelegaten an Configure für OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
stellt Überladungen von Configure zur Verfügung, die es ermöglichen, bis zu fünf Dienste zu verwenden, um Optionen zu konfigurieren:services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Erstellen Sie einen Typ, der IConfigureOptions<TOptions> oder IConfigureNamedOptions<TOptions> implementiert, und registrieren Sie den Typ als Dienst.
Es empfiehlt sich das Übergeben eines Konfigurationsdelegaten an Configure, da das Erstellen eines Diensts etwas komplexer ist. Das Erstellen eines Typs entspricht den Aktionen des Frameworks beim Aufruf von Configure. Beim Aufrufen von Configure wird eine vorübergehende generische IConfigureNamedOptions<TOptions> registriert, die über einen Konstruktor verfügt, der die angegebenen generischen Diensttypen akzeptiert.
Überprüfung von Optionen
Bei der Überprüfung der Optionen können Optionswerte überprüft werden.
Betrachten Sie die folgende appsettings.json
-Datei:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
Die folgende Klasse erstellt eine Bindung zum "MyConfig"
-Konfigurationsabschnitt und wendet einige DataAnnotations
-Regeln an:
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; }
}
Der folgende Code
- Ruft AddOptions auf, um eine OptionsBuilder<TOptions> zu erhalten, die an die Klasse
MyConfigOptions
gebunden wird. - Mit dem Code wird ValidateDataAnnotations abgerufen, um die Validierung mithilfe von
DataAnnotations
zu aktivieren.
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();
}
Die ValidateDataAnnotations
-Erweiterungsmethode wird im NuGet-Paket Microsoft.Extensions.Options.DataAnnotations definiert. Für Web-Apps, die das Microsoft.NET.Sdk.Web
SDK verwenden, wird auf dieses Paket implizit über das geteilte Framework verwiesen.
Der folgende Code zeigt die Konfigurationswerte oder die Überprüfungsfehler an:
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);
}
Der folgende Code wendet mithilfe eines Delegaten eine komplexere Überprüfungsregel an:
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 für die komplexe Überprüfung
Die folgende Klasse implementiert 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
ermöglicht das Verschieben des Überprüfungscodes aus StartUp
und in eine Klasse.
Mit dem vorangehenden Code wird die Überprüfung in Startup.ConfigureServices
aktiviert:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
Optionen für die Postkonfiguration
Legen Sie die Postkonfiguration mit IPostConfigureOptions<TOptions> fest. Die Postkonfiguration erfolgt, nachdem die gesamte IConfigureOptions<TOptions>-Konfiguration abgeschlossen ist:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
PostConfigure ist nach dem Konfigurieren der benannten Optionen verfügbar:
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Verwenden Sie PostConfigureAll zur Postkonfiguration aller Konfigurationsinstanzen:
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Zugreifen auf Optionen während des Starts
IOptions<TOptions> und IOptionsMonitor<TOptions> können in Startup.Configure
verwendet werden, da Dienste erstellt werden, bevor die Configure
-Methode ausgeführt wird.
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
Verwenden Sie IOptions<TOptions> oder IOptionsMonitor<TOptions> nicht in Startup.ConfigureServices
. Es kann einen inkonsistenten Optionszustand geben. Dies liegt an der Reihenfolge der Dienstregistrierungen.
NuGet-Paket Options.ConfigurationExtensions
Auf das Paket Microsoft.Extensions.Options.ConfigurationExtensions wird implizit in ASP.NET Core-Apps verwiesen.
ASP.NET Core