Wzorzec opcji na platformie ASP.NET Core

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Autor: Rick Anderson.

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:

  • Hermetyzacja:
    • Klasy zależne od ustawień konfiguracji zależą tylko od używanych ustawień konfiguracji.
  • Separacja zagadnień:
    • Ustawienia dla różnych części aplikacji nie są zależne ani powiązane ze sobą.

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

Ten artykuł zawiera informacje na temat wzorca opcji w programie ASP.NET Core. Aby uzyskać informacje na temat używania wzorca opcji w aplikacjach konsoli, zobacz Wzorzec opcji na platformie .NET.

Konfiguracja hierarchiczna powiązania

Preferowanym sposobem odczytywania powiązanych wartości konfiguracji jest użycie wzorca opcji. Aby na przykład odczytać następujące wartości konfiguracji:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

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

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

Klasa opcji:

  • Musi być nieskonstrakcji.
  • Ma publiczne właściwości odczytu i zapisu typu, które mają odpowiednie elementy w konfiguracji są powiązane.
  • Ma właściwości odczytu i zapisu powiązane z pasującymi wpisami w konfiguracji.
  • Nie ma powiązanych pól. W poprzednim kodzie element Position nie jest powiązany. Dzięki użyciu pola Position ciąg "Position" nie musi być ustalony w aplikacji w przypadku tworzenia powiązania klasy z dostawcą konfiguracji.

Następujący kod powoduje:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ą PositionOptions:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

Powiązanie umożliwia również konkrecję klasy abstrakcyjnej. Rozważ następujący kod, który używa klasy SomethingWithANameabstrakcyjnej :

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

Poniższy kod wyświetla NameTitleOptions wartości konfiguracji:

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

Wywołania do elementu Bind są mniej rygorystyczne niż wywołania do elementu Get<>:

  • Bind umożliwia konkrecję abstrakcyjnego.
  • Get<> musi utworzyć samo wystąpienie.

Wzorzec opcji

Alternatywne podejście w przypadku korzystania z wzorca opcji to utworzenie powiązania sekcji Position i dodanie go do kontenera usługi wstrzykiwania zależności. W poniższym kodzie element PositionOptions jest dodawany do kontenera usługi przy użyciu Configure, a następnie jest tworzone jego powiązanie z konfiguracją:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON wprowadzone po uruchomieniu aplikacji nie są odczytywane. Aby odczytać zmiany po uruchomieniu aplikacji, użyj elementu IOptionsSnapshot.

Interfejsy opcji

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenariusze po konfiguracji umożliwiają ustawienie lub zmianę opcji po zakończeniu wszystkich IConfigureOptions<TOptions> konfiguracji.

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> uruchamia najpierw wszystkie konfiguracje, a następnie po konfiguracji. 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> wystąpienia opcji w monitorze, aby wartość została ponownie skompilowana (TryRemove). Wartości można wprowadzić ręcznie za pomocą TryAddpolecenia . Metoda Clear jest używana, gdy wszystkie nazwane wystąpienia powinny być ponownie tworzone na żądanie.

Odczytywanie zaktualizowanych danych przy użyciu funkcji IOptionsSnapshot

Za pomocą polecenia IOptionsSnapshot<TOptions>:

  • Opcje są obliczane raz na żądanie w przypadku uzyskania dostępu do żądania i buforowania przez okres istnienia żądania.
  • Może spowodować znaczną karę za wydajność, ponieważ jest to usługa o określonym zakresie i jest ponownie obliczana na żądanie. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub i Zwiększ wydajność powiązania konfiguracji.
  • 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:

  • IOptionsMonitorjest usługą Singleton, 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 opcji są przeznaczone do użytku z zależnościami przejściowymi i o określonym zakresie.

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

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions wiąże się z:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

W poprzednim kodzie zmiany w JSpliku konfiguracji ON po uruchomieniu aplikacji są odczytywane.

IOptionsMonitor

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions jest powiązane z.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ż użycie następującego pliku appsettings.json:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

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

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

Poniższy kod konfiguruje nazwane opcje:

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

Poniższy kod wyświetla nazwane opcje:

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

Wszystkie opcje są nazwane wystąpienia. IConfigureOptions<TOptions> wystąpienia są traktowane jako docelowe Options.DefaultName wystąpienie, czyli string.Empty. IConfigureNamedOptions<TOptions> implementuje IConfigureOptions<TOptions>również funkcję . Domyślna implementacja elementu IOptionsFactory<TOptions> ma logikę do użycia odpowiednio. 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 stosować tę konwencję.

OptionsBuilder API

OptionsBuilder<TOptions> służy do konfigurowania TOptions wystąpień. OptionsBuilder Usprawnia tworzenie nazwanych opcji, ponieważ jest to tylko jeden parametr do początkowego AddOptions<TOptions>(string optionsName) wywołania zamiast pojawiać się we wszystkich kolejnych wywołaniach. Opcje weryfikacji i ConfigureOptions przeciążenia akceptujące zależności usługi są dostępne tylko za pośrednictwem .OptionsBuilder

OptionsBuilderjest używany w sekcji Walidacja opcji.

Aby uzyskać informacje na temat dodawania repozytorium niestandardowego, zobacz Use AddOptions (Używanie polecenia AddOptions do konfigurowania repozytorium niestandardowego).

Konfigurowanie opcji przy użyciu usług DI

Dostęp do usług można uzyskać z iniekcji zależności podczas konfigurowania opcji na dwa sposoby:

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

    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));
    
  • Utwórz typ implementujący IConfigureOptions<TOptions> lub IConfigureNamedOptions<TOptions> rejestrujący typ jako usługę.

Zalecamy przekazanie delegata konfiguracji do Configureelementu , ponieważ tworzenie usługi jest bardziej złożone. Tworzenie typu jest równoważne z tym, co robi platforma podczas wywoływania metody Configure. Wywoływanie Configure powoduje zarejestrowanie przejściowego ogólnego IConfigureNamedOptions<TOptions>, który ma konstruktor, który akceptuje określone typy usług ogólnych.

Walidacja opcji

Walidacja opcji umożliwia zweryfikowanie wartości opcji.

Rozważ użycie następującego pliku appsettings.json:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

Następująca klasa służy do powiązania z sekcją "MyConfig" konfiguracji i stosuje kilka DataAnnotations reguł:

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

Następujący kod powoduje:

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

Metoda rozszerzenia jest definiowana ValidateDataAnnotations w pakiecie NuGet Microsoft.Extensions.Options.DataAnnotations . W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web zestawu SDK ten pakiet jest odwołuje się niejawnie z udostępnionej platformy.

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

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

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

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> i IValidatableObject

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

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 umożliwia przeniesienie kodu weryfikacji z Program.cs klasy i do klasy.

Korzystając z powyższego kodu, walidacja jest włączona przy Program.cs użyciu następującego kodu:

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

Walidacja opcji obsługuje również funkcję IValidatableObject. Aby przeprowadzić walidację klasy na poziomie klasy w obrębie samej klasy:

ValidateOnStart

Walidacja opcji jest uruchamiana po raz pierwszy IOptions<TOptions>podczas tworzenia implementacji , IOptionsSnapshot<TOptions>lub IOptionsMonitor<TOptions> . Aby uruchomić walidację opcji z niecierpliwością, po uruchomieniu aplikacji wywołaj metodę w pliku ValidateOnStartProgram.cs:

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Opcje po konfiguracji

Ustaw konfigurację po konfiguracji za pomocą polecenia IPostConfigureOptions<TOptions>. Po zakończeniu konfiguracji wszystkie IConfigureOptions<TOptions> konfiguracje są uruchamiane:

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 program jest dostępny do po skonfigurowaniu nazwanych opcji:

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

Użyj PostConfigureAll polecenia , aby po skonfigurowaniu wszystkich wystąpień konfiguracji:

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

Opcje dostępu w programie Program.cs

Aby uzyskać dostęp do IOptions<TOptions> polecenia lub IOptionsMonitor<TOptions> w Program.csusłudze , wywołaj metodę GetRequiredService :WebApplication.Services

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

Dodatkowe zasoby

Przez Kirk Larkin i Rick Anderson.

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:

  • Hermetyzacja:
    • Klasy zależne od ustawień konfiguracji zależą tylko od używanych ustawień konfiguracji.
  • Separacja zagadnień:
    • Ustawienia dla różnych części aplikacji nie są zależne ani powiązane ze sobą.

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

Ten artykuł zawiera informacje na temat wzorca opcji w programie ASP.NET Core. Aby uzyskać informacje na temat używania wzorca opcji w aplikacjach konsoli, zobacz Wzorzec opcji na platformie .NET.

Konfiguracja hierarchiczna powiązania

Preferowanym sposobem odczytywania powiązanych wartości konfiguracji jest użycie wzorca opcji. Aby na przykład odczytać następujące wartości konfiguracji:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

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

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

Klasa opcji:

  • Musi być nieabstrakcyjna z publicznym konstruktorem bez parametrów.
  • Wszystkie publiczne właściwości typu do odczytu i zapisu są powiązane.
  • Pola nie są powiązane. W poprzednim kodzie element Position nie jest powiązany. Dzięki użyciu pola Position ciąg "Position" nie musi być ustalony w aplikacji w przypadku tworzenia powiązania klasy z dostawcą konfiguracji.

Następujący kod powoduje:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ą PositionOptions:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

Alternatywne podejście w przypadku korzystania z wzorca opcji to utworzenie powiązania sekcji Position i dodanie go do kontenera usługi wstrzykiwania zależności. W poniższym kodzie element PositionOptions jest dodawany do kontenera usługi przy użyciu Configure, a następnie jest tworzone jego powiązanie z konfiguracją:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON wprowadzone po uruchomieniu aplikacji nie są odczytywane. Aby odczytać zmiany po uruchomieniu aplikacji, użyj elementu IOptionsSnapshot.

Interfejsy opcji

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenariusze po konfiguracji umożliwiają ustawienie lub zmianę opcji po zakończeniu wszystkich IConfigureOptions<TOptions> konfiguracji.

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> uruchamia najpierw wszystkie konfiguracje, a następnie po konfiguracji. 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> wystąpienia opcji w monitorze, aby wartość została ponownie skompilowana (TryRemove). Wartości można wprowadzić ręcznie za pomocą TryAddpolecenia . Metoda Clear jest używana, gdy wszystkie nazwane wystąpienia powinny być ponownie tworzone na żądanie.

Odczytywanie zaktualizowanych danych przy użyciu funkcji IOptionsSnapshot

Za pomocą polecenia IOptionsSnapshot<TOptions>:

  • Opcje są obliczane raz na żądanie w przypadku uzyskania dostępu do żądania i buforowania przez okres istnienia żądania.
  • Może spowodować znaczną karę za wydajność, ponieważ jest to usługa o określonym zakresie i jest ponownie obliczana na żądanie. Aby uzyskać więcej informacji, zobacz ten problem z usługą GitHub i Zwiększ wydajność powiązania konfiguracji.
  • 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:

  • IOptionsMonitorjest usługą Singleton, 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 opcji są przeznaczone do użytku z zależnościami przejściowymi i o określonym zakresie.

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

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions wiąże się z:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

W poprzednim kodzie zmiany w JSpliku konfiguracji ON po uruchomieniu aplikacji są odczytywane.

IOptionsMonitor

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions jest powiązane z.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ż użycie następującego pliku appsettings.json:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

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

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

Poniższy kod konfiguruje nazwane opcje:

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

Poniższy kod wyświetla nazwane opcje:

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

Wszystkie opcje są nazwane wystąpienia. IConfigureOptions<TOptions> wystąpienia są traktowane jako docelowe Options.DefaultName wystąpienie, czyli string.Empty. IConfigureNamedOptions<TOptions> implementuje IConfigureOptions<TOptions>również funkcję . Domyślna implementacja elementu IOptionsFactory<TOptions> ma logikę do użycia odpowiednio. 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 stosować tę konwencję.

OptionsBuilder API

OptionsBuilder<TOptions> służy do konfigurowania TOptions wystąpień. OptionsBuilder Usprawnia tworzenie nazwanych opcji, ponieważ jest to tylko jeden parametr do początkowego AddOptions<TOptions>(string optionsName) wywołania zamiast pojawiać się we wszystkich kolejnych wywołaniach. Opcje weryfikacji i ConfigureOptions przeciążenia akceptujące zależności usługi są dostępne tylko za pośrednictwem .OptionsBuilder

OptionsBuilderjest używany w sekcji Walidacja opcji.

Aby uzyskać informacje na temat dodawania repozytorium niestandardowego, zobacz Use AddOptions (Używanie polecenia AddOptions do konfigurowania repozytorium niestandardowego).

Konfigurowanie opcji przy użyciu usług DI

Dostęp do usług można uzyskać z iniekcji zależności podczas konfigurowania opcji na dwa sposoby:

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

    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));
    
  • Utwórz typ implementujący IConfigureOptions<TOptions> lub IConfigureNamedOptions<TOptions> rejestrujący typ jako usługę.

Zalecamy przekazanie delegata konfiguracji do Configureelementu , ponieważ tworzenie usługi jest bardziej złożone. Tworzenie typu jest równoważne z tym, co robi platforma podczas wywoływania metody Configure. Wywoływanie Configure powoduje zarejestrowanie przejściowego ogólnego IConfigureNamedOptions<TOptions>, który ma konstruktor, który akceptuje określone typy usług ogólnych.

Walidacja opcji

Walidacja opcji umożliwia zweryfikowanie wartości opcji.

Rozważ użycie następującego pliku appsettings.json:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

Następująca klasa służy do powiązania z sekcją "MyConfig" konfiguracji i stosuje kilka DataAnnotations reguł:

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

Następujący kod powoduje:

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

Metoda rozszerzenia jest definiowana ValidateDataAnnotations w pakiecie NuGet Microsoft.Extensions.Options.DataAnnotations . W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web zestawu SDK ten pakiet jest odwołuje się niejawnie z udostępnionej platformy.

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

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

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

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> i IValidatableObject

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

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 umożliwia przeniesienie kodu weryfikacji z Program.cs klasy i do klasy.

Korzystając z powyższego kodu, walidacja jest włączona przy Program.cs użyciu następującego kodu:

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

Walidacja opcji obsługuje również funkcję IValidatableObject. Aby przeprowadzić walidację klasy na poziomie klasy w obrębie samej klasy:

ValidateOnStart

Walidacja opcji jest uruchamiana po raz pierwszy IOptions<TOptions>podczas tworzenia implementacji , IOptionsSnapshot<TOptions>lub IOptionsMonitor<TOptions> . Aby uruchomić walidację opcji z niecierpliwością, po uruchomieniu aplikacji wywołaj metodę w pliku ValidateOnStartProgram.cs:

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Opcje po konfiguracji

Ustaw konfigurację po konfiguracji za pomocą polecenia IPostConfigureOptions<TOptions>. Po zakończeniu konfiguracji wszystkie IConfigureOptions<TOptions> konfiguracje są uruchamiane:

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 program jest dostępny do po skonfigurowaniu nazwanych opcji:

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

Użyj PostConfigureAll polecenia , aby po skonfigurowaniu wszystkich wystąpień konfiguracji:

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

Opcje dostępu w programie Program.cs

Aby uzyskać dostęp do IOptions<TOptions> polecenia lub IOptionsMonitor<TOptions> w Program.csusłudze , wywołaj metodę GetRequiredService :WebApplication.Services

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

Dodatkowe zasoby

Przez Kirk Larkin i Rick Anderson.

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:

  • Hermetyzacja:
    • Klasy zależne od ustawień konfiguracji zależą tylko od używanych ustawień konfiguracji.
  • Separacja zagadnień:
    • Ustawienia dla różnych części aplikacji nie są zależne ani powiązane ze sobą.

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

Ten temat zawiera informacje na temat wzorca opcji w programie ASP.NET Core. Aby uzyskać informacje na temat używania wzorca opcji w aplikacjach konsoli, zobacz Wzorzec opcji na platformie .NET.

Wyświetl lub pobierz przykładowy kod (jak pobrać)

Konfiguracja hierarchiczna powiązania

Preferowanym sposobem odczytywania powiązanych wartości konfiguracji jest użycie wzorca opcji. Aby na przykład odczytać następujące wartości konfiguracji:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

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

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

Klasa opcji:

  • Musi być nieabstrakcyjna z publicznym konstruktorem bez parametrów.
  • Wszystkie publiczne właściwości typu do odczytu i zapisu są powiązane.
  • Pola nie są powiązane. W poprzednim kodzie element Position nie jest powiązany. Dzięki użyciu właściwości Position ciąg "Position" nie musi być ustalony w aplikacji w przypadku tworzenia powiązania klasy z dostawcą konfiguracji.

Następujący kod powoduje:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ą PositionOptions:

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

Alternatywne podejście w przypadku korzystania z wzorca opcji to utworzenie powiązania sekcji Position i dodanie go do kontenera usługi wstrzykiwania zależności. W poniższym kodzie element PositionOptions jest dodawany do kontenera usługi przy użyciu Configure, a następnie jest tworzone jego powiązanie z konfiguracją:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON wprowadzone po uruchomieniu aplikacji nie są odczytywane. Aby odczytać zmiany po uruchomieniu aplikacji, użyj elementu IOptionsSnapshot.

Interfejsy opcji

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenariusze po konfiguracji umożliwiają ustawienie lub zmianę opcji po zakończeniu wszystkich IConfigureOptions<TOptions> konfiguracji.

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> uruchamia najpierw wszystkie konfiguracje, a następnie po konfiguracji. 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> wystąpienia opcji w monitorze, aby wartość została ponownie skompilowana (TryRemove). Wartości można wprowadzić ręcznie za pomocą TryAddpolecenia . Metoda Clear jest używana, gdy wszystkie nazwane wystąpienia powinny być ponownie tworzone na żądanie.

Odczytywanie zaktualizowanych danych przy użyciu funkcji IOptionsSnapshot

Przy użyciu IOptionsSnapshot<TOptions>opcji opcje są obliczane raz na żądanie w przypadku uzyskania dostępu do żądania i buforowania przez okres istnienia żą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:

  • IOptionsMonitorjest usługą Singleton, 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 opcji są przeznaczone do użytku z zależnościami przejściowymi i o określonym zakresie.

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

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions wiąże się z:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

W poprzednim kodzie zmiany w JSpliku konfiguracji ON po uruchomieniu aplikacji są odczytywane.

IOptionsMonitor

Poniższy kod rejestruje wystąpienie konfiguracji, które MyOptions jest powiązane z.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

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

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

W poprzednim kodzie zmiany pliku konfiguracji JSON są domyślnie odczytywane po uruchomieniu aplikacji.

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ż użycie następującego pliku appsettings.json:

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

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

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; }
    public string Model { get; set; }
}

Poniższy kod konfiguruje nazwane opcje:

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

Poniższy kod wyświetla nazwane opcje:

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

Wszystkie opcje są nazwane wystąpienia. IConfigureOptions<TOptions> wystąpienia są traktowane jako docelowe Options.DefaultName wystąpienie, czyli string.Empty. IConfigureNamedOptions<TOptions> implementuje IConfigureOptions<TOptions>również funkcję . Domyślna implementacja elementu IOptionsFactory<TOptions> ma logikę do użycia odpowiednio. 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 stosować tę konwencję.

OptionsBuilder API

OptionsBuilder<TOptions> służy do konfigurowania TOptions wystąpień. OptionsBuilder Usprawnia tworzenie nazwanych opcji, ponieważ jest to tylko jeden parametr do początkowego AddOptions<TOptions>(string optionsName) wywołania zamiast pojawiać się we wszystkich kolejnych wywołaniach. Opcje weryfikacji i ConfigureOptions przeciążenia akceptujące zależności usługi są dostępne tylko za pośrednictwem .OptionsBuilder

OptionsBuilderjest używany w sekcji Walidacja opcji.

Aby uzyskać informacje na temat dodawania repozytorium niestandardowego, zobacz Use AddOptions (Używanie polecenia AddOptions do konfigurowania repozytorium niestandardowego).

Konfigurowanie opcji przy użyciu usług DI

Dostęp do usług można uzyskać z iniekcji zależności podczas konfigurowania opcji na dwa sposoby:

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

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Utwórz typ implementujący IConfigureOptions<TOptions> lub IConfigureNamedOptions<TOptions> rejestrujący typ jako usługę.

Zalecamy przekazanie delegata konfiguracji do Configureelementu , ponieważ tworzenie usługi jest bardziej złożone. Tworzenie typu jest równoważne z tym, co robi platforma podczas wywoływania metody Configure. Wywoływanie Configure powoduje zarejestrowanie przejściowego ogólnego IConfigureNamedOptions<TOptions>, który ma konstruktor, który akceptuje określone typy usług ogólnych.

Walidacja opcji

Walidacja opcji umożliwia zweryfikowanie wartości opcji.

Rozważ użycie następującego pliku appsettings.json:

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

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

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

Następujący kod powoduje:

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

Metoda rozszerzenia jest definiowana ValidateDataAnnotations w pakiecie NuGet Microsoft.Extensions.Options.DataAnnotations . W przypadku aplikacji internetowych korzystających z Microsoft.NET.Sdk.Web zestawu SDK ten pakiet jest odwołuje się niejawnie z udostępnionej platformy.

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

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

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

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 na potrzeby złożonej weryfikacji

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

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 umożliwia przeniesienie kodu weryfikacji z StartUp klasy i do klasy.

Korzystając z powyższego kodu, walidacja jest włączona przy Startup.ConfigureServices użyciu następującego kodu:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

Opcje po konfiguracji

Ustaw konfigurację po konfiguracji za pomocą polecenia IPostConfigureOptions<TOptions>. Po zakończeniu konfiguracji wszystkie IConfigureOptions<TOptions> konfiguracje są uruchamiane:

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure program jest dostępny do po skonfigurowaniu nazwanych opcji:

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Użyj PostConfigureAll polecenia , aby po skonfigurowaniu wszystkich wystąpień konfiguracji:

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Uzyskiwanie dostępu do opcji podczas uruchamiania

IOptions<TOptions> i IOptionsMonitor<TOptions> może być używany w programie Startup.Configure, ponieważ usługi są tworzone przed wykonaniem Configure metody.

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

Nie używaj ani IOptions<TOptions>IOptionsMonitor<TOptions> w elemecie Startup.ConfigureServices. Może istnieć niespójny stan opcji ze względu na kolejność rejestracji usług.

Options.ConfigurationExtensions pakiet NuGet

Pakiet Microsoft.Extensions.Options.ConfigurationExtensions jest niejawnie przywołyny w aplikacjach ASP.NET Core.