validación de formularios de Blazor Core de ASP.NET

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 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 .NET 8 de este artículo.

En este artículo se explica cómo usar la validación en formularios Blazor.

Validación de formularios

En escenarios de validación básica de formularios, una instancia EditForm puede usar instancias EditContext y ValidationMessageStore declaradas para validar campos de formulario. Un controlador para el evento OnValidationRequested de EditContext ejecuta la lógica de validación personalizada. El resultado del controlador actualiza la instancia ValidationMessageStore.

La validación básica de formularios es útil en los casos en los que el modelo del formulario se define dentro del componente que hospeda el formulario, ya sea como miembros directamente en el componente o en una subclase. Se recomienda el uso de un componente de validador cuando se utiliza una clase de modelo independiente en varios componentes.

En el componente siguiente, el método de controlador HandleValidationRequested borra los mensajes de validación existentes llamando a ValidationMessageStore.Clear antes de validar el formulario.

Starship8.razor:

@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}

Componente validador de anotaciones de datos y validación personalizada

El componente DataAnnotationsValidator expone la validación de anotaciones de datos en un EditContext en cascada. La habilitación de la validación de anotaciones de datos requiere el componente DataAnnotationsValidator. Para usar un sistema de validación distinto al de las anotaciones de datos, use una implementación personalizada del componente DataAnnotationsValidator. Las implementaciones del marco de DataAnnotationsValidator están disponibles para su inspección en el origen de referencia.

Nota

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Blazor realiza dos tipos de validación:

  • La validación del campo se realiza cuando el usuario sale de un campo usando la tecla TAB. Durante una validación del campo, el componente DataAnnotationsValidator asocia al campo todos los resultados de validación notificados.
  • La validación del modelo se realiza cuando el usuario envía el formulario. Durante una validación del modelo, el componente DataAnnotationsValidator intenta determinar el campo en función del nombre del miembro que se indica en el resultado de la validación. Los resultados de validación que no están asociados a un miembro individual se asocian al modelo en lugar de a un campo.

Componentes de validador

Los componentes de validador admiten la validación de formularios mediante la administración de un objeto ValidationMessageStore para el objeto EditContext de un formulario.

El marco Blazor proporciona el componente DataAnnotationsValidator para adjuntar la compatibilidad con la validación a formularios en función de atributos de validación (anotaciones de datos). Puede crear componentes de validador personalizados para procesar los mensajes de validación para diferentes formularios de la misma página o el mismo formulario en distintos pasos del procesamiento de formularios (por ejemplo, la validación del lado cliente seguida de la del lado servidor). El ejemplo de componente de validador que se muestra en esta sección, CustomValidation, se usa en las secciones siguientes de este artículo:

Nota:

En muchos casos, se pueden usar atributos de validación de anotación de datos personalizados en lugar de componentes de validador personalizados. Los atributos personalizados que se aplican al modelo del formulario se activan con el uso del componente DataAnnotationsValidator. Cuando se usan con la validación del lado servidor, los atributos personalizados que se aplican al modelo deben ser ejecutables en el servidor. Para obtener más información, vea Validación de modelos en ASP.NET Core MVC.

Cree un componente de validador a partir de ComponentBase:

  • El objeto EditContext del formulario es un parámetro en cascada del componente.
  • Cuando se inicializa el componente de validador, se crea un objeto ValidationMessageStore para mantener una lista actual de errores del formulario.
  • El almacén de mensajes recibe errores cuando el código del desarrollador del componente del formulario llama al método DisplayErrors. Los errores se pasan al método DisplayErrors en un objeto Dictionary<string, List<string>>. En el diccionario, la clave es el nombre del campo de formulario que tiene uno o más errores. El valor es la lista de errores.
  • Los mensajes se borran cuando se produce alguno de los casos siguientes:
    • La validación se solicita en el objeto EditContext cuando se genera el evento OnValidationRequested. Todos los errores se borran.
    • Un campo cambia en el formulario cuando se genera el evento OnFieldChanged. Solo se borran los errores del campo.
    • El código del desarrollador llama al método ClearErrors. Todos los errores se borran.

Actualice el espacio de nombres de la siguiente clase para que coincida con el espacio de nombres de la aplicación.

CustomValidation.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

public class CustomValidation : ComponentBase
{
    private ValidationMessageStore? messageStore;

    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }

    protected override void OnInitialized()
    {
        if (CurrentEditContext is null)
        {
            throw new InvalidOperationException(
                $"{nameof(CustomValidation)} requires a cascading " +
                $"parameter of type {nameof(EditContext)}. " +
                $"For example, you can use {nameof(CustomValidation)} " +
                $"inside an {nameof(EditForm)}.");
        }

        messageStore = new(CurrentEditContext);

        CurrentEditContext.OnValidationRequested += (s, e) => 
            messageStore?.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore?.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        if (CurrentEditContext is not null)
        {
            foreach (var err in errors)
            {
                messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        CurrentEditContext?.NotifyValidationStateChanged();
    }
}

Importante

Es necesario especificar un espacio de nombres al derivar de ComponentBase. Si no se especifica un espacio de nombres, se produce un error de compilación:

Los asistentes de etiquetas no pueden tener como destino el nombre de etiqueta "<global namespace>.{CLASS NAME}" porque contiene un carácter " ".

El marcador de posición {CLASS NAME} es el nombre de la clase de componente. El ejemplo de validador personalizado de esta sección especifica el espacio de nombres de ejemplo BlazorSample.

Nota

Las expresiones lambda anónimas son controladores de eventos registrados para OnValidationRequested y OnFieldChanged en el ejemplo anterior. No es necesario implementar IDisposable y cancelar la suscripción de los delegados de eventos en este escenario. Para más información, vea Ciclo de vida de componentes Razor de ASP.NET Core.

Validación de la lógica de negocios con un componente de validador

Para la validación de la lógica de negocios general, utilice un componente de validador que recibe errores del formulario en un diccionario.

La validación básica es útil en los casos en los que el modelo del formulario se define dentro del componente que hospeda el formulario, ya sea como miembros directamente en el componente o en una subclase. Se recomienda el uso de un componente de validador cuando se utiliza una clase de modelo independiente en varios componentes.

En el ejemplo siguiente:

  • Se utiliza una versión abreviada del formulario Starfleet Starship Database (componente Starship3) de la sección Formulario de ejemplo del artículo Componentes de entrada que solo acepta la clasificación y descripción de la nave espacial. La validación de anotaciones de datos no se desencadena al enviar el formulario porque el componente DataAnnotationsValidator no está incluido en el formulario.
  • Se usa el componente CustomValidation de la sección Componentes de validador de este artículo.
  • La validación necesita un valor para la descripción del envío (Description) si el usuario selecciona la clasificación de envío ”Defense” (Classification).

Cuando los mensajes de validación se establecen en el componente, se agregan al componente ValidationMessageStore del validador y se muestran en el resumen de validación de EditForm.

Starship9.razor:

@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}
@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}

Nota:

Como alternativa al uso de componentes de validación, se pueden utilizar atributos de validación de anotación de datos. Los atributos personalizados que se aplican al modelo del formulario se activan con el uso del componente DataAnnotationsValidator. cuando se usan con la validación del lado servidor, los atributos deben ser ejecutables en el servidor. Para obtener más información, vea Validación de modelos en ASP.NET Core MVC.

Validación del servidor con un componente de validador

Esta sección se centra en Blazor escenarios de aplicación web, pero el enfoque para cualquier tipo de aplicación que use la validación del servidor con la API web adopta el mismo enfoque general.

Esta sección se centra en escenarios de Blazor WebAssembly hospedados, pero el enfoque para cualquier tipo de aplicación que use la validación del servidor con la API web adopta el mismo enfoque general.

La validación del lado servidor se admite además de la validación del lado cliente:

  • Procese la validación del lado cliente en el formulario con el componente DataAnnotationsValidator.
  • Cuando el formulario pasa la validación del lado cliente (se llama a OnValidSubmit), envíe el objeto EditContext.Model a una API de servidor backend para el procesamiento del formulario.
  • Procese la validación del modelo en el servidor.
  • La API del servidor incluye la validación de anotaciones de datos del marco integrada y la lógica de validación personalizada proporcionada por el desarrollador. Si la validación se supera en el servidor, procese el formulario y devuelva un código de estado correcto (200 - OK). Si se produce un error en la validación, devuelva un código de estado de error (400 - Bad Request) y los errores de validación de campos.
  • Si es correcto, puede deshabilitar el formulario, o bien mostrar los errores.

La validación básica es útil en los casos en los que el modelo del formulario se define dentro del componente que hospeda el formulario, ya sea como miembros directamente en el componente o en una subclase. Se recomienda el uso de un componente de validador cuando se utiliza una clase de modelo independiente en varios componentes.

El ejemplo siguiente se basa en:

Coloque el modelo Starship (Starship.cs) en un proyecto de biblioteca de clases compartida para que los proyectos de cliente y servidor puedan usar el modelo. Agregue o actualice el espacio de nombres para que coincida con el espacio de nombres de la aplicación compartida (por ejemplo, namespace BlazorSample.Shared). Dado que el modelo requiere anotaciones de datos, confirme que la biblioteca de clases compartida usa el marco compartido o agregue el paquete de System.ComponentModel.Annotations al proyecto compartido.

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

En el proyecto principal de la aplicación web de Blazor, agregue un controlador para procesar solicitudes de validación de nave estelar y devuelva mensajes de validación con errores. Actualice los espacios de nombres de la última instrucción using para el proyecto de biblioteca de clases compartida y namespace para la clase de controlador. Además de la validación de anotaciones de datos del lado cliente y servidor, el controlador valida que se proporciona un valor para la descripción del envío (Description) si el usuario selecciona la clasificación de envío Defense (Classification).

Coloque el modelo Starship (Starship.cs) en el proyecto Shared de la solución para que las aplicaciones cliente y servidor puedan usarlo. Agregue o actualice el espacio de nombres para que coincida con el espacio de nombres de la aplicación compartida (por ejemplo, namespace BlazorSample.Shared). Como en el modelo se necesitan anotaciones de datos, agregue el paquete System.ComponentModel.Annotations al proyecto Shared:

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

En el proyecto Server , agregue un controlador para procesar las solicitudes de validación de Starship y devuelva los mensajes de validación con errores: Actualice los espacios de nombres de la última instrucción using para el proyecto Shared y namespace para la clase de controlador. Además de la validación de anotaciones de datos del lado cliente y servidor, el controlador valida que se proporciona un valor para la descripción del envío (Description) si el usuario selecciona la clasificación de envío Defense (Classification).

La validación de la clasificación de envío Defense solo se produce en el lado servidor del controlador porque el formulario siguiente no realiza la mismo validación del lado cliente cuando el formulario se envía al servidor. La validación del servidor sin validación de cliente es común en las aplicaciones que requieren la validación de lógica de negocios privada de la entrada de usuario en el servidor. Por ejemplo, la información privada de los datos almacenados para un usuario podría ser necesaria para validar la entrada del usuario. Obviamente, los datos privados no se pueden enviar al cliente para la validación del lado cliente.

Nota:

El controlador StarshipValidation de esta sección utiliza Microsoft Identity 2.0. La API web solo acepta tokens para los usuarios que tienen el ámbito ”API.Access" para esta API. Se requiere personalización adicional si el nombre de ámbito de la API es diferente de API.Access.

Para más información acerca de la seguridad del agente, consulte:

Controllers/StarshipValidation.cs:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
    private readonly ILogger<StarshipValidationController> logger;

    public StarshipValidationController(
        ILogger<StarshipValidationController> logger)
    {
        this.logger = logger;
    }

    static readonly string[] scopeRequiredByApi = new[] { "API.Access" };

    [HttpPost]
    public async Task<IActionResult> Post(Starship model)
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        try
        {
            if (model.Classification == "Defense" && 
                string.IsNullOrEmpty(model.Description))
            {
                ModelState.AddModelError(nameof(model.Description),
                    "For a 'Defense' ship " +
                    "classification, 'Description' is required.");
            }
            else
            {
                logger.LogInformation("Processing the form asynchronously");

                // async ...

                return Ok(ModelState);
            }
        }
        catch (Exception ex)
        {
            logger.LogError("Validation Error: {Message}", ex.Message);
        }

        return BadRequest(ModelState);
    }
}

Confirme o actualice el espacio de nombres del controlador anterior (BlazorSample.Server.Controllers) para que coincida con el espacio de nombres de los controladores de la aplicación.

Cuando se produce un error de validación de enlace de modelos en el servidor, un objeto ApiController (ApiControllerAttribute) devuelve normalmente una respuesta de solicitud incorrecta predeterminada con un objeto ValidationProblemDetails. La respuesta contiene otros datos además de los errores de validación, como se muestra en el ejemplo siguiente cuando no se envían todos los campos del formulario Starfleet Starship Database y no supera la validación:

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Id": ["The Id field is required."],
    "Classification": ["The Classification field is required."],
    "IsValidatedDesign": ["This form disallows unapproved ships."],
    "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
  }
}

Nota:

para mostrar la respuesta JSON anterior, debe deshabilitar la validación del lado cliente del formulario para permitir el envío de formularios de campo vacíos, o bien usar una herramienta para enviar una solicitud directamente a la API del servidor, como Firefox Browser Developer.

Si la API del servidor devuelve la respuesta JSON predeterminada anterior, el cliente puede analizarla en el código para desarrolladores para obtener los elementos secundarios del nodo errors para el procesamiento de los errores de validación de los formularios. No es conveniente escribir código para desarrolladores para analizar el archivo. El análisis manual de JSON requiere generar un Dictionary<string, List<string>> de errores después de llamar a ReadFromJsonAsync. Idealmente, la API de servidor solo debe devolver los errores de validación, como se muestra en el ejemplo siguiente:

{
  "Id": ["The Id field is required."],
  "Classification": ["The Classification field is required."],
  "IsValidatedDesign": ["This form disallows unapproved ships."],
  "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}

Para modificar la respuesta de la API de servidor a fin de que solo devuelva los errores de validación, cambie el delegado que se invoca en las acciones anotadas con ApiControllerAttribute en el archivo Program. Para el punto de conexión de API (/StarshipValidation), devuelva un objeto BadRequestObjectResult con el objeto ModelStateDictionary. Para cualquier otro punto de conexión de API, conserve el comportamiento predeterminado mediante la devolución el resultado del objeto con un objeto ValidationProblemDetails nuevo.

Agregue el espacio de nombres Microsoft.AspNetCore.Mvc a la parte superior del archivo Program en el proyecto principal de la aplicación web de Blazor:

using Microsoft.AspNetCore.Mvc;

En el archivo Program, agregue o actualice el siguiente método de extensión AddControllersWithViews y agregue la siguiente llamada a ConfigureApiBehaviorOptions:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Si va a agregar controladores al proyecto principal de la aplicación web de Blazor por primera vez, asigne los puntos de conexión del controlador al colocar el código anterior que registra los servicios para los controladores. En el ejemplo siguiente se usan rutas de controlador predeterminadas:

app.MapDefaultControllerRoute();

Nota:

En el ejemplo anterior se registran explícitamente los servicios de controlador llamando a AddControllersWithViews para mitigar automáticamente los ataques de falsificación de solicitud entre sitios (XSRF/CSRF). Si simplemente usa AddControllers, la anti-falsificación no se habilita automáticamente.

Para obtener más información sobre las respuestas de error de enrutamiento y validación del controlador, consulte los siguientes recursos:

En el proyecto .Client, agregue el componente CustomValidation que se muestra en la sección Componentes de validador. Actualice el espacio de nombres para que coincida con la aplicación (por ejemplo, namespace BlazorSample.Client).

En el proyecto .Client , el formulario Starfleet Starship Database se actualiza para mostrar los errores de validación del servidor con la ayuda del componente CustomValidation. Cuando la API de servidor devuelve mensajes de validación, se agregan al objeto ValidationMessageStoredel componente CustomValidation. Los errores están disponibles en el objeto EditContext del formulario para que los muestre el resumen de validación del formulario.

En el componente siguiente, actualice el espacio de nombres del proyecto compartido (@using BlazorSample.Shared) al espacio de nombres del proyecto compartido. Tenga en cuenta que el formulario requiere autorización, por lo que el usuario debe haber iniciado sesión en la aplicación para desplazarse al formulario.

Agregue el espacio de nombres Microsoft.AspNetCore.Mvc a la parte superior del archivo Program en la aplicación Server :

using Microsoft.AspNetCore.Mvc;

En el archivo Program, busque el método de extensión AddControllersWithViews y agregue la llamada siguiente a ConfigureApiBehaviorOptions:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Nota:

En el ejemplo anterior se registran explícitamente los servicios de controlador llamando a AddControllersWithViews para mitigar automáticamente los ataques de falsificación de solicitud entre sitios (XSRF/CSRF). Si simplemente usa AddControllers, la anti-falsificación no se habilita automáticamente.

En el proyecto Client, agregue el componente CustomValidation que se muestra en la sección Componentes de validador. Actualice el espacio de nombres para que coincida con la aplicación (por ejemplo, namespace BlazorSample.Client).

En el proyecto Client , el formulario Starfleet Starship Database se actualiza para mostrar los errores de validación del servidor con la ayuda del componente CustomValidation. Cuando la API de servidor devuelve mensajes de validación, se agregan al objeto ValidationMessageStoredel componente CustomValidation. Los errores están disponibles en el objeto EditContext del formulario para que los muestre el resumen de validación del formulario.

En el componente siguiente, actualice el espacio de nombres del proyecto Shared (@using BlazorSample.Shared) al espacio de nombres del proyecto compartido. Tenga en cuenta que el formulario requiere autorización, por lo que el usuario debe haber iniciado sesión en la aplicación para desplazarse al formulario.

Starship10.razor:

Nota:

De forma predeterminada, los formularios basados en EditForm habilitan automáticamente compatibilidad contra la falsificación. El controlador debe usar AddControllersWithViews para registrar servicios de controlador y habilitar automáticamente la compatibilidad contra la falsificación para la API web.

@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

El proyecto .Client de una aplicación web de Blazor también debe registrar un HttpClient para las solicitudes HTTP POST a un controlador de API web de back-end. Confirme o agregue lo siguiente al archivo Program del proyecto de .Client :

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

En el ejemplo anterior se establece la dirección base con builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), que obtiene la dirección base de la aplicación y se deriva normalmente del valor href de la etiqueta <base> en la página host.

@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

Nota:

Como alternativa al uso de un componente de validación, se pueden usar atributos de validación de anotación de datos. Los atributos personalizados que se aplican al modelo del formulario se activan con el uso del componente DataAnnotationsValidator. cuando se usan con la validación del lado servidor, los atributos deben ser ejecutables en el servidor. Para obtener más información, vea Validación de modelos en ASP.NET Core MVC.

Nota:

El enfoque de validación del lado servidor de esta sección es adecuado para cualquiera de los ejemplos de soluciones Blazor WebAssembly hospedadas de este conjunto de documentación:

InputText basado en el evento de entrada

Use el componente InputText para crear un componente personalizado que use el evento oninput (input) en lugar del evento onchange (change). El uso del evento input desencadena la validación de campos en cada pulsación de tecla.

El componente CustomInputText siguiente hereda el componente InputText del marco y establece el enlace de los eventos en el evento oninput (input).

CustomInputText.razor:

@inherits InputText

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />

El componente CustomInputText se puede usar en cualquier lugar donde InputText se use. El componente siguiente usa el componente CustomInputText compartido.

Starship11.razor:

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <CustomInputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <CustomInputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

Resumen de validación y componentes de los mensajes de validación

El componente ValidationSummary, que resume todos los mensajes de validación, es similar a la aplicación auxiliar de etiquetas ValidationSummary:

<ValidationSummary />

Mensajes de validación de salida de un modelo específico con el parámetro Model:

<ValidationSummary Model="Model" />

El componente ValidationMessage<TValue>, que muestra los mensajes de validación de un campo específico, es similar a la aplicación auxiliar de etiquetas Validation Message. Especifique el campo de validación con el atributo For y una expresión lambda donde se indique la propiedad del modelo:

<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />

Los componentes ValidationMessage<TValue> y ValidationSummary admiten atributos arbitrarios. Cualquier atributo que no coincida con un parámetro de componente se agrega a los elementos <div> o <ul> generados.

Controle el estilo de los mensajes de validación en la hoja de estilos de la aplicación (wwwroot/css/app.css o wwwroot/css/site.css). La clase validation-message predeterminada establece el color del texto de los mensajes de validación en rojo:

.validation-message {
    color: red;
}

Determinar si un campo de formulario es válido

Use EditContext.IsValid para determinar si un campo es válido sin obtener mensajes de validación.

Compatible, pero no recomendado:

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

Recomendado:

var isValid = editContext.IsValid(fieldIdentifier);

Atributos de validación personalizados

Para asegurarnos de que un resultado de validación está correctamente asociado a un campo cuando se usa un atributo de validación personalizado, pase el elemento MemberName del contexto de validación al crear el elemento ValidationResult.

CustomValidator.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
        ValidationContext validationContext)
    {
        ...

        return new ValidationResult("Validation message to user.",
            new[] { validationContext.MemberName });
    }
}

Inserte servicios en atributos de validación personalizados a través de ValidationContext. En el ejemplo siguiente se muestra un formulario de un chef de ensaladas que valida la entrada del usuario con la inserción de dependencias (DI).

La clase SaladChef indica la lista de ingredientes de Starship aprobada para una ensalada Ten Forward.

SaladChef.cs:

namespace BlazorSample;

public class SaladChef
{
    public string[] SaladToppers = { "Horva", "Kanda Root",
    "Krintar", "Plomeek", "Syto Bean" };
}

Registre SaladChef en el contenedor de inserción de dependencias de la aplicación en el archivo Program:

builder.Services.AddTransient<SaladChef>();

El método IsValid de la siguiente clase SaladChefValidatorAttribute obtiene el servicio SaladChef de la inserción de dependencias para comprobar la entrada del usuario.

SaladChefValidatorAttribute.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();

        if (saladChef.SaladToppers.Contains(value?.ToString()))
        {
            return ValidationResult.Success;
        }

        return new ValidationResult("Is that a Vulcan salad topper?! " +
            "The following toppers are available for a Ten Forward salad: " +
            string.Join(", ", saladChef.SaladToppers));
    }
}

El componente siguiente valida la entrada del usuario aplicando SaladChefValidatorAttribute ([SaladChefValidator]) a la cadena de ingredientes de ensaladas (SaladIngredient).

Starship12.razor:

@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">
    <DataAnnotationsValidator />
    <div>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() =>
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off">
    <DataAnnotationsValidator />
    <p>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </p>
    <button type="submit">Submit</button>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() => 
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}

Atributos de clase CSS de validación personalizados

Los atributos de clase CSS de validación personalizados son útiles al integrar con marcos de CSS, como Bootstrap.

Para especificar atributos de clase CSS de validación personalizados, empiece por proporcionar estilos CSS para la validación personalizada. En el ejemplo siguiente, se especifican estilos válidos (validField) y no válidos (invalidField).

Agregue la clase CSS siguiente a la hoja de estilos de la aplicación:

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

Cree una clase derivada de FieldCssClassProvider que compruebe los mensajes de validación de campos y aplique el estilo válido o no válido correspondiente.

CustomFieldClassProvider.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        return isValid ? "validField" : "invalidField";
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "validField" : "invalidField";
    }
}

Establezca la clase CustomFieldClassProvider como proveedor de clases CSS de campo en la instancia del formulario EditContext con SetFieldCssClassProvider.

Starship13.razor:

@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

En el ejemplo anterior se comprueba la validez de todos los campos del formulario y se aplica un estilo a cada uno. Si el formulario solo debe aplicar estilos personalizados a un subconjunto de campos, asegúrese de que CustomFieldClassProvider aplique estilos de forma condicional. En el ejemplo CustomFieldClassProvider2 siguiente solo se aplica un estilo al campo Name. Para los campos con nombres que no coinciden, se devuelve Namestring.Empty y no se aplica ningún estilo. Al usar reflexión, el campo se hace coincidir con la propiedad del nombre del modelo o el nombre del campo, no un id asignado a la entidad HTML.

CustomFieldClassProvider2.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = editContext.IsValid(fieldIdentifier);

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}

Nota

La asociación del nombre del campo en el ejemplo anterior distingue mayúsculas de minúsculas, por lo que un miembro de propiedad del modelo designado "Name" debe coincidir con una comprobación condicional en "Name":

  • Coincide correctamente:fieldId.FieldName == "Name"
  • No coincide:fieldId.FieldName == "name"
  • No coincide:fieldId.FieldName == "NAME"
  • No coincide:fieldId.FieldName == "nAmE"

Agregue una propiedad adicional a Model, por ejemplo:

[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; } 

Agregue una Description al formulario del componente CustomValidationForm:

<InputText @bind-Value="Model!.Description" />

Actualice la instancia de EditContext del método OnInitialized del componente para usar el nuevo proveedor de clases CSS de campo:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Dado que una clase de validación CSS no se aplica al campo Description, no tiene estilo. Sin embargo, la validación de campos se ejecuta normalmente. Si se proporcionan más de diez caracteres, el resumen de validación indica el error:

La descripción es demasiado larga

En el ejemplo siguiente:

  • El estilo CSS personalizado se aplica al campo Name.

  • Cualquier otro campo aplica una lógica similar a la lógica predeterminada de Blazor y con los estilos de validación CSS de campo predeterminados de Blazor, modified con valid 0 invalid. Tenga en cuenta que, para los estilos predeterminados, no es necesario agregarlos a la hoja de estilos de la aplicación si esta se basa en una plantilla de proyecto Blazor. En el caso de las aplicaciones que no se basan en una plantilla de proyecto Blazor, los estilos predeterminados se pueden agregar a la hoja de estilos de la aplicación:

    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }
    
    .invalid {
        outline: 1px solid red;
    }
    

CustomFieldClassProvider3.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}

Actualice la instancia de EditContext del método OnInitialized del componente para usar el proveedor de clases CSS del campo anterior:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Usar CustomFieldClassProvider3:

  • El campo Name usa los estilos CSS de validación personalizados de la aplicación.
  • El campo Description usa una lógica similar a la lógica de Blazor y los estilos de validación CSS del campo predeterminado de Blazor.

Paquete de validación de anotaciones de datos de Blazor

Microsoft.AspNetCore.Components.DataAnnotations.Validation es un paquete que llena los huecos de experiencia de validación mediante el componente DataAnnotationsValidator. Actualmente, el paquete está en fase experimental.

Advertencia

En NuGet.org hay una versión candidata para lanzamiento más reciente del paquete Microsoft.AspNetCore.Components.DataAnnotations.Validation. Por ahora, siga usando el paquete de versión candidata para lanzamiento experimental. Las características experimentales se proporcionan con el fin de explorar la viabilidad de las características y es posible que no se incluyan en una versión estable. Vea los anuncios del repositorio de GitHub, el dotnet/aspnetcore repositorio de GitHub, o bien la sección de este tema para obtener más actualizaciones.

Atributo [CompareProperty]

CompareAttribute no funciona bien con el componente DataAnnotationsValidator porque no asocia el resultado de la validación a un miembro específico. Esto puede generar un comportamiento incoherente entre la validación de nivel de campo y el momento en que el modelo completo se valida al enviarse. El paquete Microsoft.AspNetCore.Components.DataAnnotations.Validationexperimental incluye un atributo de validación más, ComparePropertyAttribute, que pone fin a estas limitaciones. En una aplicación Blazor, [CompareProperty] es un reemplazo directo del atributo [Compare].

Tipos de colección, tipos complejos y modelos anidados

Blazor proporciona compatibilidad para validar la entrada del formulario mediante anotaciones de datos con el elemento integrado DataAnnotationsValidator, pero DataAnnotationsValidator solo valida las propiedades de nivel superior del modelo enlazadas al formulario que no son propiedades de tipos complejos o de colección.

Para validar el gráfico de objetos completo del modelo enlazado, incluidas las propiedades de tipos complejos o de colección, use el elemento ObjectGraphDataAnnotationsValidator proporcionado por el paquete experimentalMicrosoft.AspNetCore.Components.DataAnnotations.Validation:

<EditForm ...>
    <ObjectGraphDataAnnotationsValidator />
    ...
</EditForm>

Anote las propiedades del modelo con [ValidateComplexType]. En las siguientes clases de modelo, la clase ShipDescription contiene más anotaciones de datos para validar cuando el modelo está enlazado al formulario:

Starship.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    ...

    [ValidateComplexType]
    public ShipDescription ShipDescription { get; set; } = new();

    ...
}

ShipDescription.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription
{
    [Required]
    [StringLength(40, ErrorMessage = "Description too long (40 char).")]
    public string? ShortDescription { get; set; }

    [Required]
    [StringLength(240, ErrorMessage = "Description too long (240 char).")]
    public string? LongDescription { get; set; }
}

Habilitar el botón Enviar según la validación del formulario

Para habilitar y deshabilitar el botón Enviar según la validación del formulario, en el ejemplo siguiente:

  • Usa una versión abreviada del formulario Starfleet Starship Database anterior (componente Starship3) de la sección Formulario de ejemplo del artículo Componentes de entrada que solo acepta un valor para la identificación del barco. Las demás propiedades Starship reciben valores predeterminados válidos cuando se crea una instancia del tipo Starship.
  • Se usa el elemento EditContext del formulario para asignar el modelo cuando el componente se inicialice.
  • Se valida el formulario en la devolución de llamada OnFieldChanged del contexto para habilitar y deshabilitar el botón Enviar.
  • Se implementa IDisposable y se cancela la suscripción del controlador de eventos en el método Dispose. Para más información, vea Ciclo de vida de componentes Razor de ASP.NET Core.

Nota:

Al asignar a EditForm.EditContext, no asigne también un EditForm.Model al componente EditForm.

Starship14.razor:

@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
            {
                Id = "NCC-1701",
                Classification = "Exploration",
                MaximumAccommodation = 150,
                IsValidatedDesign = true,
                ProductionDate = new DateTime(2245, 4, 11)
            };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}

Si un formulario no está cargado previamente con valores válidos y desea deshabilitar el botón Submit al cargar el formulario, establezca formInvalid en true.

Un efecto secundario del método anterior es que un resumen de validación (componente ValidationSummary) se rellena con campos no válidos después de que el usuario interactúe con algún campo. Aborde este escenario de cualquiera de las maneras siguientes:

  • No use un componente ValidationSummary en el formulario.
  • Haga que el componente ValidationSummary esté visible cuando se seleccione el botón Enviar (por ejemplo, en un método Submit).
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
    <DataAnnotationsValidator />
    <ValidationSummary style="@displaySummary" />

    ...

    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    private string displaySummary = "display:none";

    ...

    private void Submit()
    {
        displaySummary = "display:block";
    }
}