El Patrón de Opciones en C#
[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]
La configuración en aplicaciones .NET típicamente reside en appsettings.json, variables de entorno o secretos de usuario. Sacar esos datos de configuración de un archivo y llevarlos a tus clases de una manera limpia y comprobable es lo que resuelve el patrón de opciones. En lugar de leer manualmente claves JSON o pasar cadenas crudas por todas partes, vinculas una sección de configuración a una clase C# fuertemente tipada y dejas que la inyección de dependencias la entregue donde sea necesario.
En su video "The Options Pattern in C#", Tim Corey introduce las tres variaciones del patrón de opciones (IOptions, IOptionsSnapshot, y IOptionsMonitor), demuestra cada una en una aplicación de servidor Blazor y muestra cómo agregar validación para que su aplicación falle rápidamente cuando falta o está malformada la configuración. Si estás construyendo cualquier proyecto .NET que lea de archivos de configuración y use inyección de dependencias, este patrón es fundamental.
Configuración: Un Modelo POCO y appsettings.json
[0:34 - 1:46] Tim comienza con una aplicación web Blazor (renderizada en el lado del servidor, sin interactividad en el lado del cliente) que ya tiene dos piezas en su lugar. La primera es una sección en appsettings.json con tres pares clave-valor bajo una sección CloudInfo:
// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}La segunda pieza es una clase C# simple (un POCO) cuyas propiedades reflejan las claves JSON:
public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}Tim señala que la fuente de configuración no tiene que ser appsettings.json específicamente. Podría ser appsettings.Development.json, secrets.json, o cualquier combinación. El patrón de opciones lee de cualquier proveedor de configuración registrado en la aplicación, y los nombres de las propiedades en el POCO coinciden con las claves JSON por convención.
Registrar opciones en Program.cs
[2:25 - 3:15] Conectar la configuración a la inyección de dependencias requiere dos llamadas a métodos en Program.cs:
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");AddOptions<t>() registra el tipo con DI. BindConfiguration("CloudInfo") indica al framework qué sección de la configuración mapear. La cadena "CloudInfo" corresponde a la clave JSON en appsettings.json. Si el nombre de la sección y el nombre de la clase no coinciden, el argumento de cadena es lo que el framework usa para encontrar los datos correctos.
IOptions: El Enfoque del Singleton
[3:15 - 5:18] With the registration in place, Tim injects the configuration into a Blazor page using IOptions<CloudInfoOptions>:
@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}El detalle crítico es que IOptions<t> está registrado como un singleton. Los valores de configuración se leen una vez cuando la aplicación se inicia y se almacenan en caché por la vida del proceso. Si cambia appsettings.json mientras la aplicación está corriendo, IOptions no reflejará esos cambios.
Para la mayoría de las aplicaciones, esta es la opción correcta. La configuración rara vez cambia en tiempo de ejecución, y la vida útil del singleton significa cero sobrecarga por solicitud.
IOptionsSnapshot: Recarga Acotada
[5:18 - 6:55] Tim cambia IOptions por IOptionsSnapshot para demostrar la variante con alcance:
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig@inject IOptionsSnapshot<CloudInfoOptions> CloudConfigIOptionsSnapshot<t> lee la configuración fresca para cada petición (duración con alcance). Si modifica appsettings.json mientras la aplicación está corriendo, la siguiente petición web recogerá los nuevos valores. La solicitud anterior mantiene su propio snapshot, por lo que no hay inconsistencia en mitad de la solicitud.
Esto es útil para aplicaciones donde los cambios de configuración deben surtir efecto sin un reinicio, como el cambio de banderas de características, actualización de endpoints de API, o rotación de cadenas de conexión de almacenamiento. La desventaja es un pequeño costo por solicitud para volver a leer la configuración, que es insignificante para la mayoría de las cargas de trabajo.
IOptionsMonitor: Notificaciones de Cambio en Vivo
[6:55 - 8:34] La tercera variante, IOptionsMonitor<t>, va más allá que la recarga de instantáneas. Monitorea activamente los cambios de configuración y puede activar callbacks cuando los valores se actualizan:
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}A diferencia de IOptionsSnapshot, que solo se refresca entre peticiones, IOptionsMonitor puede detectar cambios durante una operación de larga duración. Tim señala que esto es más relevante para servicios en segundo plano, hubs SignalR, o cualquier componente con una vida útil más larga que una sola solicitud HTTP.
Añadiendo Validación
[8:34 - 10:05] The final piece Tim covers is validation. El patrón de opciones admite reglas de validación en línea que se ejecutan cuando la configuración se accede por primera vez:
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));Tim demuestra el caso de fallo eliminando el valor Storage de appsettings.json y lanzando la aplicación. El resultado es una excepción no manejada inmediata: "CloudInfoOptions falló en la validación." La aplicación se niega a comenzar con la configuración incompleta, que es exactamente el comportamiento que deseas. Descubrir un valor faltante al inicio es mucho mejor que enfrentar una referencia nula en producción a las 2 AM.
Para escenarios de validación más complejos (comprobaciones entre propiedades, reglas condicionales), puede encadenar múltiples llamadas a .Validate() o implementar IValidateOptions<t> como una clase de validación dedicada.
Conclusión: Tres Interfaces, Un Patrón
[10:05 - 10:10] El patrón de opciones proporciona una separación limpia entre el almacenamiento y el consumo de la configuración. IOptions cubre la mayoría de los casos de uso donde la configuración es estática. IOptionsSnapshot maneja aplicaciones que necesitan recoger cambios entre peticiones. IOptionsMonitor sirve a componentes de larga duración que necesitan conocer actualizaciones de configuración en tiempo real. Y la validación asegura que tu aplicación falle ruidosamente cuando faltan valores requeridos.
Conclusión
[10:10 - 10:15] Para resumir: registre su configuración con AddOptions<t>().BindConfiguration("SectionName") en Program.cs, inyecte una de las tres interfaces (IOptions, IOptionsSnapshot, o IOptionsMonitor) dependiendo de sus necesidades de recarga, y agregue .Validate() para capturar valores faltantes al inicio en lugar de en tiempo de ejecución.
El patrón funciona en cualquier tipo de proyecto .NET: Blazor, ASP.NET Core, aplicaciones de consola, servicios de trabajo. El registro es el mismo en todas partes.
Consejo de ejemplo: si no está seguro de qué interfaz usar, comience con IOptions<t>. Es la más simple, no tiene sobrecarga por solicitud, y cubre la gran mayoría de los casos donde la configuración se establece al inicio y no cambia. Solo pase a IOptionsSnapshot o IOptionsMonitor cuando tenga una necesidad concreta de recarga en tiempo de ejecución.
Mira el video completo en su video en su canal de YouTube y obtén más información sobre los patrones de configuración de C#.

