Modello di opzioni in ASP.NET Core
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 8 di questo articolo.
Di Rick Anderson.
Il modello di opzioni usa classi per fornire l'accesso fortemente tipizzato ai gruppi di impostazioni correlate. Quando le impostazioni di configurazione vengono isolate in base allo scenario in classi separate, l'app aderisce a due importanti principi di progettazione del software:
- Incapsulamento:
- Le classi che dipendono dalle impostazioni di configurazione dipendono solo dalle impostazioni di configurazione usate.
- Separazione dei problemi:
- Le impostazioni per parti diverse dell'app non sono dipendenti o associate l'una all'altra.
Le opzioni offrono anche un meccanismo per convalidare i dati di configurazione. Per altre informazioni, vedere la sezione Opzioni di convalida.
Questo articolo fornisce informazioni sul modello di opzioni in ASP.NET Core. Per informazioni sull'uso del modello di opzioni nelle app console, vedere Modello di opzioni in .NET.
Associare la configurazione gerarchica
Il modo preferito per leggere i valori di configurazione correlati prevede l'uso del modello di opzioni. Ad esempio, per leggere i valori di configurazione seguenti:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Creare la classe PositionOptions
seguente:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Una classe di opzioni:
- Deve essere non astratto.
- Dispone di proprietà pubbliche di lettura/scrittura del tipo con elementi corrispondenti nella configurazione sono associate.
- Dispone delle proprietà di lettura/scrittura associate alle voci corrispondenti nella configurazione.
- I campi non sono associati. Nel codice precedente
Position
non è associato. Il campoPosition
viene usato in modo che la stringa"Position"
non debba essere hardcoded nell'app quando la classe viene associata a un provider di configurazione.
Il codice seguente:
- Chiama ConfigurationBinder.Bind per associare la classe
PositionOptions
alla sezionePosition
. - Visualizza i dati di configurazione di
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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
ConfigurationBinder.Get<T>
associa e restituisce il tipo specificato. ConfigurationBinder.Get<T>
può risultare più utile rispetto all'uso di ConfigurationBinder.Bind
. Il codice seguente mostra come usare ConfigurationBinder.Get<T>
con la classe 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Bind consente anche la concretezza di una classe astratta. Si consideri il codice seguente che usa la classe SomethingWithAName
astratta :
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;
}
Nel codice seguente vengono visualizzati i valori di NameTitleOptions
configurazione:
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}"
);
}
}
Le chiamate a Bind
sono meno rigide delle chiamate a Get<>
:
Bind
consente la concretezza di un'astrazione.Get<>
deve creare un'istanza stessa.
Modello di opzioni
Un approccio alternativo quando si usa il modello opzioni consiste nell'associare la sezione Position
e aggiungerla al contenitore del servizio di inserimento di dipendenze. Nel codice seguente PositionOptions
viene aggiunta al contenitore del servizio con Configure e associata alla configurazione:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Quando si usa il codice precedente, il codice seguente legge le opzioni di posizione:
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}");
}
}
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app non vengono lette. Per leggere le modifiche dopo l'avvio dell'app, usare IOptionsSnapshot.
Interfacce per le opzioni
- non supporta:
- La lettura dei dati di configurazione dopo l'avvio dell'app.
- Opzioni denominate
- È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- È utile negli scenari in cui le opzioni devono essere ricalcolate in ogni richiesta. Per altre informazioni, vedere Usare IOptionsSnapshot per leggere i dati aggiornati.
- Viene registrato come con ambito e pertanto non può essere inserito in un servizio Singleton.
- Supporta le opzioni denominate
- Consente di recuperare le opzioni e gestire le notifiche delle opzioni per le istanze di
TOptions
. - È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- Supporta:
- Notifiche relative a modifiche
- opzioni denominate
- Configurazione ricaricabile
- Invalidamento selettivo di opzioni (IOptionsMonitorCache<TOptions>)
Gli scenari di post-configurazione abilitano l'impostazione o la modifica delle opzioni dopo che si verifica tutta la IConfigureOptions<TOptions> configurazione.
IOptionsFactory<TOptions> è responsabile della creazione di nuove istanze di opzioni. Include un singolo metodo Create. L'implementazione predefinita accetta tutte le interfacce IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrate ed esegue tutte le configurazioni, seguite dalla post-configurazione. Fa distinzione tra IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chiama solo l'interfaccia appropriata.
IOptionsMonitorCache<TOptions> viene usata da IOptionsMonitor<TOptions> per memorizzare nella cache le istanze di TOptions
. IOptionsMonitorCache<TOptions> invalida le istanze delle opzioni nel monitoraggio in modo che il valore venga ricalcolato (TryRemove). I valori possono essere introdotti manualmente con TryAdd. Il metodo Clear viene usato quando tutte le istanze denominate devono essere ricreate su richiesta.
Usare IOptionsSnapshot per leggere i dati aggiornati
Utilizzo di IOptionsSnapshot<TOptions>:
- Le opzioni vengono calcolate una volta per richiesta quando viene eseguito l'accesso e la memorizzazione nella cache per la durata della richiesta.
- Può comportare una riduzione significativa delle prestazioni perché si tratta di un servizio con ambito e viene ricalcorato per ogni richiesta. Per altre informazioni, vedere questo problema di GitHub e Migliorare le prestazioni dell'associazione di configurazione.
- Le modifiche alla configurazione vengono lette dopo l'avvio dell'app quando si usano provider di configurazione che supportano la lettura dei valori di configurazione aggiornati.
Differenze tra IOptionsMonitor
e IOptionsSnapshot
:
IOptionsMonitor
è un servizio Singleton che recupera i valori delle opzioni correnti in qualsiasi momento, particolarmente utile nelle dipendenze singleton.IOptionsSnapshot
è un servizio con ambito e fornisce uno snapshot delle opzioni al momento della costruzione dell'oggettoIOptionsSnapshot<T>
. Gli snapshot delle opzioni sono progettati per l'uso con dipendenze temporanee e con ambito.
Il codice seguente usa 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}");
}
}
Il codice seguente registra un'istanza di configurazione che MyOptions
associa a:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app vengono lette.
IOptionsMonitor
Il codice seguente registra un'istanza di configurazione che MyOptions
viene associata a .
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Nell'esempio seguente viene usato 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Supporto delle opzioni denominate con IConfigureNamedOptions
Le opzioni denominate:
- Sono utili quando più sezioni di configurazione sono associate alle stesse proprietà.
- Viene fatta distinzione tra maiuscole e minuscole.
Considerare il file appsettings.json
seguente:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anziché creare due classi per associare TopItem:Month
e TopItem:Year
, viene usata la classe seguente per ogni sezione:
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;
}
Nel codice seguente vengono configurate le opzioni denominate:
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();
Il codice seguente mostra le opzioni denominate:
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" );
}
}
Tutte le opzioni sono istanze denominate. Le istanze di IConfigureOptions<TOptions> sono considerate come destinate all'istanza di Options.DefaultName
, ovvero string.Empty
. IConfigureNamedOptions<TOptions> implementa anche IConfigureOptions<TOptions>. L'implementazione predefinita di IOptionsFactory<TOptions> include la logica per usarle in modo appropriato. L'opzione denominata null
viene usata per avere come destinazione tutte le istanze denominate anziché un'istanza denominata specifica. Questa convenzione è usata da ConfigureAll e PostConfigureAll.
API OptionsBuilder
OptionsBuilder<TOptions> viene usata per configurare le istanze di TOptions
. OptionsBuilder
semplifica la creazione di opzioni denominate perché è costituita da un singolo parametro nella chiamata iniziale ad AddOptions<TOptions>(string optionsName)
invece di essere visualizzata in tutte le chiamate successive. La convalida delle opzioni e gli overload ConfigureOptions
che accettano le dipendenze dei servizi sono disponibili solo tramite OptionsBuilder
.
OptionsBuilder
è usata nella sezione Convalida delle opzioni.
Per informazioni sull'aggiunta di un repository personalizzato, vedere Usare AddOptions.
Usare i servizi di inserimento delle dipendenze per configurare le opzioni
Per accedere ai servizi dall'inserimento delle dipendenze durante la configurazione delle opzioni è possibile procedere in due modi:
Passare un delegato di configurazione a Configure in OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
fornisce overload di Configure che consentono l'uso di un massimo di cinque servizi per configurare le opzioni: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));
Creare un tipo che implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions> e registrare il tipo come servizio.
È consigliabile passare un delegato di configurazione a Configure, perché la creazione di un servizio è più complessa. La creazione di un tipo equivale a ciò che il framework esegue quando si chiama Configure. La chiamata Configure registra un generico IConfigureNamedOptions<TOptions>temporaneo, che dispone di un costruttore che accetta i tipi di servizio generici specificati.
Convalida delle opzioni
La convalida delle opzioni consente di convalidare i valori delle opzioni.
Considerare il file appsettings.json
seguente:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La classe seguente viene usata per eseguire l'associazione "MyConfig"
alla sezione di configurazione e applica un paio di DataAnnotations
regole:
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; }
}
Il codice seguente:
- Chiama AddOptions per ottenere un oggetto OptionsBuilder<TOptions> che viene associato alla
MyConfigOptions
classe . - Chiama ValidateDataAnnotations per abilitare la convalida tramite
DataAnnotations
.
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();
Il metodo di estensione ValidateDataAnnotations
viene definito nel pacchetto NuGet Microsoft.Extensions.Options.DataAnnotations. Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, questo pacchetto viene fatto riferimento in modo implicito dal framework condiviso.
Nel codice seguente vengono visualizzati i valori di configurazione o gli errori di convalida:
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);
}
Il codice seguente applica una regola di convalida più complessa usando un delegato:
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>
e IValidatableObject
La classe seguente implementa 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
consente di spostare il codice di Program.cs
convalida da e in una classe.
Usando il codice precedente, la convalida è abilitata in Program.cs
con il codice seguente:
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();
La convalida delle opzioni supporta IValidatableObjectanche . Per eseguire la convalida a livello di classe di una classe all'interno della classe stessa:
- Implementare l'interfaccia
IValidatableObject
e il relativo Validate metodo all'interno della classe . - Chiamare ValidateDataAnnotations in
Program.cs
.
ValidateOnStart
La convalida delle opzioni viene eseguita la prima volta che viene creata un'istanza TOption
di . Ciò significa, ad esempio, quando si verifica il primo accesso a IOptionsSnapshot<TOptions>.Value
in una pipeline di richieste o quando IOptionsMonitor<TOptions>.Get(string)
viene chiamato sulle impostazioni presenti. Dopo aver ricaricato le impostazioni, la convalida viene eseguita di nuovo. Il runtime di ASP.NET Core usa OptionsCache<TOptions> per memorizzare nella cache l'istanza delle opzioni dopo la creazione.
Per eseguire la convalida delle opzioni in modo eager, all'avvio dell'app, chiamare ValidateOnStart<TOptions>(OptionsBuilder<TOptions>)in Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Post-configurazione delle opzioni
Impostare la post-configurazione con IPostConfigureOptions<TOptions>. La post-configurazione viene eseguita dopo il completamento della configurazione di tutte le IConfigureOptions<TOptions>:
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 è disponibile per la post-configurazione delle opzioni denominate:
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();
Usare PostConfigureAll per la post-configurazione di tutte le istanze di configurazione:
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";
});
Opzioni di accesso in Program.cs
Per accedere IOptions<TOptions> a o IOptionsMonitor<TOptions> in Program.cs
, chiamare GetRequiredService su WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Risorse aggiuntive
Di Kirk Larkin e Rick Anderson.
Il modello di opzioni usa classi per fornire l'accesso fortemente tipizzato ai gruppi di impostazioni correlate. Quando le impostazioni di configurazione vengono isolate in base allo scenario in classi separate, l'app aderisce a due importanti principi di progettazione del software:
- Incapsulamento:
- Le classi che dipendono dalle impostazioni di configurazione dipendono solo dalle impostazioni di configurazione usate.
- Separazione dei problemi:
- Le impostazioni per parti diverse dell'app non sono dipendenti o associate l'una all'altra.
Le opzioni offrono anche un meccanismo per convalidare i dati di configurazione. Per altre informazioni, vedere la sezione Opzioni di convalida.
Questo articolo fornisce informazioni sul modello di opzioni in ASP.NET Core. Per informazioni sull'uso del modello di opzioni nelle app console, vedere Modello di opzioni in .NET.
Associare la configurazione gerarchica
Il modo preferito per leggere i valori di configurazione correlati prevede l'uso del modello di opzioni. Ad esempio, per leggere i valori di configurazione seguenti:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Creare la classe PositionOptions
seguente:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Una classe di opzioni:
- Deve essere non astratta con un costruttore pubblico senza parametri.
- Tutte le proprietà di lettura/scrittura pubbliche del tipo sono associate.
- I campi non sono associati. Nel codice precedente
Position
non è associato. Il campoPosition
viene usato in modo che la stringa"Position"
non debba essere hardcoded nell'app quando la classe viene associata a un provider di configurazione.
Il codice seguente:
- Chiama ConfigurationBinder.Bind per associare la classe
PositionOptions
alla sezionePosition
. - Visualizza i dati di configurazione di
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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
ConfigurationBinder.Get<T>
associa e restituisce il tipo specificato. ConfigurationBinder.Get<T>
può risultare più utile rispetto all'uso di ConfigurationBinder.Bind
. Il codice seguente mostra come usare ConfigurationBinder.Get<T>
con la classe 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Un approccio alternativo quando si usa il modello opzioni consiste nell'associare la sezione Position
e aggiungerla al contenitore del servizio di inserimento di dipendenze. Nel codice seguente PositionOptions
viene aggiunta al contenitore del servizio con Configure e associata alla configurazione:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
Quando si usa il codice precedente, il codice seguente legge le opzioni di posizione:
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}");
}
}
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app non vengono lette. Per leggere le modifiche dopo l'avvio dell'app, usare IOptionsSnapshot.
Interfacce per le opzioni
- non supporta:
- La lettura dei dati di configurazione dopo l'avvio dell'app.
- Opzioni denominate
- È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- È utile negli scenari in cui le opzioni devono essere ricalcolate in ogni richiesta. Per altre informazioni, vedere Usare IOptionsSnapshot per leggere i dati aggiornati.
- Viene registrato come con ambito e pertanto non può essere inserito in un servizio Singleton.
- Supporta le opzioni denominate
- Consente di recuperare le opzioni e gestire le notifiche delle opzioni per le istanze di
TOptions
. - È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- Supporta:
- Notifiche relative a modifiche
- opzioni denominate
- Configurazione ricaricabile
- Invalidamento selettivo di opzioni (IOptionsMonitorCache<TOptions>)
Gli scenari di post-configurazione abilitano l'impostazione o la modifica delle opzioni dopo che si verifica tutta la IConfigureOptions<TOptions> configurazione.
IOptionsFactory<TOptions> è responsabile della creazione di nuove istanze di opzioni. Include un singolo metodo Create. L'implementazione predefinita accetta tutte le interfacce IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrate ed esegue tutte le configurazioni, seguite dalla post-configurazione. Fa distinzione tra IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chiama solo l'interfaccia appropriata.
IOptionsMonitorCache<TOptions> viene usata da IOptionsMonitor<TOptions> per memorizzare nella cache le istanze di TOptions
. IOptionsMonitorCache<TOptions> invalida le istanze delle opzioni nel monitoraggio in modo che il valore venga ricalcolato (TryRemove). I valori possono essere introdotti manualmente con TryAdd. Il metodo Clear viene usato quando tutte le istanze denominate devono essere ricreate su richiesta.
Usare IOptionsSnapshot per leggere i dati aggiornati
Utilizzo di IOptionsSnapshot<TOptions>:
- Le opzioni vengono calcolate una volta per richiesta quando viene eseguito l'accesso e la memorizzazione nella cache per la durata della richiesta.
- Può comportare una riduzione significativa delle prestazioni perché si tratta di un servizio con ambito e viene ricalcorato per ogni richiesta. Per altre informazioni, vedere questo problema di GitHub e Migliorare le prestazioni dell'associazione di configurazione.
- Le modifiche alla configurazione vengono lette dopo l'avvio dell'app quando si usano provider di configurazione che supportano la lettura dei valori di configurazione aggiornati.
Differenze tra IOptionsMonitor
e IOptionsSnapshot
:
IOptionsMonitor
è un servizio Singleton che recupera i valori delle opzioni correnti in qualsiasi momento, particolarmente utile nelle dipendenze singleton.IOptionsSnapshot
è un servizio con ambito e fornisce uno snapshot delle opzioni al momento della costruzione dell'oggettoIOptionsSnapshot<T>
. Gli snapshot delle opzioni sono progettati per l'uso con dipendenze temporanee e con ambito.
Il codice seguente usa 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}");
}
}
Il codice seguente registra un'istanza di configurazione che MyOptions
associa a:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app vengono lette.
IOptionsMonitor
Il codice seguente registra un'istanza di configurazione che MyOptions
viene associata a .
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
Nell'esempio seguente viene usato 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Supporto delle opzioni denominate con IConfigureNamedOptions
Le opzioni denominate:
- Sono utili quando più sezioni di configurazione sono associate alle stesse proprietà.
- Viene fatta distinzione tra maiuscole e minuscole.
Considerare il file appsettings.json
seguente:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anziché creare due classi per associare TopItem:Month
e TopItem:Year
, viene usata la classe seguente per ogni sezione:
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;
}
Nel codice seguente vengono configurate le opzioni denominate:
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();
Il codice seguente mostra le opzioni denominate:
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" );
}
}
Tutte le opzioni sono istanze denominate. Le istanze di IConfigureOptions<TOptions> sono considerate come destinate all'istanza di Options.DefaultName
, ovvero string.Empty
. IConfigureNamedOptions<TOptions> implementa anche IConfigureOptions<TOptions>. L'implementazione predefinita di IOptionsFactory<TOptions> include la logica per usarle in modo appropriato. L'opzione denominata null
viene usata per avere come destinazione tutte le istanze denominate anziché un'istanza denominata specifica. Questa convenzione è usata da ConfigureAll e PostConfigureAll.
API OptionsBuilder
OptionsBuilder<TOptions> viene usata per configurare le istanze di TOptions
. OptionsBuilder
semplifica la creazione di opzioni denominate perché è costituita da un singolo parametro nella chiamata iniziale ad AddOptions<TOptions>(string optionsName)
invece di essere visualizzata in tutte le chiamate successive. La convalida delle opzioni e gli overload ConfigureOptions
che accettano le dipendenze dei servizi sono disponibili solo tramite OptionsBuilder
.
OptionsBuilder
è usata nella sezione Convalida delle opzioni.
Per informazioni sull'aggiunta di un repository personalizzato, vedere Usare AddOptions.
Usare i servizi di inserimento delle dipendenze per configurare le opzioni
Per accedere ai servizi dall'inserimento delle dipendenze durante la configurazione delle opzioni è possibile procedere in due modi:
Passare un delegato di configurazione a Configure in OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
fornisce overload di Configure che consentono l'uso di un massimo di cinque servizi per configurare le opzioni: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));
Creare un tipo che implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions> e registrare il tipo come servizio.
È consigliabile passare un delegato di configurazione a Configure, perché la creazione di un servizio è più complessa. La creazione di un tipo equivale a ciò che il framework esegue quando si chiama Configure. La chiamata Configure registra un generico IConfigureNamedOptions<TOptions>temporaneo, che dispone di un costruttore che accetta i tipi di servizio generici specificati.
Convalida delle opzioni
La convalida delle opzioni consente di convalidare i valori delle opzioni.
Considerare il file appsettings.json
seguente:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La classe seguente viene usata per eseguire l'associazione "MyConfig"
alla sezione di configurazione e applica un paio di DataAnnotations
regole:
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; }
}
Il codice seguente:
- Chiama AddOptions per ottenere un oggetto OptionsBuilder<TOptions> che viene associato alla
MyConfigOptions
classe . - Chiama ValidateDataAnnotations per abilitare la convalida tramite
DataAnnotations
.
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();
Il metodo di estensione ValidateDataAnnotations
viene definito nel pacchetto NuGet Microsoft.Extensions.Options.DataAnnotations. Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, questo pacchetto viene fatto riferimento in modo implicito dal framework condiviso.
Nel codice seguente vengono visualizzati i valori di configurazione o gli errori di convalida:
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);
}
Il codice seguente applica una regola di convalida più complessa usando un delegato:
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>
e IValidatableObject
La classe seguente implementa 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
consente di spostare il codice di Program.cs
convalida da e in una classe.
Usando il codice precedente, la convalida è abilitata in Program.cs
con il codice seguente:
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();
La convalida delle opzioni supporta IValidatableObjectanche . Per eseguire la convalida a livello di classe di una classe all'interno della classe stessa:
- Implementare l'interfaccia
IValidatableObject
e il relativo Validate metodo all'interno della classe . - Chiamare ValidateDataAnnotations in
Program.cs
.
ValidateOnStart
La convalida delle opzioni viene eseguita la prima volta che viene creata un'implementazione IOptions<TOptions>, IOptionsSnapshot<TOptions>o IOptionsMonitor<TOptions> . Per eseguire la convalida delle opzioni in modo eager, all'avvio dell'app, chiamare ValidateOnStart in Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Post-configurazione delle opzioni
Impostare la post-configurazione con IPostConfigureOptions<TOptions>. La post-configurazione viene eseguita dopo il completamento della configurazione di tutte le IConfigureOptions<TOptions>:
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 è disponibile per la post-configurazione delle opzioni denominate:
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();
Usare PostConfigureAll per la post-configurazione di tutte le istanze di configurazione:
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";
});
Opzioni di accesso in Program.cs
Per accedere IOptions<TOptions> a o IOptionsMonitor<TOptions> in Program.cs
, chiamare GetRequiredService su WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Risorse aggiuntive
Di Kirk Larkin e Rick Anderson.
Il modello di opzioni usa classi per fornire l'accesso fortemente tipizzato ai gruppi di impostazioni correlate. Quando le impostazioni di configurazione vengono isolate in base allo scenario in classi separate, l'app aderisce a due importanti principi di progettazione del software:
- Incapsulamento:
- Le classi che dipendono dalle impostazioni di configurazione dipendono solo dalle impostazioni di configurazione usate.
- Separazione dei problemi:
- Le impostazioni per parti diverse dell'app non sono dipendenti o associate l'una all'altra.
Le opzioni offrono anche un meccanismo per convalidare i dati di configurazione. Per altre informazioni, vedere la sezione Opzioni di convalida.
In questo argomento vengono fornite informazioni sul modello di opzioni in ASP.NET Core. Per informazioni sull'uso del modello di opzioni nelle app console, vedere Modello di opzioni in .NET.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Associare la configurazione gerarchica
Il modo preferito per leggere i valori di configurazione correlati prevede l'uso del modello di opzioni. Ad esempio, per leggere i valori di configurazione seguenti:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Creare la classe PositionOptions
seguente:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
Una classe di opzioni:
- Deve essere non astratta con un costruttore pubblico senza parametri.
- Tutte le proprietà di lettura/scrittura pubbliche del tipo sono associate.
- I campi non sono associati. Nel codice precedente
Position
non è associato. La proprietàPosition
viene usata in modo che la stringa"Position"
non debba essere hardcoded nell'app quando la classe viene associata a un provider di configurazione.
Il codice seguente:
- Chiama ConfigurationBinder.Bind per associare la classe
PositionOptions
alla sezionePosition
. - Visualizza i dati di configurazione di
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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
ConfigurationBinder.Get<T>
associa e restituisce il tipo specificato. ConfigurationBinder.Get<T>
può risultare più utile rispetto all'uso di ConfigurationBinder.Bind
. Il codice seguente mostra come usare ConfigurationBinder.Get<T>
con la classe 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Un approccio alternativo quando si usa il modello opzioni consiste nell'associare la sezione Position
e aggiungerla al contenitore del servizio di inserimento di dipendenze. Nel codice seguente PositionOptions
viene aggiunta al contenitore del servizio con Configure e associata alla configurazione:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
Quando si usa il codice precedente, il codice seguente legge le opzioni di posizione:
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}");
}
}
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app non vengono lette. Per leggere le modifiche dopo l'avvio dell'app, usare IOptionsSnapshot.
Interfacce per le opzioni
- non supporta:
- La lettura dei dati di configurazione dopo l'avvio dell'app.
- Opzioni denominate
- È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- È utile negli scenari in cui le opzioni devono essere ricalcolate in ogni richiesta. Per altre informazioni, vedere Usare IOptionsSnapshot per leggere i dati aggiornati.
- È registrata come Con ambito e non può quindi essere inserita in un servizio Singleton.
- Supporta le opzioni denominate
- Consente di recuperare le opzioni e gestire le notifiche delle opzioni per le istanze di
TOptions
. - È registrata come Singleton e può essere inserita in qualsiasi durata del servizio.
- Supporta:
- Notifiche relative a modifiche
- Opzioni denominate
- Configurazione ricaricabile
- Invalidamento selettivo di opzioni (IOptionsMonitorCache<TOptions>)
Gli scenari di post-configurazione abilitano l'impostazione o la modifica delle opzioni dopo che si verifica tutta la IConfigureOptions<TOptions> configurazione.
IOptionsFactory<TOptions> è responsabile della creazione di nuove istanze di opzioni. Include un singolo metodo Create. L'implementazione predefinita accetta tutte le interfacce IConfigureOptions<TOptions> e IPostConfigureOptions<TOptions> registrate ed esegue tutte le configurazioni, seguite dalla post-configurazione. Fa distinzione tra IConfigureNamedOptions<TOptions> e IConfigureOptions<TOptions> e chiama solo l'interfaccia appropriata.
IOptionsMonitorCache<TOptions> viene usata da IOptionsMonitor<TOptions> per memorizzare nella cache le istanze di TOptions
. IOptionsMonitorCache<TOptions> invalida le istanze delle opzioni nel monitoraggio in modo che il valore venga ricalcolato (TryRemove). I valori possono essere introdotti manualmente con TryAdd. Il metodo Clear viene usato quando tutte le istanze denominate devono essere ricreate su richiesta.
Usare IOptionsSnapshot per leggere i dati aggiornati
Usando IOptionsSnapshot<TOptions>, le opzioni vengono calcolate una volta per ogni richiesta quando si accede e memorizzato nella cache per la durata della richiesta. Le modifiche alla configurazione vengono lette dopo l'avvio dell'app quando si usano provider di configurazione che supportano la lettura dei valori di configurazione aggiornati.
Differenze tra IOptionsMonitor
e IOptionsSnapshot
:
IOptionsMonitor
è un servizio Singleton che recupera i valori delle opzioni correnti in qualsiasi momento, particolarmente utile nelle dipendenze singleton.IOptionsSnapshot
è un servizio con ambito e fornisce uno snapshot delle opzioni al momento della costruzione dell'oggettoIOptionsSnapshot<T>
. Gli snapshot delle opzioni sono progettati per l'uso con dipendenze temporanee e con ambito.
Il codice seguente usa 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}");
}
}
Il codice seguente registra un'istanza di configurazione che MyOptions
associa a:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
Nel codice precedente le modifiche al file di configurazione JSON dopo l'avvio dell'app vengono lette.
IOptionsMonitor
Il codice seguente registra un'istanza di configurazione che MyOptions
viene associata a .
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
Nell'esempio seguente viene usato 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}");
}
}
Nel codice precedente, per impostazione predefinita, le modifiche apportate al file di configurazione JSON dopo l'avvio dell'app vengono lette.
Supporto delle opzioni denominate con IConfigureNamedOptions
Le opzioni denominate:
- Sono utili quando più sezioni di configurazione sono associate alle stesse proprietà.
- Viene fatta distinzione tra maiuscole e minuscole.
Considerare il file appsettings.json
seguente:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
Anziché creare due classi per associare TopItem:Month
e TopItem:Year
, viene usata la classe seguente per ogni sezione:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
Nel codice seguente vengono configurate le opzioni denominate:
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();
}
Il codice seguente mostra le opzioni denominate:
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" );
}
}
Tutte le opzioni sono istanze denominate. Le istanze di IConfigureOptions<TOptions> sono considerate come destinate all'istanza di Options.DefaultName
, ovvero string.Empty
. IConfigureNamedOptions<TOptions> implementa anche IConfigureOptions<TOptions>. L'implementazione predefinita di IOptionsFactory<TOptions> include la logica per usarle in modo appropriato. L'opzione denominata null
viene usata per avere come destinazione tutte le istanze denominate anziché un'istanza denominata specifica. Questa convenzione è usata da ConfigureAll e PostConfigureAll.
API OptionsBuilder
OptionsBuilder<TOptions> viene usata per configurare le istanze di TOptions
. OptionsBuilder
semplifica la creazione di opzioni denominate perché è costituita da un singolo parametro nella chiamata iniziale ad AddOptions<TOptions>(string optionsName)
invece di essere visualizzata in tutte le chiamate successive. La convalida delle opzioni e gli overload ConfigureOptions
che accettano le dipendenze dei servizi sono disponibili solo tramite OptionsBuilder
.
OptionsBuilder
è usata nella sezione Convalida delle opzioni.
Per informazioni sull'aggiunta di un repository personalizzato, vedere Usare AddOptions.
Usare i servizi di inserimento delle dipendenze per configurare le opzioni
Per accedere ai servizi dall'inserimento delle dipendenze durante la configurazione delle opzioni è possibile procedere in due modi:
Passare un delegato di configurazione a Configure in OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
fornisce overload di Configure che consentono l'uso di un massimo di cinque servizi per configurare le opzioni:services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Creare un tipo che implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions> e registrare il tipo come servizio.
È consigliabile passare un delegato di configurazione a Configure, perché la creazione di un servizio è più complessa. La creazione di un tipo equivale a ciò che il framework esegue quando si chiama Configure. La chiamata Configure registra un generico IConfigureNamedOptions<TOptions>temporaneo, che dispone di un costruttore che accetta i tipi di servizio generici specificati.
Convalida delle opzioni
La convalida delle opzioni consente di convalidare i valori delle opzioni.
Considerare il file appsettings.json
seguente:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La classe seguente viene associata alla sezione di configurazione "MyConfig"
e applica un paio di regole DataAnnotations
:
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; }
}
Il codice seguente:
- Chiama AddOptions per ottenere un oggetto OptionsBuilder<TOptions> che viene associato alla
MyConfigOptions
classe . - Chiama ValidateDataAnnotations per abilitare la convalida tramite
DataAnnotations
.
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();
}
Il metodo di estensione ValidateDataAnnotations
viene definito nel pacchetto NuGet Microsoft.Extensions.Options.DataAnnotations. Per le app Web che usano l'SDK Microsoft.NET.Sdk.Web
, questo pacchetto viene fatto riferimento in modo implicito dal framework condiviso.
Nel codice seguente vengono visualizzati i valori di configurazione o gli errori di convalida:
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);
}
Il codice seguente applica una regola di convalida più complessa usando un delegato:
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 per la convalida complessa
La classe seguente implementa 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
consente di spostare il codice di StartUp
convalida da e in una classe.
Usando il codice precedente, la convalida è abilitata in Startup.ConfigureServices
con il codice seguente:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
Post-configurazione delle opzioni
Impostare la post-configurazione con IPostConfigureOptions<TOptions>. La post-configurazione viene eseguita dopo il completamento della configurazione di tutte le IConfigureOptions<TOptions>:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
PostConfigure è disponibile per la post-configurazione delle opzioni denominate:
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Usare PostConfigureAll per la post-configurazione di tutte le istanze di configurazione:
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Accesso alle opzioni durante l'avvio
IOptions<TOptions> e IOptionsMonitor<TOptions> possono essere usate in Startup.Configure
, perché i servizi vengono compilati prima dell'esecuzione del metodo Configure
.
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
Non usare IOptions<TOptions> oppure IOptionsMonitor<TOptions> in Startup.ConfigureServices
. Le opzioni potrebbero avere uno stato incoerente a causa dell'ordinamento delle registrazioni dei servizi.
Pacchetto NuGet Options.ConfigurationExtensions
Il pacchetto Microsoft.Extensions.Options.ConfigurationExtensions viene fatto riferimento in modo implicito nelle app ASP.NET Core.