Patrón de opciones en ASP.NET Core
Nota:
Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.
Importante
Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.
Para la versión actual, consulte la versión de .NET 9 de este artículo.
Por Rick Anderson.
El patrón de opciones usa clases para proporcionar acceso fuertemente tipado a grupos de configuraciones relacionadas. Cuando los valores de configuración están aislados por escenario en clases independientes, la aplicación se ajusta a dos principios de ingeniería de software importantes:
- Encapsulación:
- las clases que dependen de valores de configuración dependen únicamente de los valores de configuración que usen.
- Separación de intereses:
- los valores de configuración para distintos elementos de la aplicación no son dependientes entre sí ni están emparejados.
Las opciones también proporcionan un mecanismo para validar los datos de configuración. Para obtener más información, consulta la sección Opciones de validación.
En este artículo se proporciona información sobre el patrón de opciones de ASP.NET Core. Para obtener más información sobre el uso del patrón de opciones en las aplicaciones de consola, consulta Patrón de opciones en .NET.
Enlace de configuración jerárquica
La mejor manera de leer valores de configuración relacionados es usar el patrón de opciones. Por ejemplo, para leer los siguientes valores de configuración:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crea la siguiente clase PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Una clase de opciones:
- Debe ser no abstracto.
- Tiene propiedades públicas de lectura y escritura del tipo que tienen los elementos correspondientes en la configuración enlazadas.
- Tiene sus propiedades de lectura y escritura enlazadas a entradas coincidentes en la configuración.
- No tienen sus campos enlazados. En el código anterior,
Position
no está enlazado. El campoPosition
se usa para que la cadena"Position"
no se tenga que codificar de forma rígida en la aplicación al enlazar la clase a un proveedor de configuración.
El código siguiente:
- Llama a ConfigurationBinder.Bind para enlazar la clase
PositionOptions
a la secciónPosition
. - Muestra los datos de configuración de
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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
ConfigurationBinder.Get<T>
enlaza y devuelve el tipo especificado. Puede ser más conveniente usar ConfigurationBinder.Get<T>
que ConfigurationBinder.Bind
. En el código siguiente se muestra cómo puede usar ConfigurationBinder.Get<T>
con la clase 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Enlazar también permite la concreción de una clase abstracta. Tenga en cuenta el código siguiente que usa la clase abstracta SomethingWithAName
:
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;
}
El código siguiente muestra los valores de configuración de NameTitleOptions
:
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}"
);
}
}
Las llamadas a Bind
son menos estrictas que las llamadas a Get<>
:
Bind
permite la concreción de un abstracto.Get<>
tiene que crear una instancia propiamente dicha.
El patrón de opciones
Un enfoque alternativo a la hora de usar el patrón de opciones consiste en enlazar la sección Position
y agregarla al contenedor del servicio de inserción de dependencias. En el siguiente código se agrega PositionOptions
al contenedor de servicios con Configure y se enlaza a la configuración:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
A partir del código anterior, el siguiente código lee las opciones de posición:
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}");
}
}
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación no se leen. Para leer los cambios una vez iniciada la aplicación, usa IOptionsSnapshot.
Interfaces de opciones
- No admite:
- Lectura de los datos de configuración una vez iniciada la aplicación.
- Opciones con nombre
- Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- es útil en escenarios donde se deben volver a calcular las opciones en cada solicitud. Para obtener más información, consulte el apartado Uso de IOptionsSnapshot para leer datos actualizados.
- Se registra como Con ámbito y, por tanto, no se puede insertar en un servicio Singleton.
- Admite opciones con nombre
- se usa para recuperar las opciones y administrar las notificaciones de las opciones para instancias de
TOptions
. - Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- Es compatible con:
- Notificaciones de cambios
- opciones con nombre
- Configuración que se puede recargar
- Invalidación de opciones de selección (IOptionsMonitorCache<TOptions>)
Los escenarios posteriores a la configuración permiten establecer o cambiar las opciones después de que finalice toda la configuración de IConfigureOptions<TOptions>.
IOptionsFactory<TOptions> es responsable de crear nuevas instancias de opciones. Tiene un solo método Create. La implementación predeterminada toma todas las instancias registradas de IConfigureOptions<TOptions> y IPostConfigureOptions<TOptions>, y establece todas las configuraciones primero, seguidas de las configuraciones posteriores. Distingue entre IConfigureNamedOptions<TOptions> y IConfigureOptions<TOptions>, y solo llama a la interfaz adecuada.
IOptionsMonitorCache<TOptions> se usa por IOptionsMonitor<TOptions> para almacenar en caché las instancias de TOptions
. IOptionsMonitorCache<TOptions> invalida instancias de opciones en la supervisión para que se pueda volver a calcular el valor (TryRemove). Los valores se pueden introducir manualmente y mediante TryAdd. Se usa el método Clear cuando todas las instancias con nombre se deben volver a crear a petición.
Uso de IOptionsSnapshot para leer datos actualizados
Usar IOptionsSnapshot<TOptions>:
- Cuando se accede a las opciones y se las almacena en caché durante la vigencia de la solicitud, se calculan una vez por solicitud.
- Puede sufrir una pérdida de rendimiento significativa porque es un servicio con ámbito y se vuelve a calcular por solicitud. Para obtener más información, consulte Este problema de GitHub y Mejorar el rendimiento del enlace de configuración.
- Los cambios en la configuración se leen tras iniciarse la aplicación al usar proveedores de configuración que admiten la lectura de valores de configuración actualizados.
La diferencia entre IOptionsMonitor
y IOptionsSnapshot
es que:
IOptionsMonitor
es un servicio singleton que recupera los valores de las opciones actuales en cualquier momento, lo que resulta especialmente útil en las dependencias singleton.IOptionsSnapshot
es un servicio con ámbito y proporciona una instantánea de las opciones en el momento en que se construye el objetoIOptionsSnapshot<T>
. Las instantáneas de opciones están diseñadas para usarlas con dependencias transitorias y con ámbito.
El código siguiente 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}");
}
}
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
IOptionsMonitor
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
En el ejemplo siguiente se usa 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Compatibilidad de opciones con nombre con IConfigureNamedOptions
Opciones con nombre:
- son útiles cuando varias secciones de configuración se enlazan a las mismas propiedades.
- Distinguen mayúsculas de minúsculas.
Fíjese en el siguiente archivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
En lugar de crear dos clases para enlazar TopItem:Month
y TopItem:Year
, se usará la clase siguiente para cada sección:
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;
}
El siguiente código configura las opciones con nombre:
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();
En el código siguiente se muestran las opciones con nombre:
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" );
}
}
Todas las opciones son instancias con nombre. Las instancias de IConfigureOptions<TOptions> se usan para seleccionar como destino la instancia de Options.DefaultName
, que es string.Empty
. IConfigureNamedOptions<TOptions> también implementa IConfigureOptions<TOptions>. La implementación predeterminada de IOptionsFactory<TOptions> tiene lógica para usar cada una de forma adecuada. La opción con nombre null
se usa para seleccionar como destino todas las instancias con nombre, en lugar de una instancia con nombre determinada. ConfigureAll y PostConfigureAll usan esta convención.
API OptionsBuilder
OptionsBuilder<TOptions> se usa para configurar instancias TOptions
. OptionsBuilder
simplifica la creación de opciones con nombre, ya que es un único parámetro para la llamada AddOptions<TOptions>(string optionsName)
inicial en lugar de aparecer en todas las llamadas posteriores. La validación de opciones y las sobrecargas ConfigureOptions
que aceptan las dependencias de servicio solo están disponibles mediante OptionsBuilder
.
OptionsBuilder
se usa en la sección Opciones de validación.
Vea Uso de AddOptions para configurar un repositorio personalizado para obtener información sobre cómo agregar un repositorio personalizado.
Uso de servicios de DI para configurar opciones
Hay dos formas de acceder a los servicios desde la inserción de dependencias durante la configuración de opciones:
Pasar un delegado de configuración a Configure en OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
proporciona sobrecargas de Configure que permiten usar hasta cinco servicios para configurar las opciones: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));
Si se crea un tipo que implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions>, y se registra como un servicio.
Se recomienda pasar un delegado de configuración a Configure, ya que la creación de un servicio es más complicada. La creación de un tipo es equivalente a lo que el marco hace cuando se llama a Configure. La llamada a Configure registra una interfaz IConfigureNamedOptions<TOptions> genérica y transitoria, que tiene un constructor que acepta los tipos de servicio genéricos especificados.
Opciones de validación
Opciones de validación permite que se validen los valores de opción.
Fíjese en el siguiente archivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La siguiente clase se enlaza a la sección de configuración "MyConfig"
y aplica un par de reglas 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; }
}
El código siguiente:
- Llama a AddOptions para obtener un elemento OptionsBuilder<TOptions> que se enlaza a la clase
MyConfigOptions
. - Llama a ValidateDataAnnotations para habilitar la validación con
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();
El método de extensión ValidateDataAnnotations
se define en el paquete NuGet Microsoft.Extensions.Options.DataAnnotations. En el caso de las aplicaciones web que usan el SDK de Microsoft.NET.Sdk.Web
, se hace referencia implícita a este paquete desde el marco compartido.
En el código siguiente se muestran los valores de configuración o los errores de validación:
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);
}
El código siguiente aplica una regla de validación más compleja mediante un delegado:
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>
y IValidatableObject
La siguiente clase 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
permite mover el código de validación fuera de Program.cs
y dentro de una clase.
Con el código anterior, la validación se habilita en Program.cs
con el código siguiente:
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 validación de opciones también admite IValidatableObject. Para realizar la validación de nivel de clase de una clase dentro de la propia clase:
- Implemente la interfaz
IValidatableObject
y su método Validate dentro de la clase. - Llame a ValidateDataAnnotations en
Program.cs
.
ValidateOnStart
La validación de opciones se ejecuta la primera vez que se crea una TOption
instancia. Esto significa, por ejemplo, cuando se produce el primer acceso a IOptionsSnapshot<TOptions>.Value
en una canalización de solicitud o cuando IOptionsMonitor<TOptions>.Get(string)
se llama a en la configuración presente. Después de volver a cargar la configuración, la validación se vuelve a ejecutar. El entorno de ejecución de ASP.NET Core usa OptionsCache<TOptions> para almacenar en caché la instancia de opciones una vez creada.
Para ejecutar la validación de opciones diligentemente, cuando se inicie la aplicación, llama a ValidateOnStart<TOptions>(OptionsBuilder<TOptions>) en Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Configuración posterior de las opciones
Establece la configuración posterior con IPostConfigureOptions<TOptions>. La configuración posterior se ejecuta una vez completada toda la configuración de 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 está disponible para configurar posteriormente las opciones con nombre:
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();
Usa PostConfigureAll para configurar posteriormente todas las instancias de configuración:
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";
});
Opciones de acceso en Program.cs
Para acceder a IOptions<TOptions> o IOptionsMonitor<TOptions> en Program.cs
, llama a GetRequiredService en WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Recursos adicionales
Por Kirk Larkin y Rick Anderson.
El patrón de opciones usa clases para proporcionar acceso fuertemente tipado a grupos de configuraciones relacionadas. Cuando los valores de configuración están aislados por escenario en clases independientes, la aplicación se ajusta a dos principios de ingeniería de software importantes:
- Encapsulación:
- las clases que dependen de valores de configuración dependen únicamente de los valores de configuración que usen.
- Separación de intereses:
- los valores de configuración para distintos elementos de la aplicación no son dependientes entre sí ni están emparejados.
Las opciones también proporcionan un mecanismo para validar los datos de configuración. Para obtener más información, consulta la sección Opciones de validación.
En este artículo se proporciona información sobre el patrón de opciones de ASP.NET Core. Para obtener más información sobre el uso del patrón de opciones en las aplicaciones de consola, consulta Patrón de opciones en .NET.
Enlace de configuración jerárquica
La mejor manera de leer valores de configuración relacionados es usar el patrón de opciones. Por ejemplo, para leer los siguientes valores de configuración:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crea la siguiente clase PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
Una clase de opciones:
- Debe ser no abstracta con un constructor público sin parámetros.
- Todas las propiedades de lectura y escritura públicas del tipo están enlazadas.
- Los campos no se enlazan. En el código anterior,
Position
no está enlazado. El campoPosition
se usa para que la cadena"Position"
no se tenga que codificar de forma rígida en la aplicación al enlazar la clase a un proveedor de configuración.
El código siguiente:
- Llama a ConfigurationBinder.Bind para enlazar la clase
PositionOptions
a la secciónPosition
. - Muestra los datos de configuración de
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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
ConfigurationBinder.Get<T>
enlaza y devuelve el tipo especificado. Puede ser más conveniente usar ConfigurationBinder.Get<T>
que ConfigurationBinder.Bind
. En el código siguiente se muestra cómo puede usar ConfigurationBinder.Get<T>
con la clase 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Un enfoque alternativo a la hora de usar el patrón de opciones consiste en enlazar la sección Position
y agregarla al contenedor del servicio de inserción de dependencias. En el siguiente código se agrega PositionOptions
al contenedor de servicios con Configure y se enlaza a la configuración:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
A partir del código anterior, el siguiente código lee las opciones de posición:
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}");
}
}
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación no se leen. Para leer los cambios una vez iniciada la aplicación, usa IOptionsSnapshot.
Interfaces de opciones
- No admite:
- Lectura de los datos de configuración una vez iniciada la aplicación.
- Opciones con nombre
- Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- es útil en escenarios donde se deben volver a calcular las opciones en cada solicitud. Para obtener más información, consulte el apartado Uso de IOptionsSnapshot para leer datos actualizados.
- Se registra como Con ámbito y, por tanto, no se puede insertar en un servicio Singleton.
- Admite opciones con nombre
- se usa para recuperar las opciones y administrar las notificaciones de las opciones para instancias de
TOptions
. - Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- Es compatible con:
- Notificaciones de cambios
- opciones con nombre
- Configuración que se puede recargar
- Invalidación de opciones de selección (IOptionsMonitorCache<TOptions>)
Los escenarios posteriores a la configuración permiten establecer o cambiar las opciones después de que finalice toda la configuración de IConfigureOptions<TOptions>.
IOptionsFactory<TOptions> es responsable de crear nuevas instancias de opciones. Tiene un solo método Create. La implementación predeterminada toma todas las instancias registradas de IConfigureOptions<TOptions> y IPostConfigureOptions<TOptions>, y establece todas las configuraciones primero, seguidas de las configuraciones posteriores. Distingue entre IConfigureNamedOptions<TOptions> y IConfigureOptions<TOptions>, y solo llama a la interfaz adecuada.
IOptionsMonitorCache<TOptions> se usa por IOptionsMonitor<TOptions> para almacenar en caché las instancias de TOptions
. IOptionsMonitorCache<TOptions> invalida instancias de opciones en la supervisión para que se pueda volver a calcular el valor (TryRemove). Los valores se pueden introducir manualmente y mediante TryAdd. Se usa el método Clear cuando todas las instancias con nombre se deben volver a crear a petición.
Uso de IOptionsSnapshot para leer datos actualizados
Usar IOptionsSnapshot<TOptions>:
- Cuando se accede a las opciones y se las almacena en caché durante la vigencia de la solicitud, se calculan una vez por solicitud.
- Puede sufrir una pérdida de rendimiento significativa porque es un servicio con ámbito y se vuelve a calcular por solicitud. Para obtener más información, consulte Este problema de GitHub y Mejorar el rendimiento del enlace de configuración.
- Los cambios en la configuración se leen tras iniciarse la aplicación al usar proveedores de configuración que admiten la lectura de valores de configuración actualizados.
La diferencia entre IOptionsMonitor
y IOptionsSnapshot
es que:
IOptionsMonitor
es un servicio singleton que recupera los valores de las opciones actuales en cualquier momento, lo que resulta especialmente útil en las dependencias singleton.IOptionsSnapshot
es un servicio con ámbito y proporciona una instantánea de las opciones en el momento en que se construye el objetoIOptionsSnapshot<T>
. Las instantáneas de opciones están diseñadas para usarlas con dependencias transitorias y con ámbito.
El código siguiente 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}");
}
}
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
:
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
IOptionsMonitor
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
.
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
En el ejemplo siguiente se usa 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Compatibilidad de opciones con nombre con IConfigureNamedOptions
Opciones con nombre:
- son útiles cuando varias secciones de configuración se enlazan a las mismas propiedades.
- Distinguen mayúsculas de minúsculas.
Fíjese en el siguiente archivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
En lugar de crear dos clases para enlazar TopItem:Month
y TopItem:Year
, se usará la clase siguiente para cada sección:
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;
}
El siguiente código configura las opciones con nombre:
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();
En el código siguiente se muestran las opciones con nombre:
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" );
}
}
Todas las opciones son instancias con nombre. Las instancias de IConfigureOptions<TOptions> se usan para seleccionar como destino la instancia de Options.DefaultName
, que es string.Empty
. IConfigureNamedOptions<TOptions> también implementa IConfigureOptions<TOptions>. La implementación predeterminada de IOptionsFactory<TOptions> tiene lógica para usar cada una de forma adecuada. La opción con nombre null
se usa para seleccionar como destino todas las instancias con nombre, en lugar de una instancia con nombre determinada. ConfigureAll y PostConfigureAll usan esta convención.
API OptionsBuilder
OptionsBuilder<TOptions> se usa para configurar instancias TOptions
. OptionsBuilder
simplifica la creación de opciones con nombre, ya que es un único parámetro para la llamada AddOptions<TOptions>(string optionsName)
inicial en lugar de aparecer en todas las llamadas posteriores. La validación de opciones y las sobrecargas ConfigureOptions
que aceptan las dependencias de servicio solo están disponibles mediante OptionsBuilder
.
OptionsBuilder
se usa en la sección Opciones de validación.
Vea Uso de AddOptions para configurar un repositorio personalizado para obtener información sobre cómo agregar un repositorio personalizado.
Uso de servicios de DI para configurar opciones
Hay dos formas de acceder a los servicios desde la inserción de dependencias durante la configuración de opciones:
Pasar un delegado de configuración a Configure en OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
proporciona sobrecargas de Configure que permiten usar hasta cinco servicios para configurar las opciones: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));
Si se crea un tipo que implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions>, y se registra como un servicio.
Se recomienda pasar un delegado de configuración a Configure, ya que la creación de un servicio es más complicada. La creación de un tipo es equivalente a lo que el marco hace cuando se llama a Configure. La llamada a Configure registra una interfaz IConfigureNamedOptions<TOptions> genérica y transitoria, que tiene un constructor que acepta los tipos de servicio genéricos especificados.
Opciones de validación
Opciones de validación permite que se validen los valores de opción.
Fíjese en el siguiente archivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La siguiente clase se enlaza a la sección de configuración "MyConfig"
y aplica un par de reglas 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; }
}
El código siguiente:
- Llama a AddOptions para obtener un elemento OptionsBuilder<TOptions> que se enlaza a la clase
MyConfigOptions
. - Llama a ValidateDataAnnotations para habilitar la validación con
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();
El método de extensión ValidateDataAnnotations
se define en el paquete NuGet Microsoft.Extensions.Options.DataAnnotations. En el caso de las aplicaciones web que usan el SDK de Microsoft.NET.Sdk.Web
, se hace referencia implícita a este paquete desde el marco compartido.
En el código siguiente se muestran los valores de configuración o los errores de validación:
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);
}
El código siguiente aplica una regla de validación más compleja mediante un delegado:
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>
y IValidatableObject
La siguiente clase 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
permite mover el código de validación fuera de Program.cs
y dentro de una clase.
Con el código anterior, la validación se habilita en Program.cs
con el código siguiente:
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 validación de opciones también admite IValidatableObject. Para realizar la validación de nivel de clase de una clase dentro de la propia clase:
- Implemente la interfaz
IValidatableObject
y su método Validate dentro de la clase. - Llame a ValidateDataAnnotations en
Program.cs
.
ValidateOnStart
La validación de opciones se ejecuta la primera vez que se crea una implementación IOptions<TOptions>, IOptionsSnapshot<TOptions> o IOptionsMonitor<TOptions>. Para ejecutar la validación de opciones diligentemente, cuando se inicie la aplicación, llama a ValidateOnStart en Program.cs
:
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
Configuración posterior de las opciones
Establece la configuración posterior con IPostConfigureOptions<TOptions>. La configuración posterior se ejecuta una vez completada toda la configuración de 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 está disponible para configurar posteriormente las opciones con nombre:
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();
Usa PostConfigureAll para configurar posteriormente todas las instancias de configuración:
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";
});
Opciones de acceso en Program.cs
Para acceder a IOptions<TOptions> o IOptionsMonitor<TOptions> en Program.cs
, llama a GetRequiredService en WebApplication.Services:
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
Recursos adicionales
Por Kirk Larkin y Rick Anderson.
El patrón de opciones usa clases para proporcionar acceso fuertemente tipado a grupos de configuraciones relacionadas. Cuando los valores de configuración están aislados por escenario en clases independientes, la aplicación se ajusta a dos principios de ingeniería de software importantes:
- Encapsulación:
- las clases que dependen de valores de configuración dependen únicamente de los valores de configuración que usen.
- Separación de intereses:
- los valores de configuración para distintos elementos de la aplicación no son dependientes entre sí ni están emparejados.
Las opciones también proporcionan un mecanismo para validar los datos de configuración. Para obtener más información, consulte la sección Opciones de validación.
En este tema se proporciona información sobre el patrón de opciones de ASP.NET Core. Para más información sobre el uso del patrón de opciones en las aplicaciones de consola, consulte Patrón de opciones en .NET.
Vea o descargue el código de ejemplo (cómo descargarlo)
Enlace de configuración jerárquica
La mejor manera de leer valores de configuración relacionados es usar el patrón de opciones. Por ejemplo, para leer los siguientes valores de configuración:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
Crea la siguiente clase PositionOptions
:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
Una clase de opciones:
- Debe ser no abstracta con un constructor público sin parámetros.
- Todas las propiedades de lectura y escritura públicas del tipo están enlazadas.
- Los campos no se enlazan. En el código anterior,
Position
no está enlazado. La propiedadPosition
se usa para que la cadena"Position"
no tenga que estar codificada de forma rígida en la aplicación al enlazar la clase a un proveedor de configuración.
El código siguiente:
- Llama a ConfigurationBinder.Bind para enlazar la clase
PositionOptions
a la secciónPosition
. - Muestra los datos de configuración de
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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
ConfigurationBinder.Get<T>
enlaza y devuelve el tipo especificado. Puede ser más conveniente usar ConfigurationBinder.Get<T>
que ConfigurationBinder.Bind
. En el código siguiente se muestra cómo puede usar ConfigurationBinder.Get<T>
con la clase 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Un enfoque alternativo a la hora de usar el patrón de opciones consiste en enlazar la sección Position
y agregarla al contenedor del servicio de inserción de dependencias. En el siguiente código se agrega PositionOptions
al contenedor de servicios con Configure y se enlaza a la configuración:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
A partir del código anterior, el siguiente código lee las opciones de posición:
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}");
}
}
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación no se leen. Para leer los cambios una vez iniciada la aplicación, usa IOptionsSnapshot.
Interfaces de opciones
- No admite:
- Lectura de los datos de configuración una vez iniciada la aplicación.
- Opciones con nombre
- Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- es útil en escenarios donde se deben volver a calcular las opciones en cada solicitud. Para obtener más información, consulte el apartado Uso de IOptionsSnapshot para leer datos actualizados.
- Se registra como Con ámbito y, por tanto, no se puede insertar en un servicio Singleton.
- Admite opciones con nombre
- se usa para recuperar las opciones y administrar las notificaciones de las opciones para instancias de
TOptions
. - Se registra como Singleton y se puede insertar en cualquier duración del servicio.
- Es compatible con:
- Notificaciones de cambios
- Opciones con nombre
- Configuración que se puede recargar
- Invalidación de opciones de selección (IOptionsMonitorCache<TOptions>)
Los escenarios posteriores a la configuración permiten establecer o cambiar las opciones después de que finalice toda la configuración de IConfigureOptions<TOptions>.
IOptionsFactory<TOptions> es responsable de crear nuevas instancias de opciones. Tiene un solo método Create. La implementación predeterminada toma todas las instancias registradas de IConfigureOptions<TOptions> y IPostConfigureOptions<TOptions>, y establece todas las configuraciones primero, seguidas de las configuraciones posteriores. Distingue entre IConfigureNamedOptions<TOptions> y IConfigureOptions<TOptions>, y solo llama a la interfaz adecuada.
IOptionsMonitorCache<TOptions> se usa por IOptionsMonitor<TOptions> para almacenar en caché las instancias de TOptions
. IOptionsMonitorCache<TOptions> invalida instancias de opciones en la supervisión para que se pueda volver a calcular el valor (TryRemove). Los valores se pueden introducir manualmente y mediante TryAdd. Se usa el método Clear cuando todas las instancias con nombre se deben volver a crear a petición.
Uso de IOptionsSnapshot para leer datos actualizados
Al usar IOptionsSnapshot<TOptions>, cuando se accede a las opciones y se las almacena en caché durante la vigencia de la solicitud, se calculan una vez por solicitud. Los cambios en la configuración se leen tras iniciarse la aplicación al usar proveedores de configuración que admiten la lectura de valores de configuración actualizados.
La diferencia entre IOptionsMonitor
y IOptionsSnapshot
es que:
IOptionsMonitor
es un servicio singleton que recupera los valores de las opciones actuales en cualquier momento, lo que resulta especialmente útil en las dependencias singleton.IOptionsSnapshot
es un servicio con ámbito y proporciona una instantánea de las opciones en el momento en que se construye el objetoIOptionsSnapshot<T>
. Las instantáneas de opciones están diseñadas para usarlas con dependencias transitorias y con ámbito.
El código siguiente 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}");
}
}
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
En el código anterior, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
IOptionsMonitor
El código siguiente registra una instancia de configuración en la que se enlaza MyOptions
.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
En el ejemplo siguiente se usa 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}");
}
}
En el código anterior, de forma predeterminada, los cambios en el archivo de configuración de JSON producidos una vez iniciada la aplicación se leen.
Compatibilidad de opciones con nombre con IConfigureNamedOptions
Opciones con nombre:
- son útiles cuando varias secciones de configuración se enlazan a las mismas propiedades.
- Distinguen mayúsculas de minúsculas.
Fíjese en el siguiente archivo appsettings.json
:
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
En lugar de crear dos clases para enlazar TopItem:Month
y TopItem:Year
, se usará la clase siguiente para cada sección:
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
El siguiente código configura las opciones con nombre:
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();
}
En el código siguiente se muestran las opciones con nombre:
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" );
}
}
Todas las opciones son instancias con nombre. Las instancias de IConfigureOptions<TOptions> se usan para seleccionar como destino la instancia de Options.DefaultName
, que es string.Empty
. IConfigureNamedOptions<TOptions> también implementa IConfigureOptions<TOptions>. La implementación predeterminada de IOptionsFactory<TOptions> tiene lógica para usar cada una de forma adecuada. La opción con nombre null
se usa para seleccionar como destino todas las instancias con nombre, en lugar de una instancia con nombre determinada. ConfigureAll y PostConfigureAll usan esta convención.
API OptionsBuilder
OptionsBuilder<TOptions> se usa para configurar instancias TOptions
. OptionsBuilder
simplifica la creación de opciones con nombre, ya que es un único parámetro para la llamada AddOptions<TOptions>(string optionsName)
inicial en lugar de aparecer en todas las llamadas posteriores. La validación de opciones y las sobrecargas ConfigureOptions
que aceptan las dependencias de servicio solo están disponibles mediante OptionsBuilder
.
OptionsBuilder
se usa en la sección Opciones de validación.
Vea Uso de AddOptions para configurar un repositorio personalizado para obtener información sobre cómo agregar un repositorio personalizado.
Uso de servicios de DI para configurar opciones
Hay dos formas de acceder a los servicios desde la inserción de dependencias durante la configuración de opciones:
Pasar un delegado de configuración a Configure en OptionsBuilder<TOptions>.
OptionsBuilder<TOptions>
proporciona sobrecargas de Configure que permiten usar hasta cinco servicios para configurar las opciones:services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
Si se crea un tipo que implementa IConfigureOptions<TOptions> o IConfigureNamedOptions<TOptions>, y se registra como un servicio.
Se recomienda pasar un delegado de configuración a Configure, ya que la creación de un servicio es más complicada. La creación de un tipo es equivalente a lo que el marco hace cuando se llama a Configure. La llamada a Configure registra una interfaz IConfigureNamedOptions<TOptions> genérica y transitoria, que tiene un constructor que acepta los tipos de servicio genéricos especificados.
Opciones de validación
Opciones de validación permite que se validen los valores de opción.
Fíjese en el siguiente archivo appsettings.json
:
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
La siguiente clase se enlaza a la sección de configuración "MyConfig"
y aplica un par de reglas 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; }
}
El código siguiente:
- Llama a AddOptions para obtener un elemento OptionsBuilder<TOptions> que se enlaza a la clase
MyConfigOptions
. - Llama a ValidateDataAnnotations para habilitar la validación con
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();
}
El método de extensión ValidateDataAnnotations
se define en el paquete NuGet Microsoft.Extensions.Options.DataAnnotations. En el caso de las aplicaciones web que usan el SDK de Microsoft.NET.Sdk.Web
, se hace referencia implícita a este paquete desde el marco compartido.
En el código siguiente se muestran los valores de configuración o los errores de validación:
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);
}
El código siguiente aplica una regla de validación más compleja mediante un delegado:
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 para la validación compleja
La siguiente clase 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
permite mover el código de validación fuera de StartUp
y dentro de una clase.
Con el código anterior, la validación se habilita en Startup.ConfigureServices
con el código siguiente:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
Configuración posterior de las opciones
Establece la configuración posterior con IPostConfigureOptions<TOptions>. La configuración posterior se ejecuta una vez completada toda la configuración de IConfigureOptions<TOptions>:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
PostConfigure está disponible para configurar posteriormente las opciones con nombre:
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Use PostConfigureAll para configurar posteriormente todas las instancias de configuración:
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Acceso a opciones durante el inicio
IOptions<TOptions> y IOptionsMonitor<TOptions> puede usarse en Startup.Configure
, ya que los servicios se compilan antes de que se ejecute el método Configure
.
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
No use IOptions<TOptions> o IOptionsMonitor<TOptions> en Startup.ConfigureServices
. Puede que exista un estado incoherente de opciones debido al orden de los registros de servicio.
Paquete NuGet Options.ConfigurationExtensions
Se hace referencia implícita al paquete Microsoft.Extensions.Options.ConfigurationExtensions en las aplicaciones de ASP.NET Core.