Udostępnij przez


Wzorzec opcji na platformie .NET

Wzorzec opcji używa klas w celu zapewnienia silnie typizowanego dostępu do grup powiązanych ustawień. Gdy ustawienia konfiguracji są izolowane według scenariusza w oddzielnych klasach, aplikacja jest zgodna z dwoma ważnymi zasadami inżynierii oprogramowania:

Opcje udostępniają również mechanizm sprawdzania poprawności danych konfiguracji. Aby uzyskać więcej informacji, zobacz sekcję Walidacja opcji.

Powiązanie konfiguracji hierarchicznej

Preferowanym sposobem odczytywania powiązanych wartości konfiguracji jest użycie wzorca opcji. Wzorzec opcji jest możliwy za pośrednictwem interfejsu IOptions<TOptions>, gdzie parametr typu ogólnego TOptions jest ograniczony do class klasy. Można IOptions<TOptions> później przekazać za pomocą wstrzykiwania zależności. Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności na platformie .NET.

Aby na przykład odczytać wyróżnione wartości konfiguracji z pliku appsettings.json :

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

Utwórz następującą klasę TransientFaultHandlingOptions:

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

W przypadku korzystania ze wzorca opcji klasa opcji:

  • Musi być nie abstrakcyjny z publicznym konstruktorem bez parametrów
  • Zawiera publiczne właściwości do odczytu i zapisu do powiązania (pola nie są powiązane)

Poniższy kod jest częścią pliku Program.cs C#i:

  • Wywołanie elementu ConfigurationBinder.Bind w celu powiązania klasy TransientFaultHandlingOptions z sekcją "TransientFaultHandlingOptions".
  • Wyświetlenie danych konfiguracji .
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:

W poprzednim kodzie sekcja "TransientFaultHandlingOptions" pliku konfiguracji JSON jest powiązana z wystąpieniem TransientFaultHandlingOptions. Spowoduje to nawodnienie właściwości obiektów języka C# odpowiednimi wartościami z konfiguracji.

Element ConfigurationBinder.Get<T> tworzy powiązanie i zwraca określony typ. Element ConfigurationBinder.Get<T> może być wygodniejszy w użyciu niż element ConfigurationBinder.Bind. W poniższym kodzie pokazano sposób użycia elementu ConfigurationBinder.Get<T> z klasą TransientFaultHandlingOptions:

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

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

W poprzednim kodzie ConfigurationBinder.Get<T> jest używany do uzyskania wystąpienia obiektu TransientFaultHandlingOptions z jego wartościami właściwości pochodzącymi z konfigurowania podstawowego.

Ważne

Klasa ConfigurationBinder uwidacznia kilka interfejsów API, takich jak .Bind(object instance) i .Get<T>(), które są nieograniczone do class. W przypadku korzystania z dowolnego interfejsu Opcje należy przestrzegać wyżej wymienionych ograniczeń klasy opcji.

Alternatywną metodą użycia wzorca opcji jest powiązanie "TransientFaultHandlingOptions" sekcji i dodanie jej do kontenera usług wstrzykiwania zależności. W poniższym kodzie element TransientFaultHandlingOptions jest dodawany do kontenera usługi przy użyciu Configure, a następnie jest tworzone jego powiązanie z konfiguracją:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

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

W poprzednim przykładzie builder jest przykładem HostApplicationBuilder.

Wskazówka

Parametr key jest nazwą sekcji konfiguracji do wyszukania. Nie musi być zgodna z nazwą typu, który go reprezentuje. Na przykład możesz mieć sekcję o nazwie "FaultHandling" i może być reprezentowana przez klasę TransientFaultHandlingOptions . W tym przypadku należy przekazać "FaultHandling" do funkcji GetSection. Operator nameof jest używany jako wygoda, gdy nazwana sekcja jest zgodna z typem, do którego odpowiada.

Przy użyciu poprzedniego kodu następujący kod odczytuje opcje położenia:

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

W poprzednim kodzie zmiany w pliku konfiguracji JSON po uruchomieniu aplikacji nie są odczytywane. Aby odczytać zmiany po uruchomieniu aplikacji, użyj funkcji IOptionsSnapshot lub IOptionsMonitor , aby monitorować zmiany w miarę ich występowania i odpowiednio reagować.

Interfejsy opcji

IOptions<TOptions>:

  • Nie obsługuje:
    • Odczytywanie danych konfiguracji po uruchomieniu aplikacji.
    • Opcje nazwane
  • Rejestrowany jest jako Singleton i może być wstrzykiwany do dowolnego cyklu życia usługi.

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

IOptionsFactory<TOptions> jest odpowiedzialny za tworzenie nowych wystąpień opcji. Ma jedną Create metodę. Domyślna implementacja pobiera wszystkie zarejestrowane IConfigureOptions<TOptions> i IPostConfigureOptions<TOptions> i najpierw uruchamia wszystkie konfiguracje, a następnie konfiguracje końcowe. Rozróżnia między elementami IConfigureNamedOptions<TOptions> i IConfigureOptions<TOptions> i wywołuje tylko odpowiedni interfejs.

IOptionsMonitorCache<TOptions> jest używany przez IOptionsMonitor<TOptions> program do buforowania TOptions wystąpień. Unieważnia IOptionsMonitorCache<TOptions> instancje opcji w monitorze, aby wartość została ponownie obliczona (TryRemove). Wartości można wprowadzić ręcznie za pomocą TryAdd. Metoda Clear jest używana, gdy wszystkie nazwane wystąpienia powinny być odtwarzane na żądanie.

IOptionsChangeTokenSource<TOptions> Służy do pobierania danych IChangeToken , które śledzą zmiany w wystąpieniu bazowym TOptions . Aby uzyskać więcej informacji na temat prymitywów tokenu zmiany, zobacz Powiadomienia o zmianach.

Korzyści z interfejsów opcji

Użycie ogólnego typu opakowania pozwala na uniezależnienie okresu istnienia opcji od kontenera DI (wstrzykiwania zależności). Interfejs IOptions<TOptions>.Value zapewnia warstwę abstrakcji, w tym ogólne ograniczenia, dla typu opcji. Oferuje to następujące korzyści:

  • Ocena wystąpienia konfiguracji T jest odroczona do uzyskania dostępu do IOptions<TOptions>.Value, a nie podczas wstrzykiwania. Jest to ważne, ponieważ można skorzystać T z opcji z różnych miejsc i wybrać semantykę czasu życia bez zmiany niczego o T.
  • Podczas rejestrowania opcji typu T, nie trzeba jawnie rejestrować typu T. Jest to wygoda, gdy tworzysz bibliotekę z prostymi wartościami domyślnymi i nie chcesz wymuszać, aby obiekt wywołujący zarejestrował opcje w kontenerze DI z określonym okresem istnienia.
  • Z perspektywy interfejsu API, umożliwia nałożenie ograniczeń na typ T (w tym przypadku T jest ograniczone do typu referencyjnego).

Odczytywanie zaktualizowanych danych przy użyciu funkcji IOptionsSnapshot

Przy użyciu IOptionsSnapshot<TOptions>, opcje są obliczane tylko raz przy każdym żądaniu, gdy są dostępne, i są buforowane przez cały czas trwania żądania. Zmiany konfiguracji są odczytywane po uruchomieniu aplikacji podczas korzystania z dostawców konfiguracji obsługujących odczytywanie zaktualizowanych wartości konfiguracji.

Różnica między elementami IOptionsMonitor i IOptionsSnapshot polega na tym, że:

  • IOptionsMonitor to pojedyncza usługa , która w dowolnym momencie pobiera bieżące wartości opcji, co jest szczególnie przydatne w zależnościach pojedynczych.
  • IOptionsSnapshotjest usługą o określonym zakresie i udostępnia migawkę opcji w momencie IOptionsSnapshot<T> konstruowania obiektu. Migawki ustawień są przeznaczone do użytku z zależnościami tymczasowymi i o określonym zakresie.

Poniższy kod używa metody 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}");
    }
}

Poniższy kod rejestruje instancję konfiguracji, która TransientFaultHandlingOptions powiązuje z:

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

W poprzednim kodzie Configure<TOptions> metoda służy do rejestrowania instancji konfiguracji, do której TOptions zostanie powiązany, i aktualizuje opcje po zmianie konfiguracji.

IOptionsMonitor

Typ IOptionsMonitor obsługuje powiadomienia o zmianie i umożliwia dynamiczne reagowanie aplikacji na zmiany źródła konfiguracji. Jest to przydatne, gdy trzeba reagować na zmiany w danych konfiguracji po uruchomieniu aplikacji. Powiadomienia o zmianach są obsługiwane tylko dla dostawców konfiguracji opartych na systemie plików, takich jak:

Aby użyć monitora opcji, obiekty opcji są konfigurowane w taki sam sposób w sekcji konfiguracji.

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

W poniższym przykładzie użyto metody IOptionsMonitor<TOptions>:

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class MonitorService(IOptionsMonitor<TransientFaultHandlingOptions> monitor)
{
    public void DisplayValues()
    {
        TransientFaultHandlingOptions options = monitor.CurrentValue;

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

W poprzednim kodzie zmiany w pliku konfiguracji JSON po uruchomieniu aplikacji są odczytywane.

Wskazówka

Niektóre systemy plików, takie jak kontenery platformy Docker i udziały sieciowe, mogą nie niezawodnie wysyłać powiadomień o zmianie. W przypadku korzystania z interfejsu IOptionsMonitor<TOptions> w tych środowiskach ustaw zmienną DOTNET_USE_POLLING_FILE_WATCHER środowiskową na 1 lub true sonduj system plików pod kątem zmian. Interwał sondowania zmian jest co cztery sekundy i nie można go konfigurować.

Aby uzyskać więcej informacji na temat kontenerów platformy Docker, zobacz Containerize a .NET app (Konteneryzowanie aplikacji .NET).

Obsługa opcji nazwanych przy użyciu funkcji IConfigureNamedOptions

Nazwane opcje:

  • Są przydatne, gdy wiele sekcji konfiguracji wiąże się z tymi samymi właściwościami.
  • Uwzględnia wielkość liter.

Rozważ następujący plik appsettings.json :

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

Zamiast tworzyć dwie klasy do powiązania Features:Personalize i Features:WeatherStation, dla każdej sekcji jest używana następująca klasa:

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

Poniższy kod konfiguruje nazwane opcje:

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

Poniższy kod wyświetla nazwane opcje:

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

Wszystkie opcje są nazwanymi wystąpieniami. IConfigureOptions<TOptions> wystąpienia są traktowane jako docelowe Options.DefaultName wystąpienie, czyli string.Empty. IConfigureNamedOptions<TOptions> również implementuje IConfigureOptions<TOptions>. Domyślna implementacja elementu IOptionsFactory<TOptions> zawiera logikę pozwalającą na odpowiednie użycie. Nazwana null opcja służy do określania wartości docelowej wszystkich nazwanych wystąpień zamiast określonego nazwanego wystąpienia. ConfigureAll i PostConfigureAll stosują tę konwencję.

OptionsBuilder API

OptionsBuilder<TOptions> służy do konfigurowania TOptions wystąpień. OptionsBuilder Ułatwia tworzenie nazwanych opcji, gdyż jest to tylko jeden parametr do początkowego wywołania AddOptions<TOptions>(string optionsName), zamiast pojawiać się w każdym z kolejnych wywołań. Opcje weryfikacji i przeciążenia ConfigureOptions, które akceptują zależności usług, są dostępne tylko za pośrednictwem OptionsBuilder.

OptionsBuilder jest używany w sekcji Walidacja opcji.

Konfigurowanie opcji przy użyciu usług DI

Podczas konfigurowania opcji można użyć wstrzykiwania zależności w celu uzyskania dostępu do zarejestrowanych usług i użyć ich do skonfigurowania opcji. Jest to przydatne, gdy konieczne jest uzyskanie dostępu do usług w celu skonfigurowania opcji. Dostęp do usług można uzyskać z di podczas konfigurowania opcji na dwa sposoby:

  • Przekaż delegata konfiguracji do Configure w OptionsBuilder<TOptions>. OptionsBuilder<TOptions> zapewnia przeciążenia metody Configure, które umożliwiają korzystanie z maksymalnie pięciu usług do konfiguracji opcji.

    builder.Services
        .AddOptions<MyOptions>("optionalName")
        .Configure<ExampleService, ScopedService, MonitorService>(
            (options, es, ss, ms) =>
                options.Property = DoSomethingWith(es, ss, ms));
    
  • Utwórz typ implementujący IConfigureOptions<TOptions> lub IConfigureNamedOptions<TOptions> rejestrujący typ jako usługę.

Zaleca się przekazanie delegata konfiguracyjnego do Configure, ponieważ tworzenie usługi jest bardziej złożone. Tworzenie typu jest równoważne z tym, co platforma wykonuje podczas wywoływania konfiguracji. Wywoływanie polecenia Configure powoduje zarejestrowanie przejściowego typu ogólnego IConfigureNamedOptions<TOptions>, którego konstruktor akceptuje określone typy usług ogólnych.

Walidacja opcji

Walidacja opcji umożliwia zweryfikowanie wartości opcji.

Rozważ następujący plik appsettings.json :

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

Następująca klasa wiąże się z sekcją "MyCustomSettingsSection" konfiguracji i stosuje kilka DataAnnotations reguł:

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

W poprzedniej SettingsOptions klasie ConfigurationSectionName właściwość zawiera nazwę sekcji konfiguracji, z która ma być powiązana. W tym scenariuszu obiekt options zawiera nazwę sekcji konfiguracji.

Wskazówka

Nazwa sekcji konfiguracji jest niezależna od obiektu konfiguracji, z którą jest powiązanie. Innymi słowy, sekcja konfiguracji o nazwie "FooBarOptions" może być powiązana z obiektem opcji o nazwie ZedOptions. Chociaż często nazywa się je tak samo, nie jest to konieczne i może powodować konflikty nazw.

Następujący kod:

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

Metoda rozszerzenia ValidateDataAnnotations jest zdefiniowana w pakiecie NuGet Microsoft.Extensions.Options.DataAnnotations.

Poniższy kod wyświetla wartości konfiguracji lub zgłasza błędy walidacji:

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

Poniższy kod stosuje bardziej złożoną regułę walidacji przy użyciu delegata:

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

Walidacja odbywa się w czasie wykonywania, ale można skonfigurować ją tak, aby występowała podczas uruchamiania, zestawiając wywołania do elementu 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();

Aby włączyć walidację przy uruchamianiu dla określonego typu opcji, użyj interfejsu AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String) API:

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 w przypadku złożonej weryfikacji

Następująca klasa implementuje IValidateOptions<TOptions>element :

using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

sealed partial class ValidateSettingsOptions(
    IConfiguration config)
    : IValidateOptions<SettingsOptions>
{
    public SettingsOptions? Settings { get; private set; } =
        config.GetSection(SettingsOptions.ConfigurationSectionName)
              .Get<SettingsOptions>();

    public ValidateOptionsResult Validate(string? name, SettingsOptions options)
    {
        StringBuilder? failure = null;
    
        if (!ValidationRegex().IsMatch(options.SiteTitle))
        {
            (failure ??= new()).AppendLine($"{options.SiteTitle} doesn't match RegEx");
        }

        if (options.Scale is < 0 or > 1_000)
        {
            (failure ??= new()).AppendLine($"{options.Scale} isn't within Range 0 - 1000");
        }

        if (Settings is { Scale: 0 } && Settings.VerbosityLevel <= Settings.Scale)
        {
            (failure ??= new()).AppendLine("VerbosityLevel must be > than Scale.");
        }

        return failure is not null
            ? ValidateOptionsResult.Fail(failure.ToString())
            : ValidateOptionsResult.Success;
    }

    [GeneratedRegex("^[a-zA-Z''-'\\s]{1,40}$")]
    private static partial Regex ValidationRegex();
}

IValidateOptions umożliwia przeniesienie kodu weryfikacji do klasy.

Uwaga

Ten przykładowy kod opiera się na pakiecie NuGet Microsoft.Extensions.Configuration.Json .

Korzystając z powyższego kodu, walidacja jest włączona podczas konfigurowania usług przy użyciu następującego kodu:

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

Rekursywna walidacja za pomocą polecenia ValidateObjectMembers i ValidateEnumeratedItems

Domyślnie DataAnnotations walidacja weryfikuje tylko właściwości samej klasy options. Nie rekursywnie weryfikuje zagnieżdżonych obiektów ani elementów w kolekcjach. Aby włączyć walidację rekursywną, użyj atrybutów ValidateObjectMembersAttribute i ValidateEnumeratedItemsAttribute.

Rozważ następujące zagnieżdżone klasy opcji:

public sealed class DatabaseOptions
{
    [Required]
    [MinLength(1)]
    public string ConnectionString { get; set; } = string.Empty;

    [Range(1, 100)]
    public int MaxRetries { get; set; } = 3;

    [Range(1, 300)]
    public int TimeoutSeconds { get; set; } = 30;
}
public sealed class ServerOptions
{
    [Required]
    [RegularExpression(@"^[a-zA-Z0-9\-\.]+$")]
    public string HostName { get; set; } = string.Empty;

    [Required]
    [Range(1, 65535)]
    public int Port { get; set; }
}
public sealed class ApplicationOptionsWithAttribute
{
    public const string ConfigurationSectionName = "ApplicationWithAttribute";

    [Required]
    public required string ApplicationName { get; set; }

    // Validation recurses into DatabaseOptions.
    [ValidateObjectMembers]
    public DatabaseOptions Database { get; set; } = new();

    // Validation recurses into each ServerOptions in the list.
    [ValidateEnumeratedItems]
    public List<ServerOptions> Servers { get; set; } = [];
}

W poprzednim kodzie właściwość Database jest obiektem zagnieżdżonym typu DatabaseOptions.

  • Bez zastosowania atrybutu [ValidateObjectMembers] do właściwości DatabaseOptions, atrybuty weryfikacji na jej właściwościach (na przykład na [Required]) nie byłyby oceniane. Po zastosowaniu [ValidateObjectMembers], walidacja jest również wykonywana rekurencyjnie w obrębie właściwości Database i weryfikuje jej członków zgodnie z ich DataAnnotations atrybutami.
  • Bez zastosowania atrybutu [ValidateEnumeratedItems] do właściwości kolekcji Servers, atrybuty walidacji poszczególnych elementów ServerOptions nie zostaną ocenione. Po zastosowaniu atrybutu [ValidateEnumeratedItems] każdy ServerOptions element na liście jest weryfikowany zgodnie z jego DataAnnotations atrybutami.

Wskazówka

Zarówno ValidateObjectMembersAttribute, jak i ValidateEnumeratedItemsAttribute pracują z generatorem źródłowym weryfikacji opcji w czasie kompilacji, aby zwiększyć wydajność. Aby uzyskać więcej informacji, zobacz Generowanie źródła walidacji opcji czasu kompilacji.

Opcje po konfiguracji

Ustaw konfigurację końcową używając IPostConfigureOptions<TOptions>. Po zakończeniu konfiguracji, działania pokonfiguracyjne IConfigureOptions<TOptions> mogą być przydatne w scenariuszach, w których trzeba nadpisać konfigurację:

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

PostConfigure jest dostępny do konfiguracji po ustawieniu opcji o nazwach:

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

Użyj PostConfigureAll, aby skonfigurować wszystkie wystąpienia konfiguracji.

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

Zobacz też