Delen via


Optiepatroon in ASP.NET Core

Opmerking

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikel voor de huidige release.

Door Rick Anderson.

Het optiespatroon maakt gebruik van klassen om sterk getypte toegang te bieden tot groepen gerelateerde instellingen. Wanneer configuratie-instellingen worden geïsoleerd door scenario's in afzonderlijke klassen, voldoet de app aan twee belangrijke principes voor software-engineering:

  • Inkapseling:
    • Klassen die afhankelijk zijn van configuratie-instellingen, zijn alleen afhankelijk van de configuratie-instellingen die ze gebruiken.
  • Scheiding van zorgen:
    • Instellingen voor verschillende onderdelen van de app zijn niet afhankelijk van of gekoppeld aan elkaar.

Opties bieden ook een mechanisme voor het valideren van configuratiegegevens. Zie de sectie Optiesvalidatie voor meer informatie.

Dit artikel bevat informatie over het optiepatroon in ASP.NET Core. Zie Optiepatroon in .NET voor meer informatie over het gebruik van het optiepatroon in console-apps.

Hiërarchische bindingsconfiguratie

De voorkeursmethode voor het lezen van gerelateerde configuratiewaarden is het gebruik van het patroon opties. Bijvoorbeeld om de volgende configuratiewaarden te lezen:

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

Maak de volgende PositionOptions klasse:

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

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

Een opties klasse:

  • Moet niet-abstract zijn.
  • Heeft openbare lees-schrijfeigenschappen van het type waarvoor corresponderende items in config zijn gebonden.
  • Heeft de lees-schrijfeigenschappen gebonden aan overeenkomende items in de configuratie.
  • Heeft zijn velden niet gebonden. In de voorgaande code, Position is niet gebonden. Het Position veld wordt gebruikt, zodat de tekenreeks "Position" niet hard gecodeerd hoeft te worden in de app wanneer de klasse aan een configuratieprovider wordt gekoppeld.

De volgende code:

  • Roept ConfigurationBinder.Bind aan om de PositionOptions klasse aan de Position sectie te binden.
  • Geeft de Position configuratiegegevens weer.
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}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

ConfigurationBinder.Get<T> bindt en retourneert het opgegeven type. ConfigurationBinder.Get<T>kan handiger zijn dan het gebruik.ConfigurationBinder.Bind De volgende code laat zien hoe u deze kunt gebruiken ConfigurationBinder.Get<T> met de PositionOptions klasse:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Bind maakt ook de concretie van een abstracte klasse mogelijk. Beschouw de volgende code die de abstracte klasse SomethingWithANamegebruikt:

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

De volgende code geeft de NameTitleOptions configuratiewaarden weer:

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

Oproepen naar Bind zijn minder streng dan oproepen naar Get<>:

  • Bind maakt de concretie van een abstract mogelijk.
  • Get<> moet zelf een instantie maken.

Het optiepatroon

Een alternatieve benadering bij het gebruik van het optiepatroon is om de Position sectie te binden en toe te voegen aan de afhankelijkheidsinjectieservicecontainer. In de volgende code PositionOptions wordt deze toegevoegd aan de servicecontainer met Configure en gebonden aan de configuratie:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Met behulp van de voorgaande code leest de volgende code de positieopties:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand nadat de app is gestart, niet gelezen. Als u wijzigingen wilt lezen nadat de app is gestart, gebruikt u IOptionsSnapshot.

Optiesinterfaces

IOptions<TOptions>:

  • Biedt geen ondersteuning voor:
    • Het lezen van configuratiegegevens nadat de app is gestart.
    • Benoemde opties
  • Is geregistreerd als een Singleton en kan worden geïnjecteerd in elke levensduur van de service.

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenario's na de configuratie maken het mogelijk om opties in te stellen of te wijzigen nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden.

IOptionsFactory<TOptions> is verantwoordelijk voor het maken van nieuwe opties instanties. Het heeft één Create methode. De standaard implementatie neemt alle geregistreerde IConfigureOptions<TOptions> en IPostConfigureOptions<TOptions> voert eerst alle configuraties uit, gevolgd door de postconfiguratie. Er wordt onderscheid gemaakt tussen IConfigureNamedOptions<TOptions> en IConfigureOptions<TOptions> wordt alleen de juiste interface aanroepen.

IOptionsMonitorCache<TOptions> wordt gebruikt voor IOptionsMonitor<TOptions> het opslaan van exemplaren in de cache TOptions . De IOptionsMonitorCache<TOptions> opties in de monitor worden ongeldig, zodat de waarde opnieuw wordt gecomputeerd (TryRemove). Waarden kunnen handmatig worden geïntroduceerd met TryAdd. De Clear methode wordt gebruikt wanneer alle benoemde exemplaren op aanvraag opnieuw moeten worden gemaakt.

IOptionsSnapshot gebruiken om bijgewerkte gegevens te lezen

Met behulp van IOptionsSnapshot<TOptions>:

  • Opties worden eenmaal per aanvraag berekend wanneer ze worden geopend en in de cache worden opgeslagen gedurende de levensduur van de aanvraag.
  • Er kan een aanzienlijke prestatiefout worden opgelegd omdat het een Scoped-service is en per aanvraag opnieuw wordt berekend. Zie dit GitHub-probleem en De prestaties van configuratiebinding verbeteren voor meer informatie.
  • Wijzigingen in de configuratie worden gelezen nadat de app wordt gestart bij het gebruik van configuratieproviders die ondersteuning bieden voor het lezen van bijgewerkte configuratiewaarden.

Het verschil tussen IOptionsMonitor en IOptionsSnapshot is dat:

  • IOptionsMonitor is een Singleton-service die op elk gewenst moment de huidige optiewaarden ophaalt, wat vooral handig is in singleton-afhankelijkheden.
  • IOptionsSnapshot is een Scoped-service en biedt een momentopname van de opties op het moment dat het IOptionsSnapshot<T> object wordt gebouwd. Momentopnamen van opties zijn ontworpen voor gebruik met tijdelijke en bereikafhankelijkheden.

In de volgende code wordt gebruikgemaakt van 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}");
    }
}

Met de volgende code wordt een configuratie-exemplaar geregistreerd waarmee MyOptions een binding wordt uitgevoerd:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand gewijzigd nadat de app is gestart.

IOptionsMonitor

De volgende code registreert een configuratie-instantie die MyOptions bindt tegen.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

In het volgende voorbeeld wordt IOptionsMonitor<TOptions>gebruikt:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Geef een aangepaste sleutelnaam op voor een configuratie-eigenschap met behulp van ConfigurationKeyName

Standaard worden de eigenschapsnamen van de optieklasse gebruikt als de sleutelnaam in de configuratiebron. Als de eigenschapsnaam is Title, is Title de sleutelnaam in de configuratie dat ook.

Wanneer de namen differentiëren, kunt u het ConfigurationKeyName kenmerk gebruiken om de sleutelnaam in de configuratiebron op te geven. Met behulp van deze techniek kunt u een eigenschap in de configuratie toewijzen aan een eigenschap in uw code met een andere naam.

Dit is handig wanneer de eigenschapsnaam in de configuratiebron geen geldige C#-id is of wanneer u een andere naam in uw code wilt gebruiken.

Beschouw bijvoorbeeld de volgende opties klasse:

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

    [ConfigurationKeyName("position-title")]
    public string Title { get; set; } = string.Empty;

    [ConfigurationKeyName("position-name")]
    public string Name { get; set; } = string.Empty;
}

De Title eigenschappen van de klasse en Name zijn gebonden aan de position-title en position-name uit het volgende appsettings.json bestand:

{
  "Position": {
    "position-title": "Editor",
    "position-name": "Joe Smith"
  }
}

Ondersteuning voor benoemde opties met IConfigureNamedOptions

Benoemde opties:

  • Dit is handig wanneer meerdere configuratiesecties verbinding maken met dezelfde eigenschappen.
  • Zijn hoofdlettergevoelig.

Houd rekening met het volgende appsettings.json bestand:

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

In plaats van twee klassen te maken om te binden TopItem:Month en TopItem:Yearwordt de volgende klasse gebruikt voor elke sectie:

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

Met de volgende code worden de benoemde opties geconfigureerd:

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

Met de volgende code worden de benoemde opties weergegeven:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Alle opties zijn benoemde exemplaren. IConfigureOptions<TOptions>exemplaren worden behandeld als gericht op het Options.DefaultName exemplaar.string.Empty IConfigureNamedOptions<TOptions> implementeert IConfigureOptions<TOptions>ook . De standaard implementatie van de IOptionsFactory<TOptions> bevat logica om elk op de juiste manier te gebruiken. De null benoemde optie wordt gebruikt om alle benoemde exemplaren te targeten in plaats van op een specifiek benoemd exemplaar. ConfigureAll en PostConfigureAll gebruik deze conventie.

OptionsBuilder-API

OptionsBuilder<TOptions> wordt gebruikt voor het configureren van TOptions exemplaren. OptionsBuilder stroomlijnt het maken van benoemde opties, omdat het slechts één parameter is voor de eerste AddOptions<TOptions>(string optionsName) aanroep in plaats van in alle volgende aanroepen weer te geven. Optiesvalidatie en de ConfigureOptions overbelastingen die serviceafhankelijkheden accepteren, zijn alleen beschikbaar via OptionsBuilder.

OptionsBuilder wordt gebruikt in de sectie Optiesvalidatie .

Zie AddOptions gebruiken om een aangepaste opslagplaats te configureren voor informatie over het toevoegen van een aangepaste opslagplaats.

DI-services gebruiken om opties te configureren

Services zijn toegankelijk via afhankelijkheidsinjectie terwijl de opties op twee manieren worden geconfigureerd:

  • Geef een configuratiegemachtigde door aan Configure op OptionsBuilder<TOptions>. OptionsBuilder<TOptions> Biedt overbelasting van Configure die het gebruik van maximaal vijf services mogelijk maken om opties te configureren:

    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));
    
  • Maak een type dat het type als een service implementeert IConfigureOptions<TOptions> of IConfigureNamedOptions<TOptions> registreert.

We raden u aan een configuratiegemachtigde door te geven aan Configure, omdat het maken van een service complexer is. Het maken van een type is gelijk aan wat het framework doet bij het aanroepen Configurevan . Bij het aanroepen Configure wordt een tijdelijke generieke geregistreerd IConfigureNamedOptions<TOptions>, die een constructor heeft die de opgegeven algemene servicetypen accepteert.

Validatie van opties

Met optiesvalidatie kunnen optiewaarden worden gevalideerd.

Houd rekening met het volgende appsettings.json bestand:

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

De volgende klasse wordt gebruikt om te binden aan de "MyConfig" configuratiesectie en past een aantal DataAnnotations regels toe:

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

De volgende code:

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

De ValidateDataAnnotations extensiemethode wordt gedefinieerd in het NuGet-pakket Microsoft.Extensions.Options.DataAnnotations . Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet naar dit pakket verwezen vanuit het gedeelde framework.

De volgende code geeft de configuratiewaarden of de validatiefouten weer:

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

Met de volgende code wordt een complexere validatieregel toegepast met behulp van een gemachtigde:

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

De volgende klasse implementeert IValidateOptions<TOptions>:

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions Hiermee kunt u de validatiecode uit Program.cs en naar een klas verplaatsen.

Met behulp van de voorgaande code wordt validatie ingeschakeld Program.cs met de volgende code:

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

Optievalidatie ondersteunt IValidatableObjectook . Validatie op klasniveau uitvoeren van een klasse binnen de klasse zelf:

ValidateOnStart

Optievalidatie wordt uitgevoerd wanneer een TOption exemplaar voor het eerst wordt gemaakt. Dat betekent bijvoorbeeld wanneer de eerste toegang tot IOptionsSnapshot<TOptions>.Value plaatsvindt in een aanvraagpijplijn of wanneer IOptionsMonitor<TOptions>.Get(string) wordt aangeroepen op de aanwezige instellingen. Nadat de instellingen opnieuw zijn geladen, wordt de validatie opnieuw uitgevoerd. De ASP.NET Core-runtime wordt gebruikt OptionsCache<TOptions> om de optie-instantie in de cache op te slaan zodra deze is gemaakt.

Om de validatie van opties gretig uit te voeren, roept ValidateOnStart<TOptions>(OptionsBuilder<TOptions>)u bij het opstarten van de app het volgende op Program.cs:

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

Opties na configuratie

Stel na de configuratie in met IPostConfigureOptions<TOptions>. De post-configuratie wordt uitgevoerd nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden:

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 is beschikbaar voor het na configureren van benoemde opties:

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

Gebruik PostConfigureAll dit om alle configuratie-exemplaren na de configuratie te configureren:

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

Toegangsopties in Program.cs

Om toegang te krijgen IOptions<TOptions> of IOptionsMonitor<TOptions>Program.cs in , bel GetRequiredService op WebApplication.Services:

var app = builder.Build();

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

Aanvullende bronnen

Door Kirk Larkin en Rick Anderson.

Het optiespatroon maakt gebruik van klassen om sterk getypte toegang te bieden tot groepen gerelateerde instellingen. Wanneer configuratie-instellingen worden geïsoleerd door scenario's in afzonderlijke klassen, voldoet de app aan twee belangrijke principes voor software-engineering:

  • Inkapseling:
    • Klassen die afhankelijk zijn van configuratie-instellingen, zijn alleen afhankelijk van de configuratie-instellingen die ze gebruiken.
  • Scheiding van zorgen:
    • Instellingen voor verschillende onderdelen van de app zijn niet afhankelijk van of gekoppeld aan elkaar.

Opties bieden ook een mechanisme voor het valideren van configuratiegegevens. Zie de sectie Optiesvalidatie voor meer informatie.

Dit artikel bevat informatie over het optiepatroon in ASP.NET Core. Zie Optiepatroon in .NET voor meer informatie over het gebruik van het optiepatroon in console-apps.

Hiërarchische bindingsconfiguratie

De voorkeursmethode voor het lezen van gerelateerde configuratiewaarden is het gebruik van het patroon opties. Bijvoorbeeld om de volgende configuratiewaarden te lezen:

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

Maak de volgende PositionOptions klasse:

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

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

Een opties klasse:

  • Moet niet-abstract zijn met een openbare parameterloze constructor.
  • Alle openbare lees-schrijfeigenschappen van het type zijn gebonden.
  • Velden zijn niet gebonden. In de voorgaande code, Position is niet gebonden. Het Position veld wordt gebruikt, zodat de tekenreeks "Position" niet hard gecodeerd hoeft te worden in de app wanneer de klasse aan een configuratieprovider wordt gekoppeld.

De volgende code:

  • Roept ConfigurationBinder.Bind aan om de PositionOptions klasse aan de Position sectie te binden.
  • Geeft de Position configuratiegegevens weer.
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}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

ConfigurationBinder.Get<T> bindt en retourneert het opgegeven type. ConfigurationBinder.Get<T>kan handiger zijn dan het gebruik.ConfigurationBinder.Bind De volgende code laat zien hoe u deze kunt gebruiken ConfigurationBinder.Get<T> met de PositionOptions klasse:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Een alternatieve benadering bij het gebruik van het optiepatroon is om de Position sectie te binden en toe te voegen aan de afhankelijkheidsinjectieservicecontainer. In de volgende code PositionOptions wordt deze toegevoegd aan de servicecontainer met Configure en gebonden aan de configuratie:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Met behulp van de voorgaande code leest de volgende code de positieopties:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand nadat de app is gestart, niet gelezen. Als u wijzigingen wilt lezen nadat de app is gestart, gebruikt u IOptionsSnapshot.

Optiesinterfaces

IOptions<TOptions>:

  • Biedt geen ondersteuning voor:
    • Het lezen van configuratiegegevens nadat de app is gestart.
    • Benoemde opties
  • Is geregistreerd als een Singleton en kan worden geïnjecteerd in elke levensduur van de service.

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenario's na de configuratie maken het mogelijk om opties in te stellen of te wijzigen nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden.

IOptionsFactory<TOptions> is verantwoordelijk voor het maken van nieuwe opties instanties. Het heeft één Create methode. De standaard implementatie neemt alle geregistreerde IConfigureOptions<TOptions> en IPostConfigureOptions<TOptions> voert eerst alle configuraties uit, gevolgd door de postconfiguratie. Er wordt onderscheid gemaakt tussen IConfigureNamedOptions<TOptions> en IConfigureOptions<TOptions> wordt alleen de juiste interface aanroepen.

IOptionsMonitorCache<TOptions> wordt gebruikt voor IOptionsMonitor<TOptions> het opslaan van exemplaren in de cache TOptions . De IOptionsMonitorCache<TOptions> opties in de monitor worden ongeldig, zodat de waarde opnieuw wordt gecomputeerd (TryRemove). Waarden kunnen handmatig worden geïntroduceerd met TryAdd. De Clear methode wordt gebruikt wanneer alle benoemde exemplaren op aanvraag opnieuw moeten worden gemaakt.

IOptionsSnapshot gebruiken om bijgewerkte gegevens te lezen

Met behulp van IOptionsSnapshot<TOptions>:

  • Opties worden eenmaal per aanvraag berekend wanneer ze worden geopend en in de cache worden opgeslagen gedurende de levensduur van de aanvraag.
  • Er kan een aanzienlijke prestatiefout worden opgelegd omdat het een Scoped-service is en per aanvraag opnieuw wordt berekend. Zie dit GitHub-probleem en De prestaties van configuratiebinding verbeteren voor meer informatie.
  • Wijzigingen in de configuratie worden gelezen nadat de app wordt gestart bij het gebruik van configuratieproviders die ondersteuning bieden voor het lezen van bijgewerkte configuratiewaarden.

Het verschil tussen IOptionsMonitor en IOptionsSnapshot is dat:

  • IOptionsMonitor is een Singleton-service die op elk gewenst moment de huidige optiewaarden ophaalt, wat vooral handig is in singleton-afhankelijkheden.
  • IOptionsSnapshot is een Scoped-service en biedt een momentopname van de opties op het moment dat het IOptionsSnapshot<T> object wordt gebouwd. Momentopnamen van opties zijn ontworpen voor gebruik met tijdelijke en bereikafhankelijkheden.

In de volgende code wordt gebruikgemaakt van 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}");
    }
}

Met de volgende code wordt een configuratie-exemplaar geregistreerd waarmee MyOptions een binding wordt uitgevoerd:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand gewijzigd nadat de app is gestart.

IOptionsMonitor

De volgende code registreert een configuratie-instantie die MyOptions bindt tegen.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

In het volgende voorbeeld wordt IOptionsMonitor<TOptions>gebruikt:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Ondersteuning voor benoemde opties met IConfigureNamedOptions

Benoemde opties:

  • Dit is handig wanneer meerdere configuratiesecties verbinding maken met dezelfde eigenschappen.
  • Zijn hoofdlettergevoelig.

Houd rekening met het volgende appsettings.json bestand:

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

In plaats van twee klassen te maken om te binden TopItem:Month en TopItem:Yearwordt de volgende klasse gebruikt voor elke sectie:

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

Met de volgende code worden de benoemde opties geconfigureerd:

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

Met de volgende code worden de benoemde opties weergegeven:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Alle opties zijn benoemde exemplaren. IConfigureOptions<TOptions>exemplaren worden behandeld als gericht op het Options.DefaultName exemplaar.string.Empty IConfigureNamedOptions<TOptions> implementeert IConfigureOptions<TOptions>ook . De standaard implementatie van de IOptionsFactory<TOptions> bevat logica om elk op de juiste manier te gebruiken. De null benoemde optie wordt gebruikt om alle benoemde exemplaren te targeten in plaats van op een specifiek benoemd exemplaar. ConfigureAll en PostConfigureAll gebruik deze conventie.

OptionsBuilder-API

OptionsBuilder<TOptions> wordt gebruikt voor het configureren van TOptions exemplaren. OptionsBuilder stroomlijnt het maken van benoemde opties, omdat het slechts één parameter is voor de eerste AddOptions<TOptions>(string optionsName) aanroep in plaats van in alle volgende aanroepen weer te geven. Optiesvalidatie en de ConfigureOptions overbelastingen die serviceafhankelijkheden accepteren, zijn alleen beschikbaar via OptionsBuilder.

OptionsBuilder wordt gebruikt in de sectie Optiesvalidatie .

Zie AddOptions gebruiken om een aangepaste opslagplaats te configureren voor informatie over het toevoegen van een aangepaste opslagplaats.

DI-services gebruiken om opties te configureren

Services zijn toegankelijk via afhankelijkheidsinjectie terwijl de opties op twee manieren worden geconfigureerd:

  • Geef een configuratiegemachtigde door aan Configure op OptionsBuilder<TOptions>. OptionsBuilder<TOptions> Biedt overbelasting van Configure die het gebruik van maximaal vijf services mogelijk maken om opties te configureren:

    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));
    
  • Maak een type dat het type als een service implementeert IConfigureOptions<TOptions> of IConfigureNamedOptions<TOptions> registreert.

We raden u aan een configuratiegemachtigde door te geven aan Configure, omdat het maken van een service complexer is. Het maken van een type is gelijk aan wat het framework doet bij het aanroepen Configurevan . Bij het aanroepen Configure wordt een tijdelijke generieke geregistreerd IConfigureNamedOptions<TOptions>, die een constructor heeft die de opgegeven algemene servicetypen accepteert.

Validatie van opties

Met optiesvalidatie kunnen optiewaarden worden gevalideerd.

Houd rekening met het volgende appsettings.json bestand:

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

De volgende klasse wordt gebruikt om te binden aan de "MyConfig" configuratiesectie en past een aantal DataAnnotations regels toe:

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

De volgende code:

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

De ValidateDataAnnotations extensiemethode wordt gedefinieerd in het NuGet-pakket Microsoft.Extensions.Options.DataAnnotations . Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet naar dit pakket verwezen vanuit het gedeelde framework.

De volgende code geeft de configuratiewaarden of de validatiefouten weer:

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

Met de volgende code wordt een complexere validatieregel toegepast met behulp van een gemachtigde:

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

De volgende klasse implementeert IValidateOptions<TOptions>:

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions Hiermee kunt u de validatiecode uit Program.cs en naar een klas verplaatsen.

Met behulp van de voorgaande code wordt validatie ingeschakeld Program.cs met de volgende code:

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

Optievalidatie ondersteunt IValidatableObjectook . Validatie op klasniveau uitvoeren van een klasse binnen de klasse zelf:

ValidateOnStart

Optievalidatie wordt uitgevoerd wanneer IOptions<TOptions>een , IOptionsSnapshot<TOptions>, of IOptionsMonitor<TOptions> implementatie voor het eerst wordt gemaakt. Om de validatie van opties gretig uit te voeren, roept ValidateOnStart u bij het opstarten van de app het volgende op Program.cs:

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

Opties na configuratie

Stel na de configuratie in met IPostConfigureOptions<TOptions>. De post-configuratie wordt uitgevoerd nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden:

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 is beschikbaar voor het na configureren van benoemde opties:

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

Gebruik PostConfigureAll dit om alle configuratie-exemplaren na de configuratie te configureren:

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

Toegangsopties in Program.cs

Om toegang te krijgen IOptions<TOptions> of IOptionsMonitor<TOptions>Program.cs in , bel GetRequiredService op WebApplication.Services:

var app = builder.Build();

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

Aanvullende bronnen

Door Kirk Larkin en Rick Anderson.

Het optiespatroon maakt gebruik van klassen om sterk getypte toegang te bieden tot groepen gerelateerde instellingen. Wanneer configuratie-instellingen worden geïsoleerd door scenario's in afzonderlijke klassen, voldoet de app aan twee belangrijke principes voor software-engineering:

  • Inkapseling:
    • Klassen die afhankelijk zijn van configuratie-instellingen, zijn alleen afhankelijk van de configuratie-instellingen die ze gebruiken.
  • Scheiding van zorgen:
    • Instellingen voor verschillende onderdelen van de app zijn niet afhankelijk van of gekoppeld aan elkaar.

Opties bieden ook een mechanisme voor het valideren van configuratiegegevens. Zie de sectie Optiesvalidatie voor meer informatie.

In dit onderwerp vindt u informatie over het optiepatroon in ASP.NET kern. Zie Optiepatroon in .NET voor meer informatie over het gebruik van het optiepatroon in console-apps.

Voorbeeldcode bekijken of downloaden (hoe download je)

Hiërarchische bindingsconfiguratie

De voorkeursmethode voor het lezen van gerelateerde configuratiewaarden is het gebruik van het patroon opties. Bijvoorbeeld om de volgende configuratiewaarden te lezen:

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

Maak de volgende PositionOptions klasse:

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

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

Een opties klasse:

  • Moet niet-abstract zijn met een openbare parameterloze constructor.
  • Alle openbare lees-schrijfeigenschappen van het type zijn gebonden.
  • Velden zijn niet gebonden. In de voorgaande code, Position is niet gebonden. De Position eigenschap wordt gebruikt, zodat de tekenreeks "Position" niet hard gecodeerd hoeft te worden in de app wanneer de klasse aan een configuratieprovider wordt gekoppeld.

De volgende code:

  • Roept ConfigurationBinder.Bind aan om de PositionOptions klasse aan de Position sectie te binden.
  • Geeft de Position configuratiegegevens weer.
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}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

ConfigurationBinder.Get<T> bindt en retourneert het opgegeven type. ConfigurationBinder.Get<T>kan handiger zijn dan het gebruik.ConfigurationBinder.Bind De volgende code laat zien hoe u deze kunt gebruiken ConfigurationBinder.Get<T> met de PositionOptions klasse:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Een alternatieve benadering bij het gebruik van het optiepatroon is om de Position sectie te binden en toe te voegen aan de afhankelijkheidsinjectieservicecontainer. In de volgende code PositionOptions wordt deze toegevoegd aan de servicecontainer met Configure en gebonden aan de configuratie:

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

Met behulp van de voorgaande code leest de volgende code de positieopties:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand nadat de app is gestart, niet gelezen. Als u wijzigingen wilt lezen nadat de app is gestart, gebruikt u IOptionsSnapshot.

Optiesinterfaces

IOptions<TOptions>:

  • Biedt geen ondersteuning voor:
    • Het lezen van configuratiegegevens nadat de app is gestart.
    • Benoemde opties
  • Is geregistreerd als een Singleton en kan worden geïnjecteerd in elke levensduur van de service.

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

Scenario's na de configuratie maken het mogelijk om opties in te stellen of te wijzigen nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden.

IOptionsFactory<TOptions> is verantwoordelijk voor het maken van nieuwe opties instanties. Het heeft één Create methode. De standaard implementatie neemt alle geregistreerde IConfigureOptions<TOptions> en IPostConfigureOptions<TOptions> voert eerst alle configuraties uit, gevolgd door de postconfiguratie. Er wordt onderscheid gemaakt tussen IConfigureNamedOptions<TOptions> en IConfigureOptions<TOptions> wordt alleen de juiste interface aanroepen.

IOptionsMonitorCache<TOptions> wordt gebruikt voor IOptionsMonitor<TOptions> het opslaan van exemplaren in de cache TOptions . De IOptionsMonitorCache<TOptions> opties in de monitor worden ongeldig, zodat de waarde opnieuw wordt gecomputeerd (TryRemove). Waarden kunnen handmatig worden geïntroduceerd met TryAdd. De Clear methode wordt gebruikt wanneer alle benoemde exemplaren op aanvraag opnieuw moeten worden gemaakt.

IOptionsSnapshot gebruiken om bijgewerkte gegevens te lezen

Met behulp van IOptionsSnapshot<TOptions>worden opties eenmaal per aanvraag berekend wanneer ze worden geopend en in de cache worden opgeslagen gedurende de levensduur van de aanvraag. Wijzigingen in de configuratie worden gelezen nadat de app wordt gestart bij het gebruik van configuratieproviders die ondersteuning bieden voor het lezen van bijgewerkte configuratiewaarden.

Het verschil tussen IOptionsMonitor en IOptionsSnapshot is dat:

  • IOptionsMonitor is een Singleton-service die op elk gewenst moment de huidige optiewaarden ophaalt, wat vooral handig is in singleton-afhankelijkheden.
  • IOptionsSnapshot is een Scoped-service en biedt een momentopname van de opties op het moment dat het IOptionsSnapshot<T> object wordt gebouwd. Momentopnamen van opties zijn ontworpen voor gebruik met tijdelijke en bereikafhankelijkheden.

In de volgende code wordt gebruikgemaakt van 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}");
    }
}

Met de volgende code wordt een configuratie-exemplaar geregistreerd waarmee MyOptions een binding wordt uitgevoerd:

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

    services.AddRazorPages();
}

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand gewijzigd nadat de app is gestart.

IOptionsMonitor

De volgende code registreert een configuratie-instantie die MyOptions bindt tegen.

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

    services.AddRazorPages();
}

In het volgende voorbeeld wordt IOptionsMonitor<TOptions>gebruikt:

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

In de voorgaande code worden wijzigingen in het JSON-configuratiebestand standaard gelezen nadat de app is gestart.

Ondersteuning voor benoemde opties met IConfigureNamedOptions

Benoemde opties:

  • Dit is handig wanneer meerdere configuratiesecties verbinding maken met dezelfde eigenschappen.
  • Zijn hoofdlettergevoelig.

Houd rekening met het volgende appsettings.json bestand:

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

In plaats van twee klassen te maken om te binden TopItem:Month en TopItem:Yearwordt de volgende klasse gebruikt voor elke sectie:

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

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

Met de volgende code worden de benoemde opties geconfigureerd:

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

Met de volgende code worden de benoemde opties weergegeven:

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Alle opties zijn benoemde exemplaren. IConfigureOptions<TOptions>exemplaren worden behandeld als gericht op het Options.DefaultName exemplaar.string.Empty IConfigureNamedOptions<TOptions> implementeert IConfigureOptions<TOptions>ook . De standaard implementatie van de IOptionsFactory<TOptions> bevat logica om elk op de juiste manier te gebruiken. De null benoemde optie wordt gebruikt om alle benoemde exemplaren te targeten in plaats van op een specifiek benoemd exemplaar. ConfigureAll en PostConfigureAll gebruik deze conventie.

OptionsBuilder-API

OptionsBuilder<TOptions> wordt gebruikt voor het configureren van TOptions exemplaren. OptionsBuilder stroomlijnt het maken van benoemde opties, omdat het slechts één parameter is voor de eerste AddOptions<TOptions>(string optionsName) aanroep in plaats van in alle volgende aanroepen weer te geven. Optiesvalidatie en de ConfigureOptions overbelastingen die serviceafhankelijkheden accepteren, zijn alleen beschikbaar via OptionsBuilder.

OptionsBuilder wordt gebruikt in de sectie Optiesvalidatie .

Zie AddOptions gebruiken om een aangepaste opslagplaats te configureren voor informatie over het toevoegen van een aangepaste opslagplaats.

DI-services gebruiken om opties te configureren

Services zijn toegankelijk via afhankelijkheidsinjectie terwijl de opties op twee manieren worden geconfigureerd:

  • Geef een configuratiegemachtigde door aan Configure op OptionsBuilder<TOptions>. OptionsBuilder<TOptions> Biedt overbelasting van Configure die het gebruik van maximaal vijf services mogelijk maken om opties te configureren:

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Maak een type dat het type als een service implementeert IConfigureOptions<TOptions> of IConfigureNamedOptions<TOptions> registreert.

We raden u aan een configuratiegemachtigde door te geven aan Configure, omdat het maken van een service complexer is. Het maken van een type is gelijk aan wat het framework doet bij het aanroepen Configurevan . Bij het aanroepen Configure wordt een tijdelijke generieke geregistreerd IConfigureNamedOptions<TOptions>, die een constructor heeft die de opgegeven algemene servicetypen accepteert.

Validatie van opties

Met optiesvalidatie kunnen optiewaarden worden gevalideerd.

Houd rekening met het volgende appsettings.json bestand:

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

De volgende klasse bindt aan de "MyConfig" configuratiesectie en past een aantal DataAnnotations regels toe:

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

De volgende code:

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

De ValidateDataAnnotations extensiemethode wordt gedefinieerd in het NuGet-pakket Microsoft.Extensions.Options.DataAnnotations . Voor web-apps die gebruikmaken van de Microsoft.NET.Sdk.Web SDK, wordt impliciet naar dit pakket verwezen vanuit het gedeelde framework.

De volgende code geeft de configuratiewaarden of de validatiefouten weer:

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

Met de volgende code wordt een complexere validatieregel toegepast met behulp van een gemachtigde:

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

IValidateOpties voor complexe validatie

De volgende klasse implementeert IValidateOptions<TOptions>:

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions Hiermee kunt u de validatiecode uit StartUp en naar een klas verplaatsen.

Met behulp van de voorgaande code wordt validatie ingeschakeld Startup.ConfigureServices met de volgende code:

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

Opties na configuratie

Stel na de configuratie in met IPostConfigureOptions<TOptions>. De post-configuratie wordt uitgevoerd nadat alle IConfigureOptions<TOptions> configuraties hebben plaatsgevonden:

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

PostConfigure is beschikbaar voor het na configureren van benoemde opties:

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

Gebruik PostConfigureAll dit om alle configuratie-exemplaren na de configuratie te configureren:

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

Toegang tot opties tijdens het opstarten

IOptions<TOptions> en IOptionsMonitor<TOptions> kan worden gebruikt in Startup.Configure, omdat services worden gebouwd voordat de Configure methode wordt uitgevoerd.

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

Gebruik of IOptions<TOptions>IOptionsMonitor<TOptions> niet in Startup.ConfigureServices. Er kan sprake zijn van een inconsistente optiestatus als gevolg van de volgorde van serviceregistraties.

Options.ConfigurationExtensions NuGet-pakket

In ASP.NET Core-apps wordt impliciet verwezen naar het pakket Microsoft.Extensions.Options.ConfigurationExtensions .