Optionsmuster in .NET

Das Optionsmuster verwendet Klassen, um stark typisierten Zugriff auf Gruppen zusammengehöriger Einstellungen zu ermöglichen. Wenn Konfigurationseinstellungen nach Szenario in separate Klassen isoliert werden, entspricht die Anwendung zwei wichtigen Prinzipien der Softwareentwicklung:

Optionen bieten auch einen Mechanismus, um Konfigurationsdaten zu validieren. Weitere Informationen finden Sie im Abschnitt Optionsvalidierung.

Binden von hierarchischen Konfigurationsdaten

Die bevorzugte Methode für das Lesen zugehöriger Konfigurationswerte ist die Verwendung des Optionsmusters. Das Optionsmuster wird durch die IOptions<TOptions>-Schnittstelle ermöglicht, bei der generische Typparameter TOptions auf class eingeschränkt ist. IOptions<TOptions> kann später über die Abhängigkeitsinjektion (Dependency Injection, DI) bereitgestellt werden. Weitere Informationen finden Sie unter Abhängigkeitsinjektion in .NET.

So lesen Sie beispielsweise die hervorgehobenen Konfigurationswerte aus einer appsettings.json-Datei:

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

Erstellen Sie die folgende neue TransientFaultHandlingOptions-Klasse:

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

Wenn Sie das Optionsmuster verwenden, gelten für eine Optionsklasse folgende Bedingungen:

  • Sie darf nicht abstrakt sein und muss über einen öffentlichen parameterlosen Konstruktor verfügen.
  • Enthalten von öffentlichen Lese-/Schreibeigenschaften für Bindungen (Felder sind nicht gebunden)

Der folgende Code ist Teil der C#-Datei Program.cs und:

  • Ruft ConfigurationBinder.Bind auf, um die TransientFaultHandlingOptions-Klasse an den "TransientFaultHandlingOptions"-Abschnitt zu binden.
  • Zeigt die Konfigurationsdaten an.
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:

Im vorherigen Code ist der "TransientFaultHandlingOptions"-Abschnitt der JSON-Konfigurationsdatei an die TransientFaultHandlingOptions-Instanz gebunden. Dadurch werden die Eigenschaften von C#-Objekten mit den entsprechenden Werten aus der Konfiguration versorgt.

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 TransientFaultHandlingOptions-Klasse:

var options =
    builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
        .Get<TransientFaultHandlingOptions>();

Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

Im vorherigen Code wird ConfigurationBinder.Get<T> verwendet, um eine Instanz des TransientFaultHandlingOptions-Objekts mit seinen aus der zugrunde liegenden Konfiguration aufgefüllten Eigenschaftswerten zu erhalten.

Wichtig

Die Klasse ConfigurationBinder macht mehrere APIs verfügbar, z. B. .Bind(object instance) und .Get<T>(), die nicht auf class eingeschränkt sind. Wenn Sie eine der Optionsschnittstellen verwenden, müssen Sie die erwähnten Einschränkungen für Optionsklassen beachten.

Eine alternative Vorgehensweise bei der Verwendung des Optionsmusters besteht darin, den Abschnitt "TransientFaultHandlingOptions" zu binden und ihn dem Dienstcontainer für die Abhängigkeitsinjektion hinzuzufügen. Im folgenden Code wird TransientFaultHandlingOptions mit Configure zum Dienstcontainer hinzugefügt und an die Konfiguration gebunden:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.Configure<TransientFaultHandlingOptions>(
    builder.Configuration.GetSection(
        key: nameof(TransientFaultHandlingOptions)));

Der builder im vorherigen Beispiel ist eine Instanz von HostApplicationBuilder.

Tipp

Der key-Parameter entspricht dem Namen des zu suchenden Konfigurationsabschnitts. Er muss nicht mit dem Namen des Typs übereinstimmen, der ihn darstellt. Beispiel: Ein Abschnitt namens "FaultHandling" wird durch die Klasse TransientFaultHandlingOptions dargestellt. In diesem Fall übergeben Sie "FaultHandling" an die Funktion GetSection. Der nameof-Operator wird verwendet, wenn der benannte Abschnitt mit dem Typ übereinstimmt, dem er entspricht.

Mithilfe des vorangehenden Codes liest der folgende Code die Positionsoptionen:

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}");
    }
}

Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App nicht gelesen. Nach dem Start der App auftretende Änderungen lesen Sie mit IOptionsSnapshot oder IOptionsMonitor, um Änderungen zu überwachen, während sie auftreten, und entsprechend zu reagieren.

Optionenschnittstellen

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

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.

IOptionsChangeTokenSource<TOptions> wird verwendet, um das IChangeToken abzurufen, das Änderungen an der zugrunde liegenden TOptions-Instanz nachverfolgt. Weitere Informationen zu Grundelementen von Änderungstoken finden Sie unter Änderungsbenachrichtigungen.

Vorteile von Optionsschnittstellen

Die Verwendung eines generischen Wrappertyps bietet Ihnen die Möglichkeit, die Lebensdauer der Option vom DI-Container zu entkoppeln. Die IOptions<TOptions>.Value-Schnittstelle stellt für Ihren Optionstyp eine Abstraktionsebene einschließlich allgemeiner Einschränkungen bereit. Dies bietet folgende Vorteile:

  • Die Auswertung der T-Konfigurationsinstanz wird auf den Zugriff von IOptions<TOptions>.Value verschoben und nicht zum Zeitpunkt der Injektion durchgeführt. Dies ist wichtig, weil Sie die Option T an verschiedenen Stellen nutzen und die Lebensdauersemantik auswählen können, ohne T zu verändern.
  • Beim Registrieren von Optionen des Typs T müssen Sie den Typ T nicht explizit registrieren. Dies ist beim Erstellen einer Bibliothek mit einfachen Standardeinstellungen hilfreich, wenn Sie den Aufrufer nicht zum Registrieren von Optionen im DI-Container mit einer bestimmten Lebensdauer zwingen möchten.
  • Aus Sicht der API sind hierdurch Einschränkungen für den Typ T möglich (in diesem Fall ist T auf einen Verweistyp beschränkt).

Verwenden von IOptionsSnapshot zum Lesen aktualisierter Daten

Bei Verwendung von IOptionsSnapshot<TOptions> werden Optionen einmal pro Anforderung (beim Zugriff) berechnet. Anschließend werden sie für die Lebensdauer der Anforderung zwischengespeichert. Ä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, da das IOptionsSnapshot<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>.

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}");
    }
}

Der folgende Code registriert eine Konfigurationsinstanz, mit der TransientFaultHandlingOptions eine Bindung herstellt:

builder.Services
    .Configure<TransientFaultHandlingOptions>(
        configurationRoot.GetSection(
            nameof(TransientFaultHandlingOptions)));

Im vorherigen Code wird die Configure<TOptions>-Methode verwendet, um eine Konfigurationsinstanz zu registrieren, an die TOptions gebunden wird, und die Optionen werden aktualisiert, wenn sich die Konfiguration ändert.

IOptionsMonitor

Um den Optionsmonitor zu verwenden, werden Optionsobjekte auf die gleiche Weise wie in einem Konfigurationsabschnitt konfiguriert.

builder.Services
    .Configure<TransientFaultHandlingOptions>(
        configurationRoot.GetSection(
            nameof(TransientFaultHandlingOptions)));

Im folgenden Beispiel wird IOptionsMonitor<TOptions> verwendet:

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}");
    }
}

Im vorangehenden Code werden Änderungen an der JSON-Konfigurationsdatei nach dem Start der App gelesen.

Tipp

Einige Dateisysteme, z.B. Docker-Container und Netzwerkfreigaben, senden Änderungsmeldungen möglicherweise nicht zuverlässig . Wenn Sie das IOptionsMonitor<TOptions> Interface in diesen Umgebungen verwenden, legen Sie die DOTNET_USE_POLLING_FILE_WATCHER Umgebungsvariable auf 1oder true fest, um das Dateisystem auf Änderungen hin abzufragen. Das Intervall, in dem Änderungen abgefragt werden, beträgt alle vier Sekunden und ist nicht konfigurierbar.

Weitere Informationen zu Docker-Containern finden Sie unter Containerisieren einer .NET-App.

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.

Sehen Sie sich die nachfolgende Datei appsettings.json an:

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

Anstatt zwei Klassen für die Bindung von Features:Personalize und Features:WeatherStation zu erstellen, wird folgende Klasse für jeden Abschnitt verwendet:

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; }
}

Der folgende Code dient zum Konfigurieren der benannten Optionen:

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"));

Der folgende Code zeigt die benannten Optionen an:

public class sealed Service
{
    private readonly Features _personalizeFeature;
    private readonly Features _weatherStationFeature;

    public Service(IOptionsSnapshot<Features> namedOptionsAccessor)
    {
        _personalizeFeature = namedOptionsAccessor.Get(Features.Personalize);
        _weatherStationFeature = namedOptionsAccessor.Get(Features.WeatherStation);
    }
}

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.

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 eines Konfigurationsdelegaten an Konfigurieren auf 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<ExampleService, ScopedService, MonitorService>(
            (options, es, ss, ms) =>
                options.Property = DoSomethingWith(es, ss, ms));
    
  • 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 Konfigurieren, da das Erstellen eines Diensts etwas komplexer ist. Das Erstellen eines Typs entspricht den Aktionen des Frameworks beim Aufruf von Configure. Wenn Konfigurieren aufgerufen wird, wird eine vorübergehende, generische IConfigureNamedOptions<TOptions> registriert, die über einen Konstruktor verfügt, der die angegeben generischen Diensttypen akzeptiert.

Überprüfung von Optionen

Bei der Überprüfung der Optionen können Optionswerte überprüft werden.

Sehen Sie sich die nachfolgende Datei appsettings.json an:

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

Die folgende Klasse erstellt eine Bindung zum "MyCustomSettingsSection"-Konfigurationsabschnitt und wendet einige DataAnnotations-Regeln an:

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 der vorangehenden SettingsOptions-Klasse enthält die Eigenschaft ConfigurationSectionName den Namen des Konfigurationsabschnitts, an die er gebunden werden soll. In diesem Szenario stellt das Optionsobjekt den Namen seines Konfigurationsabschnitts zur Verfügung.

Tipp

Der Name des Konfigurationsabschnitts ist unabhängig vom Konfigurationsobjekt, an das er gebunden ist. Anders ausgedrückt: Ein Konfigurationsabschnitt mit dem Namen "FooBarOptions" kann an ein Optionsobjekt namens ZedOptions gebunden werden. Obwohl es üblich ist, diese beiden Elemente gleich zu benennen, ist dies nicht notwendig und kann tatsächlich Namenskonflikte verursachen.

Der folgende Code

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations();

Die ValidateDataAnnotations-Erweiterungsmethode wird im NuGet-Paket Microsoft.Extensions.Options.DataAnnotations definiert.

Der folgende Code zeigt die Konfigurationswerte an oder meldet Überprüfungsfehler:

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);
            }
        }
    }
}

Der folgende Code wendet mithilfe eines Delegaten eine komplexere Überprüfungsregel an:

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.");

Die Überprüfung erfolgt zur Laufzeit, sie kann jedoch so konfiguriert werden, dass sie beim Start erfolgt, indem Sie stattdessen einen Aufruf an ValidateOnStart anhängen:

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();

Ab .NET 8 können Sie eine alternative API, AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String), verwenden, die die Überprüfung für einen bestimmten Optionstyp ermöglicht:

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 zur komplexen Überprüfung

Die folgende Klasse implementiert 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 ermöglicht das Verschieben des Überprüfungscodes in eine Klasse.

Hinweis

Dieser Beispielcode basiert auf dem NuGet-Paket Microsoft.Extensions.Configuration.Json.

Bei Verwendung des Codes oben wird die Validierung aktiviert, wenn Dienste mit dem folgenden Code konfiguriert werden:

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>());

Optionen für die Postkonfiguration

Legen Sie die Postkonfiguration mit IPostConfigureOptions<TOptions> fest. Die Postkonfiguration wird ausgeführt, nachdem die gesamte IConfigureOptions<TOptions>-Konfiguration erfolgt ist. Sie kann in Szenarien nützlich sein, in denen die Konfiguration außer Kraft gesetzt werden soll:

builder.Services.PostConfigure<CustomOptions>(customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

PostConfigure ist nach dem Konfigurieren der benannten Optionen verfügbar:

builder.Services.PostConfigure<CustomOptions>("named_options_1", customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

Verwenden Sie PostConfigureAll zur Postkonfiguration aller Konfigurationsinstanzen:

builder.Services.PostConfigureAll<CustomOptions>(customOptions =>
{
    customOptions.Option1 = "post_configured_option1_value";
});

Siehe auch