Model možností v .NET

Vzor možností používá třídy k zajištění přístupu silného typu ke skupinám souvisejících nastavení. Pokud jsou nastavení konfigurace izolované podle scénáře do samostatných tříd, aplikace dodržuje dva důležité principy softwarového inženýrství:

Možnosti také poskytují mechanismus pro ověření konfiguračních dat. Další informace najdete v části Možnosti ověření .

Vytvoření vazby hierarchické konfigurace

Upřednostňovaným způsobem čtení souvisejících hodnot konfigurace je použití vzoru možností. Vzor možností je možný prostřednictvím IOptions<TOptions> rozhraní, kde je parametr TOptions obecného typu omezen na class. Později ho IOptions<TOptions> můžete poskytnout prostřednictvím injektáže závislostí. Další informace naleznete v tématu Injektáž závislostí v .NET.

Pokud chcete například číst zvýrazněné konfigurační hodnoty ze souboru appsettings.json :

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

Vytvořte následující třídu TransientFaultHandlingOptions:

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

Při použití vzoru možností třída options:

  • Nesmí být abstraktní s veřejným konstruktorem bez parametrů.
  • Obsahují veřejné vlastnosti pro čtení i zápis pro vazbu (pole nejsou svázaná).

Následující kód je součástí souboru Program.cs C# a:

  • Zavolá metodu ConfigurationBinder.Bind pro svázání třídy TransientFaultHandlingOptions s oddílem "TransientFaultHandlingOptions".
  • Zobrazí konfigurační data pole .
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ConsoleJson.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Configuration.Sources.Clear();

IHostEnvironment env = builder.Environment;

builder.Configuration
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);

TransientFaultHandlingOptions options = new();
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
    .Bind(options);

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

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

// <Output>
// Sample output:

V předchozím kódu má konfigurační soubor JSON svůj "TransientFaultHandlingOptions" oddíl svázaný s TransientFaultHandlingOptions instancí. To hydratuje vlastnosti objektů jazyka C# s odpovídajícími hodnotami z konfigurace.

ConfigurationBinder.Get<T> naváže a vrátí určený typ. ConfigurationBinder.Get<T> může být pohodlnější než použití ConfigurationBinder.Bind. Následující kód ukazuje, jak používat ConfigurationBinder.Get<T> s třídou TransientFaultHandlingOptions:

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

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

V předchozím kódu ConfigurationBinder.Get<T> se používá k získání instance objektu TransientFaultHandlingOptions s jeho hodnotami vlastností naplněnými z podkladové konfigurace.

Důležité

Třída ConfigurationBinder zveřejňuje několik rozhraní API, například .Bind(object instance) a .Get<T>() které nejsou omezeny na .class Při použití některého z rozhraní Options musíte dodržovat výše uvedené možnosti omezení třídy.

Alternativním přístupem při použití vzoru možností je vytvořit vazbu oddílu "TransientFaultHandlingOptions" a přidat ho do kontejneru služby injektáže závislostí. V následujícím kódu se TransientFaultHandlingOptions přidá do kontejneru služby s příkazem Configure a sváže se s konfigurací:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

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

V builder předchozím příkladu je instance HostApplicationBuilder.

Tip

Parametr key je název oddílu konfigurace, který se má vyhledat. Nemusí odpovídat názvu typu, který ho představuje. Můžete mít například pojmenovaný "FaultHandling" oddíl, který může být reprezentován TransientFaultHandlingOptions třídou. V tomto případě byste funkci předali "FaultHandling"GetSection . Operátor nameof se používá jako pohodlí, když pojmenovaný oddíl odpovídá typu, který odpovídá.

Pomocí předchozího kódu přečte následující kód možnosti pozice:

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ExampleService(IOptions<TransientFaultHandlingOptions> options)
{
    private readonly TransientFaultHandlingOptions _options = options.Value;

    public void DisplayValues()
    {
        Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
        Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
    }
}

V předchozím kódu se změny konfiguračního souboru JSON po spuštění aplikace nepřečtou. Pokud chcete číst změny po spuštění aplikace, použijte IOptionsSnapshot nebo IOptionsMonitor k monitorování změn, jak k nim dojde, a odpovídajícím způsobem reagovat.

Rozhraní možností

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

IOptionsFactory<TOptions> zodpovídá za vytváření nových instancí možností. Má jednu Create metodu. Výchozí implementace vezme všechny registrované IConfigureOptions<TOptions> a IPostConfigureOptions<TOptions> nejprve spustí všechny konfigurace, za kterými následuje po konfiguraci. Rozlišuje mezi IConfigureNamedOptions<TOptions> rozhraním a IConfigureOptions<TOptions> volá pouze příslušné rozhraní.

IOptionsMonitorCache<TOptions> používá IOptionsMonitor<TOptions> se k ukládání instancí do mezipaměti TOptions . Instance IOptionsMonitorCache<TOptions> možností v monitorování zneplatní, aby byla hodnota přepočítána (TryRemove). Hodnoty lze ručně zavést s TryAdd. Metoda Clear se používá, když se všechny pojmenované instance mají znovu vytvořit na vyžádání.

IOptionsChangeTokenSource<TOptions> slouží k načtení IChangeToken sledování změn v podkladové TOptions instanci. Další informace o primitivech tokenů změn najdete v tématu Oznámení o změnách.

Výhody rozhraní možností

Použití obecného typu obálky umožňuje oddělit životnost možnosti od kontejneru DI. Rozhraní IOptions<TOptions>.Value poskytuje vrstvu abstrakce, včetně obecných omezení, pro váš typ možností. Přináší to následující výhody:

  • Vyhodnocení T instance konfigurace se odloží na přístup IOptions<TOptions>.Valuek instanci , nikoli při vkládání. To je důležité, protože můžete využívat T možnost z různých míst a zvolit sémantiku života beze změny čehokoli o T.
  • Při registraci možností typu Tnemusíte typ explicitně registrovat T . To je pohodlí při vytváření knihovny s jednoduchými výchozími nastaveními a nechcete vynutit volajícímu registraci možností do kontejneru DI s určitou životností.
  • Z pohledu rozhraní API umožňuje omezení typu T (v tomto případě T je omezena na typ odkazu).

Čtení aktualizovaných dat pomocí IOptionsSnapshot

Při použití IOptionsSnapshot<TOptions>se možnosti použijí jednou za požadavek při přístupu a po celou dobu platnosti požadavku se použijí v mezipaměti. Změny konfigurace se čtou po spuštění aplikace při použití zprostředkovatelů konfigurace, kteří podporují čtení aktualizovaných konfiguračních hodnot.

Rozdíl mezi IOptionsMonitor a IOptionsSnapshot je následující:

  • IOptionsMonitor je jednoúčelová služba , která načítá aktuální hodnoty možností kdykoli, což je zvlášť užitečné v jednoúčelových závislostech.
  • IOptionsSnapshot je vymezená služba a poskytuje snímek možností v době vytváření objektu IOptionsSnapshot<T> . Snímky možností jsou navržené pro použití s přechodnými a vymezenými závislostmi.

Následující kód používá IOptionsSnapshot<TOptions>.

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ScopedService(IOptionsSnapshot<TransientFaultHandlingOptions> options)
{
    private readonly TransientFaultHandlingOptions _options = options.Value;

    public void DisplayValues()
    {
        Console.WriteLine($"TransientFaultHandlingOptions.Enabled={_options.Enabled}");
        Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={_options.AutoRetryDelay}");
    }
}

Následující kód zaregistruje instanci konfigurace, která TransientFaultHandlingOptions se sváže s:

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

V předchozím kódu Configure<TOptions> se metoda používá k registraci instance konfigurace, která TOptions bude svázána s, a aktualizuje možnosti při změně konfigurace.

IOptionsMonitor

Pokud chcete použít monitorování možností, objekty možností se konfigurují stejným způsobem jako v části konfigurace.

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

Následující příklad používá IOptionsMonitor<TOptions>:

using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

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

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

V předchozím kódu se změny konfiguračního souboru JSON po spuštění aplikace načtou.

Tip

Některé systémy souborů, jako jsou kontejnery Dockeru a sdílené síťové složky, nemusí spolehlivě odesílat oznámení o změnách. Při použití IOptionsMonitor<TOptions> rozhraní v těchto prostředích nastavte DOTNET_USE_POLLING_FILE_WATCHER proměnnou prostředí na 1 systém souborů nebo true dotazujte systém souborů na změny. Interval, ve kterém se změny dotazují, je každých čtyři sekundy a není konfigurovatelný.

Další informace o kontejnerech Dockeru najdete v tématu Kontejnerizace aplikace .NET.

Podpora pojmenovaných možností pomocí IConfigureNamedOptions

Pojmenované možnosti:

  • Jsou užitečné, když se několik oddílů konfigurace sváže se stejnými vlastnostmi.
  • Rozlišují se malá a velká písmena.

Zvažte následující appsettings.json soubor:

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

Místo vytvoření dvou tříd pro vytvoření vazby Features:Personalize a Features:WeatherStationpro každou část se používá následující třída:

public class Features
{
    public const string Personalize = nameof(Personalize);
    public const string WeatherStation = nameof(WeatherStation);

    public bool Enabled { get; set; }
    public string ApiKey { get; set; }
}

Následující kód nakonfiguruje pojmenované možnosti:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Omitted for brevity...

builder.Services.Configure<Features>(
    Features.Personalize,
    builder.Configuration.GetSection("Features:Personalize"));

builder.Services.Configure<Features>(
    Features.WeatherStation,
    builder.Configuration.GetSection("Features:WeatherStation"));

Následující kód zobrazí pojmenované možnosti:

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

Všechny možnosti jsou pojmenované instance. IConfigureOptions<TOptions> instance jsou považovány za cíl instance Options.DefaultName , což je string.Empty. IConfigureNamedOptions<TOptions> implementuje IConfigureOptions<TOptions>také . Výchozí implementace má logiku IOptionsFactory<TOptions> , která se má použít odpovídajícím způsobem. Pojmenovaná null možnost se používá k cílení na všechny pojmenované instance místo na konkrétní pojmenovanou instanci. ConfigureAll a PostConfigureAll používat tuto konvenci.

OptionsBuilder API

OptionsBuilder<TOptions> slouží ke konfiguraci TOptions instancí. OptionsBuilder zjednodušuje vytváření pojmenovaných možností, protože se jedná pouze o jediný parametr počátečního AddOptions<TOptions>(string optionsName) volání místo toho, aby se zobrazoval ve všech následných voláních. Možnosti ověřování a ConfigureOptions přetížení, která přijímají závislosti služby, jsou k dispozici pouze prostřednictvím OptionsBuilder.

OptionsBuilder se používá v části Ověřování možností.

Konfigurace možností pomocí služeb DI

Ke službám je možné přistupovat prostřednictvím injektáže závislostí a současně konfigurovat možnosti dvěma způsoby:

  • Předejte delegáta konfigurace pro konfiguraci v možnostech TOptions> nástroje OptionsBuilder<. OptionsBuilder<TOptions> poskytuje přetížení konfigurace , které umožňují použití až pěti služeb ke konfiguraci možností:

    builder.Services
        .AddOptions<MyOptions>("optionalName")
        .Configure<ExampleService, ScopedService, MonitorService>(
            (options, es, ss, ms) =>
                options.Property = DoSomethingWith(es, ss, ms));
    
  • Vytvořte typ, který implementuje IConfigureOptions<TOptions> nebo IConfigureNamedOptions<TOptions> zaregistruje typ jako službu.

Doporučujeme předat delegáta konfigurace ke konfiguraci, protože vytvoření služby je složitější. Vytvoření typu odpovídá tomu, co architektura dělá při volání Konfigurace. Volání Konfigurace registruje přechodný obecný IConfigureNamedOptions<TOptions>, který má konstruktor, který přijímá obecné typy služby zadané.

Ověřování možností

Ověření možností umožňuje ověření hodnot možností.

Zvažte následující appsettings.json soubor:

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

Následující třída vytvoří vazbu na "MyCustomSettingsSection" oddíl konfigurace a použije několik DataAnnotations pravidel:

using System.ComponentModel.DataAnnotations;

namespace ConsoleJson.Example;

public sealed class SettingsOptions
{
    public const string ConfigurationSectionName = "MyCustomSettingsSection";

    [Required]
    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public required string SiteTitle { get; set; }

    [Required]
    [Range(0, 1_000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public required int Scale { get; set; }

    [Required]
    public required int VerbosityLevel { get; set; }
}

V předchozí SettingsOptions třídě ConfigurationSectionName obsahuje vlastnost název oddílu konfigurace, ke kterému se má vytvořit vazba. V tomto scénáři objekt options poskytuje název jeho konfiguračního oddílu.

Tip

Název oddílu konfigurace je nezávislý na objektu konfigurace, ke kterému je vázán. Jinými slovy, oddíl konfigurace pojmenovaný "FooBarOptions" může být vázán na objekt možností s názvem ZedOptions. I když může být běžné je pojmenovat stejně, není to nutné a může ve skutečnosti způsobit konflikty názvů.

Následující kód:

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

Metoda ValidateDataAnnotations rozšíření je definována v balíčku NuGet Microsoft.Extensions.Options.DataAnnotations .

Následující kód zobrazí konfigurační hodnoty nebo hlásí chyby ověření:

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ConsoleJson.Example;

public sealed class ValidationService
{
    private readonly ILogger<ValidationService> _logger;
    private readonly IOptions<SettingsOptions> _config;

    public ValidationService(
        ILogger<ValidationService> logger,
        IOptions<SettingsOptions> config)
    {
        _config = config;
        _logger = logger;

        try
        {
            SettingsOptions options = _config.Value;
        }
        catch (OptionsValidationException ex)
        {
            foreach (string failure in ex.Failures)
            {
                _logger.LogError("Validation error: {FailureMessage}", failure);
            }
        }
    }
}

Následující kód používá složitější ověřovací pravidlo pomocí delegáta:

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.");

Ověření probíhá za běhu, ale můžete ho nakonfigurovat tak, aby k němu došlo při spuštění, a to tak, že místo toho zřetězíte volání ValidateOnStartna:

builder.Services
    .AddOptions<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.")
    .ValidateOnStart();

Počínaje rozhraním .NET 8 můžete použít alternativní rozhraní API, AddOptionsWithValidateOnStart<TOptions>(IServiceCollection, String)které umožňuje ověřování při spuštění pro konkrétní typ možností:

builder.Services
    .AddOptionsWithValidateOnStart<SettingsOptions>()
    .Bind(Configuration.GetSection(SettingsOptions.ConfigurationSectionName))
    .ValidateDataAnnotations()
    .Validate(config =>
    {
        if (config.Scale != 0)
        {
            return config.VerbosityLevel > config.Scale;
        }

        return true;
    }, "VerbosityLevel must be > than Scale.");

IValidateOptions pro komplexní ověřování

Následující třída implementuje IValidateOptions<TOptions>:

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

namespace ConsoleJson.Example;

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

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

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

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

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

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

IValidateOptions umožňuje přesun ověřovacího kódu do třídy.

Poznámka:

Tento ukázkový kód spoléhá na balíček NuGet Microsoft.Extensions.Configuration.Json .

Při konfiguraci služeb s následujícím kódem se povolí ověření pomocí předchozího kódu:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Omitted for brevity...

builder.Services.Configure<SettingsOptions>(
    builder.Configuration.GetSection(
        SettingsOptions.ConfigurationSectionName));

builder.Services.TryAddEnumerable(
    ServiceDescriptor.Singleton
        <IValidateOptions<SettingsOptions>, ValidateSettingsOptions>());

Možnosti po konfiguraci

Nastavte po konfiguraci pomocí parametru IPostConfigureOptions<TOptions>. Po dokončení konfigurace se spustí všechna IConfigureOptions<TOptions> konfigurace a může být užitečná ve scénářích, kdy potřebujete přepsat konfiguraci:

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

PostConfigure je k dispozici pro pokonfigurované pojmenované možnosti:

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

Slouží PostConfigureAll k následné konfiguraci všech instancí konfigurace:

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

Viz také