ASP.NET CORE

Validación de la Configuración en ASP.NET Core Mediante el Patrón de Opciones

ASP.NET Core permite validar los valores de configuración de clases de opciones de tres formas:

Atributos de Anotación de Datos ↑

Como su nombre lo indica, permite realizar validaciones por medio de atributos anotaciones de datos. Las mismas que son listadas a continuación:

Atributo Descripción
AllowedValuesEspecifica una lista de valores que se deben permitir en una propiedad .
AssociationEspecifica que un miembro de entidad representa una relación de datos, como una relación de clave externa.
Base64StringEspecifica que un valor de campo de datos es una cadena Base64 bien formada.
CompareProporciona un atributo que compara dos propiedades.
ConcurrencyCheckEspecifica que una propiedad participa en las comprobaciones de simultaneidad optimista.
CreditCardEspecifica que el valor de un campo de datos es un número de tarjeta de crédito.
CustomValidationEspecifica un método de validación personalizado que se usa para validar una instancia de clase o propiedad.
DataTypeEspecifica el nombre de un tipo adicional para asociar con un campo de datos.
DeniedValuesEspecifica una lista de valores que no se deben permitir en una propiedad .
DisplayProporciona un atributo de uso general que permite especificar cadenas localizables para tipos y miembros de clases parciales de entidad.
DisplayColumnEspecifica la columna que se muestra en la tabla a la que se hizo referencia como una columna de clave externa.
DisplayFormatEspecifica el modo en que los datos dinámicos de ASP.NET muestran y dan formato a los campos de datos.
EditableIndica si un campo de datos es editable.
EmailAddressValida una dirección de correo electrónico.
EnumDataTypePermite que se asigne una enumeración .NET a una columna de datos.
FileExtensionsValida las extensiones de nombre de archivo.
FilterUIHintRepresenta un atributo que se usa para especificar el comportamiento de filtrado de una columna.
KeyDenota una o varias propiedades que identifican una entidad de forma única.
LengthEspecifica la longitud mínima y máxima de los datos de colección o cadena permitidos en una propiedad .
MaxLengthEspecifica la longitud máxima de los datos de matriz o de cadena permitida en una propiedad.
MetadataTypeEspecifica la clase de metadatos que debe asociarse a una clase de modelo de datos.
MinLengthEspecifica la longitud mínima de los datos de matriz o de cadena permitida en una propiedad.
PhoneEspecifica que un valor de campo de datos es un número de teléfono con formato correcto.
RangeEspecifica las restricciones de intervalo numérico para el valor de un campo de datos.
RegularExpressionEspecifica que un valor de campo de datos en los datos dinámicos de ASP.NET debe coincidir con la expresión regular especificada.
RequiredEspecifica que se requiere un valor de campo de datos.
ScaffoldColumnEspecifica si una clase o una columna de datos usan la técnica scaffolding.
StringLengthEspecifica la longitud mínima y máxima de caracteres que se permite en un campo de datos.
TimestampEspecifica el tipo de datos de la columna como una versión de fila.
UIHintEspecifica la plantilla o el control de usuario que los datos dinámicos usan para mostrar un campo de datos.
UrlProporciona la validación de la dirección URL.

Considere el siguiente archivo appsettings.json:

appsettings.json
{
   "MongoDbSettings": {
      "Host": "localhost",
      "Port": 27017
   }
}

El siguiente código implementa atributos de anotación de datos en la clase de opciones MongoDbSettings

MongoDbSettings.cs
using System.ComponentModel.DataAnnotations;

public class MongoDbSettings
{
    [Required, Url]
    public string? Host { get; set; }
    
    [Required]
    [Range(0,
           65535,
           ErrorMessage = "Valor de {0} debe estar entre {1} y {2}.")]
    public int Port { get; set; }
}

El código C# anterior:

Mediante el siguiente código, se configura la validación de la clase de opciones MongoDbSettings

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOptions()
                .BindConfiguration(nameof(MongoDbSettings))
                .ValidateDataAnnotations()
                .ValidateOnStart();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.WriteIndented = true;
});

var app = builder.Build();

app.MapGet("/", () => app.Configuration.AsEnumerable());
app.Run();

El código C# anterior:

Resultados de la validación de la propiedad Host:

Validación Host 1
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=DataAnnotation validation failed for 'MongoDbSettings' members: 'Host' with the error: 'The Host field is required.'.
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.
Validación Host 2
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=DataAnnotation validation failed for 'MongoDbSettings' members: 'Host' with the error: 'The Host field is not a valid fully-qualified http, https, or ftp URL.'.
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.

Resultado de la validación de la propiedad Port:

Validación Port
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=DataAnnotation validation failed for 'MongoDbSettings' members: 'Port' with the error: 'Valor de Port debe estar entre 0 y 65535.'.
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.
Información
INFORMACIÓN

A partir de .NET 8 puede usar un método alternativo a ValidateOnStart; AddOptionsWithValidateOnStart, que habilita la validación al iniciarse para un tipo de opciones específico.

Delegado ↑

Se pueden agregar una o más reglas de validación mediante el método Validate, el cual registra una acción de validación más compleja para un tipo de opciones.

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOptions()
                .BindConfiguration(nameof(MongoDbSettings))
                .ValidateDataAnnotations()
                .Validate(config =>
                {
                    if (config.Host == "http://localhost/")
                    {
                        return config.Port == 27017;
                    }

                    return true;
                }, "Si el Host es http://localhost/, el puerto debe ser 27017")
                .Validate(mongoDbSettings =>
                {
                    return mongoDbSettings.Host.StartsWith("https://");
                }, "El host debe iniciar con https://")
                .ValidateOnStart();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.WriteIndented = true;
});

var app = builder.Build();

app.MapGet("/", () => app.Configuration.AsEnumerable());
app.Run();

El código C# anterior:

Resultado de la validación 1:

Resultado
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=Si el Host es http://localhost/, el puerto debe ser 27017
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.

Resultado de la validación 2:

Resultado
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=El host debe iniciar con https://
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.

Interfaz IValidateOptions ↑

La interfaz IValidateOptions permite implementar las reglas de validación dentro de una clase.

La siguiente clase ValidateMongoDbSettings implementa la interfaz IValidateOptions:

ValidateMongoDbSettings.cs
using Microsoft.Extensions.Options;
using System.Text;

public class ValidateMongoDbSettings : IValidateOptions
{
    public ValidateOptionsResult Validate(string? name,
                                          MongoDbSettings options)
    {
        StringBuilder? error = null;

        if (options.Port is < 0 or > 65535)
        {
            (error ??= new()).AppendLine($"Valor de puerto {options.Port} debe estar entre 0 y 65535.");
        }

        if (options.Host == "http://localhost/")
        {
            if (options.Port != 27017)
            {
                (error ??= new()).AppendLine($"Número de puerto incorrecto: {options.Port}. Si el Host es http://localhost/, el puerto debe ser 27017");
            }
        }

        return error is not null
            ? ValidateOptionsResult.Fail(error.ToString())
            : ValidateOptionsResult.Success;
    }
}

Que es configurada mediante el siguiente código de la clase Program.cs:

Program.cs
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOptions<MongoDbSettings>()
                .BindConfiguration(nameof(MongoDbSettings))
                .ValidateOnStart();

builder.Services.AddSingleton(serviceType =>
                              serviceType.GetRequiredService<IOptions<MongoDbSettings>>().Value);

builder.Services.TryAddEnumerable(
                    ServiceDescriptor.Singleton<IValidateOptions<MongoDbSettings>, ValidateMongoDbSettings>());

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.WriteIndented = true;
});

var app = builder.Build();

app.MapGet("/", () => app.Configuration.AsEnumerable());
app.Run();

Resultado de la validación:

Resultado
Microsoft.Extensions.Options.OptionsValidationException
 HResult=0x80131500
 Message=Número de puerto incorrecto: 37017. Si el Host es http://localhost/, el puerto debe ser 27017
 Source=Microsoft.Extensions.Options
 StackTrace... //Omitido por razones de brevedad.

Artículos Relacionados

Recursos Adicionales