Componentes de entrada y formularios de ASP.NET CoreBlazor

Nota

Esta no es la versión más reciente de este artículo. Para cambiar a la versión más reciente, use el selector de versión de ASP.NET Core en la parte superior de la tabla de contenido.

Selector de versión

Si no es posible ver el selector en una ventana estrecha del explorador, amplíe la ventana o seleccione los puntos suspensivos verticales () >Tabla de contenido.

Selector de tabla de contenido

El marco de Blazor admite formularios y proporciona componentes de entrada integrados:

El espacio de nombres Microsoft.AspNetCore.Components.Forms proporciona:

  • Clases para administrar elementos de formulario, estado y validación.
  • Acceso a componentes integrados Input*, que se pueden usar en aplicaciones Blazor.

Un proyecto creado a partir de la plantilla de proyecto Blazor incluye el espacio de nombres de forma predeterminada en el archivo _Imports.razor de la aplicación, lo que hace que el espacio de nombres esté disponible en todos los archivos de componentes Razor (.razor) de la aplicación sin directivas @using explícitas:

@using Microsoft.AspNetCore.Components.Forms

Para mostrar cómo funciona un comportamiento EditForm, veamos el ejemplo siguiente. ExampleModel representa el modelo de datos enlazado al formulario y define una propiedad Name, que se usa para almacenar el valor del campo name del formulario proporcionado por el usuario.

ExampleModel.cs:

public class ExampleModel
{
    public string? Name { get; set; }
}

Para demostrar cómo funciona un componente EditForm con la validación de anotaciones de datos, tenga en cuenta el tipo ExampleModel siguiente. La propiedad Name se marca como obligatoria con RequiredAttribute y especifica un límite máximo de longitud de cadena StringLengthAttribute y un mensaje de error.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

Para demostrar cómo funciona un componente EditForm con la validación de anotaciones de datos, tenga en cuenta el tipo ExampleModel siguiente. La propiedad Name se marca como obligatoria con RequiredAttribute y especifica un límite máximo de longitud de cadena StringLengthAttribute y un mensaje de error.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

Para demostrar cómo funciona un componente EditForm con la validación de anotaciones de datos, tenga en cuenta el tipo ExampleModel siguiente. La propiedad Name se marca como obligatoria con RequiredAttribute y especifica un límite máximo de longitud de cadena StringLengthAttribute y un mensaje de error.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

Un formulario se define mediante el componente EditForm del marco Blazor. El componente siguiente Razor muestra elementos típicos, componentes y código Razor para representar un formulario web mediante un componente EditForm, que está enlazado al tipo ExampleModel anterior.

Pages/FormExample1.razor:

@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnSubmit="@HandleSubmit">
    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();

    private void HandleSubmit()
    {
        Logger.LogInformation("HandleSubmit called");

        // Process the form
    }
}

En el componente FormExample1 anterior:

  • El componente EditForm se representa donde aparezca el elemento <EditForm>.
  • El modelo se crea en el bloque @code del componente y se conserva en un campo privado (exampleModel). El campo se asigna al atributo de EditForm.Model (Model) del elemento <EditForm>.
  • El componente InputText (id="name") es un componente de entrada para editar valores de cadena. El atributo de directiva @bind-Value enlaza la propiedad del modelo exampleModel.Name a la propiedad Value del componente InputText.
  • El método HandleSubmit se registra como controlador para la devolución de llamada OnSubmit. Se llama al controlador cuando el usuario envía el formulario.

Para demostrar cómo funciona el componente EditForm anterior con la validación de las anotaciones de datos:

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

El componente FormExample1 anterior se modifica:

  • OnSubmit se reemplaza por OnValidSubmit, que procesa el controlador de eventos asignado si el formulario es válido cuando el usuario lo envía. El nombre del método se cambia a HandleValidSubmit, que refleja que se llama al método cuando el formulario es válido.
  • Se agrega un componente ValidationSummary para mostrar mensajes de validación cuando el formulario no es válido en el envío del formulario.
  • El validador de anotaciones de datos (componente† DataAnnotationsValidator) expone la compatibilidad con la validación mediante anotaciones de datos:
    • Si el campo de formulario <input> se deja en blanco cuando se selecciona el botón Submit, aparece un error en el resumen de validación (componente‡ ValidationSummary) ("The Name field is required.") y se llama a HandleValidSubmit.
    • Si el campo de formulario <input> contiene más de diez caracteres cuando se selecciona el botón Submit, aparece un error en el resumen de validación ("Name is too long.") y no se llama a HandleValidSubmit.
    • Si el campo de formulario <input> contiene un valor válido cuando se selecciona el botón Submit , se llama a HandleValidSubmit.

†El componente DataAnnotationsValidator se trata en la sección Componentes de validador. ‡El componente ValidationSummary se trata en la sección Resumen de validación y componentes de los mensajes de validación. Para más información sobre el enlace de propiedades, vea Enlace de datos de Blazor en ASP.NET Core.

Pages/FormExample1.razor:

@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new ExampleModel();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

En el componente FormExample1 anterior:

  • El componente EditForm se representa donde aparezca el elemento <EditForm>.
  • El modelo se crea en el bloque @code del componente y se conserva en un campo privado (exampleModel). El campo se asigna al atributo de EditForm.Model (Model) del elemento <EditForm>.
  • El componente InputText (id="name") es un componente de entrada para editar valores de cadena. El atributo de directiva @bind-Value enlaza la propiedad del modelo exampleModel.Name a la propiedad Value del componente InputText.
  • El método HandleValidSubmit se asigna a OnValidSubmit. Se llama al controlador si el formulario pasa la validación.
  • El validador de anotaciones de datos (componente† DataAnnotationsValidator) expone la compatibilidad con la validación mediante anotaciones de datos:
    • Si el campo de formulario <input> se deja en blanco cuando se selecciona el botón Submit, aparece un error en el resumen de validación (componente‡ ValidationSummary) ("The Name field is required.") y se llama a HandleValidSubmit.
    • Si el campo de formulario <input> contiene más de diez caracteres cuando se selecciona el botón Submit, aparece un error en el resumen de validación ("Name is too long.") y no se llama a HandleValidSubmit.
    • Si el campo de formulario <input> contiene un valor válido cuando se selecciona el botón Submit , se llama a HandleValidSubmit.

†El componente DataAnnotationsValidator se trata en la sección Componentes de validador. ‡El componente ValidationSummary se trata en la sección Resumen de validación y componentes de los mensajes de validación. Para más información sobre el enlace de propiedades, vea Enlace de datos de Blazor en ASP.NET Core.

Enlace de un formulario

Un componente EditForm crea un objeto EditContext basado en la instancia de modelo asignada como un valor en cascada para otros componentes del formulario. El componente EditContext hace seguimiento de los metadatos del proceso de edición, incluidos los campos que se han modificado y los mensajes de validación actuales. La asignación a EditForm.Model o a EditForm.EditContext puede enlazar un formulario a los datos.

Asignación a EditForm.Model:

<EditForm Model="@exampleModel" ...>

@code {
    private ExampleModel exampleModel = new() { ... };
}

Asignación a EditForm.EditContext:

<EditForm EditContext="@editContext" ...>

@code {
    private ExampleModel exampleModel = new() { ... };
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new(exampleModel);
    }
}
<EditForm EditContext="@editContext" ...>

@code {
    private ExampleModel exampleModel = new() { ... };
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new(exampleModel);
    }
}

Asigne EditContextun o un Model a un EditForm. No se admite la asignación de los dos y se generará un error en tiempo de ejecución:

Componente de representación de excepción no controlada: EditForm requiere un parámetro Model o un parámetro EditContext, pero no ambos.

Control del envío de formularios

El componente EditForm proporciona las siguientes devoluciones de llamada para controlar el envío de formularios:

  • Use OnValidSubmit para asignar un controlador de eventos que se ejecute cuando se envía un formulario con campos válidos.
  • Use OnInvalidSubmit para asignar un controlador de eventos que se ejecute cuando se envía un formulario con campos no válidos.
  • Use OnSubmit para asignar un controlador de eventos que se ejecute independientemente del estado de validación de los campos de formulario. El formulario se valida mediante una llamada a EditContext.Validate en el método de controlador de eventos. Si Validate devuelve true, el formulario es válido.

Componentes de entrada integrados

El marco Blazor proporciona componentes de formulario integrados para recibir y validar la entrada del usuario. Los componentes de entrada integrados de la tabla siguiente se admiten en una EditForm con una EditContext.

Los componentes de la tabla también se admiten fuera de un formulario en el marcado de componentes Razor. Las entradas se validan cuando se cambian y cuando un formulario se envía.

Componente de entrada Se representa como…
InputCheckbox <input type="checkbox">
InputDate<TValue> <input type="date">
InputFile <input type="file">
InputNumber<TValue> <input type="number">
InputRadio<TValue> <input type="radio">
InputRadioGroup<TValue> Grupo de componentes InputRadio<TValue> secundarios
InputSelect<TValue> <select>
InputText <input>
InputTextArea <textarea>

Para más información sobre el componente InputFile, vea Cargas de archivo de Blazor en ASP NET Core.

Componente de entrada Se representa como…
InputCheckbox <input type="checkbox">
InputDate<TValue> <input type="date">
InputNumber<TValue> <input type="number">
InputSelect<TValue> <select>
InputText <input>
InputTextArea <textarea>

Nota:

Los componentes InputRadio<TValue> y InputRadioGroup<TValue> están disponibles en ASP.NET Core 5.0 o posteriores. Para más información, seleccione una versión 5.0 o posterior de este artículo.

Todos los componentes de entrada, incluido EditForm, admiten atributos arbitrarios. Cualquier atributo que no coincida con un parámetro de componente se agrega al elemento HTML representado.

Los componentes de entrada proporcionan un comportamiento predeterminado para validar cuando se cambia un campo:

  • En el caso de los componentes de entrada de un formulario con EditContext, el comportamiento de validación predeterminado incluye actualizar la clase CSS de campo para reflejar el estado del campo como válido o no válido con el estilo de validación del elemento HTML subyacente.
  • Para los controles que no tienen un EditContext, la validación predeterminada refleja el estado válido o no válido, pero no proporciona estilos de validación al elemento HTML subyacente.

Algunos componentes incluyen lógica de análisis de utilidad. Así, por ejemplo, InputDate<TValue> y InputNumber<TValue> tratan los valores no analizables sin problemas registrándolos como errores de validación. Los tipos con capacidad para aceptar valores nulos también admiten la nulabilidad del campo de destino (por ejemplo, int? para un entero que admite un valor NULL).

Para más información sobre el componente InputFile, vea Cargas de archivo de Blazor en ASP NET Core.

Formulario de ejemplo

El siguiente tipo Starship, que se usa en varios de los ejemplos de este artículo, define un conjunto diverso de propiedades con anotaciones de datos:

  • Identifier se requiere porque se anota con RequiredAttribute. Identifier requiere un valor de al menos un carácter, pero no más de 16 caracteres mediante StringLengthAttribute.
  • Description es opcional porque no se anota con RequiredAttribute.
  • Classification es obligatorio.
  • El valor predeterminado de la propiedad MaximumAccommodation es cero, pero requiere un valor de uno a 100 000 por su RangeAttribute.
  • IsValidatedDesign requiere que la propiedad tenga un valor true, que coincide con un estado seleccionado cuando la propiedad está enlazada a una casilla en la interfaz de usuario (<input type="checkbox">).
  • ProductionDate es un objeto DateTime y es obligatorio.

Starship.cs:

using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string? Identifier { get; set; }

    public string? Description { get; set; }

    [Required]
    public string? Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string? Identifier { get; set; }

    public string? Description { get; set; }

    [Required]
    public string? Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string Identifier { get; set; }

    public string Description { get; set; }

    [Required]
    public string Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    [Required]
    [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
    public string Identifier { get; set; }

    public string Description { get; set; }

    [Required]
    public string Classification { get; set; }

    [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
    public int MaximumAccommodation { get; set; }

    [Required]
    [Range(typeof(bool), "true", "true", 
        ErrorMessage = "This form disallows unapproved ships.")]
    public bool IsValidatedDesign { get; set; }

    [Required]
    public DateTime ProductionDate { get; set; }
}

El siguiente formulario acepta y valida la entrada del usuario mediante lo siguiente:

Pages/FormExample2.razor:

@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>, 
        ©1966-2019 CBS Studios, Inc. and 
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>, 
        ©1966-2019 CBS Studios, Inc. and 
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>, 
        ©1966-2019 CBS Studios, Inc. and 
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>, 
        ©1966-2019 CBS Studios, Inc. and 
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = new Starship() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

El componente EditForm del ejemplo anterior crea un objeto EditContext basado en la instancia Starship asignada (Model="@starship") y controla un formulario válido. En el ejemplo siguiente (componente FormExample3) se muestra cómo asignar un componente EditContext a un formulario y validar cuándo se envía el formulario.

En el ejemplo siguiente:

  • Se usa una versión abreviada del formulario Starfleet Starship Database anterior (componente FormExample2) que solo acepta un valor para el identificador de Starship. Las demás propiedades Starship reciben valores predeterminados válidos cuando se crea una instancia del tipo Starship.
  • El método HandleSubmit se ejecuta cuando se selecciona el botón Submit .
  • El formulario se valida mediante una llamada a EditContext.Validate en el método HandleSubmit.
  • El código adicional se ejecuta según el resultado de la validación.

Nota

HandleSubmit en el componente FormExample3 se muestra como un método asincrónico porque el almacenamiento de valores de formulario a menudo usa llamadas asincrónicas (await ...). Si el formulario se usa en una aplicación de prueba, tal como se muestra, HandleSubmit simplemente se ejecuta sincrónicamente. Con fines de prueba, ignore la siguiente advertencia de compilación:

This async method lacks 'await' operators and will run synchronously (Program.cs(77,44): advertencia CS1998: este método asincrónico carece de operadores "await" y se ejecutará de manera sincrónica). ...

Pages/FormExample3.razor:

@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
    }

    private async Task HandleSubmit()
    {
        if (editContext != null && editContext.Validate())
        {
            Logger.LogInformation("HandleSubmit called: Form is valid");

            // Process the valid form
            // await ...
            await Task.CompletedTask;
        }
        else
        {
            Logger.LogInformation("HandleSubmit called: Form is INVALID");
        }
    }
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
    }

    private async Task HandleSubmit()
    {
        if (editContext != null && editContext.Validate())
        {
            Logger.LogInformation("HandleSubmit called: Form is valid");

            // Process the valid form
            // await ...
            await Task.CompletedTask;
        }
        else
        {
            Logger.LogInformation("HandleSubmit called: Form is INVALID");
        }
    }
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
    }

    private async Task HandleSubmit()
    {
        if (editContext.Validate())
        {
            Logger.LogInformation("HandleSubmit called: Form is valid");

            // Process the valid form
            // await ...
        }
        else
        {
            Logger.LogInformation("HandleSubmit called: Form is INVALID");
        }
    }
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger

<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private Starship starship = 
        new Starship()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(starship);
    }

    private async Task HandleSubmit()
    {
        if (editContext.Validate())
        {
            Logger.LogInformation("HandleSubmit called: Form is valid");

            // Process the valid form
            // await ...
        }
        else
        {
            Logger.LogInformation("HandleSubmit called: Form is INVALID");
        }
    }
}

Nota:

No se admite el cambio de EditContext después de su asignación.

Selección de varias opciones con el componente InputSelect

El enlace admite la selección de la opción multiple con el componente InputSelect<TValue>. El evento @onchange proporciona una matriz de las opciones seleccionadas mediante argumentos de evento (ChangeEventArgs). El valor se debe enlazar a un tipo de matriz y el enlace a un tipo de matriz hace que el atributo multiple sea opcional en la etiqueta InputSelect<TValue>.

En el ejemplo siguiente, el usuario debe seleccionar al menos dos clasificaciones de Starship, pero no más de tres.

Pages/BindMultipleWithInputSelect.razor:

@page "/bind-multiple-with-inputselect"
@using System.ComponentModel.DataAnnotations 
@using Microsoft.Extensions.Logging
@inject ILogger<BindMultipleWithInputSelect> Logger 

<h1>Bind Multiple <code>InputSelect</code>Example</h1>

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Select classifications (Minimum: 2, Maximum: 3):
            <InputSelect @bind-Value="starship.SelectedClassification">
                <option value="@Classification.Exploration">Exploration</option>
                <option value="@Classification.Diplomacy">Diplomacy</option>
                <option value="@Classification.Defense">Defense</option>
                <option value="@Classification.Research">Research</option>
            </InputSelect>
        </label>
    </p>

    <button type="submit">Submit</button>
</EditForm>

<p>
    Selected Classifications: 
    @string.Join(", ", starship.SelectedClassification)
</p>

@code {
    private EditContext? editContext;
    private Starship starship = new();

    protected override void OnInitialized()
    {
        editContext = new(starship);
    }

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");
    }

    private class Starship
    {
        [Required, MinLength(2), MaxLength(3)]
        public Classification[] SelectedClassification { get; set; } =
            new[] { Classification.Diplomacy };
    }

    private enum Classification { Exploration, Diplomacy, Defense, Research }
}

Para obtener información sobre cómo se controlan las cadenas vacías y los valores null en el enlace de datos, vea la sección Enlace de opciones de InputSelect a valores null de objetos de C#.

Enlace de opciones de InputSelect a valores null de objetos de C#

Para obtener información sobre cómo se controlan las cadenas vacías y los valores null en el enlace de datos, vea Enlace de datos de Blazor en ASP NET.Core.

Compatibilidad con nombres para mostrar

Varios componentes integrados admiten nombres para mostrar con el parámetro InputBase<TValue>.DisplayName.

En el formulario Starfleet Starship Database (componente FormExample2) de la sección Formulario de ejemplo, la fecha de producción de un nuevo objeto Starship no especifica un nombre para mostrar:

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" />
</label>

Si el campo contiene una fecha no válida cuando se envía el formulario, el mensaje de error no muestra un nombre descriptivo. El nombre del campo "ProductionDate" no tiene un espacio entre "Production" y "Date" cuando aparece en el resumen de validación:

El campo ProductionDate debe ser una fecha.

Establezca la propiedad DisplayName en un nombre descriptivo con un espacio entre las palabras "Production" y "Date":

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" 
               DisplayName="Production Date" />
</label>

El resumen de validación muestra el nombre descriptivo cuando el valor del campo no es válido:

El campo Fecha de producción debe ser una fecha.

Compatibilidad con plantillas de mensaje de error

InputDate<TValue> y InputNumber<TValue> admiten plantillas de mensaje de error:

En el formulario Starfleet Starship Database (componente FormExample2) de la sección Formulario de ejemplo con un nombre para mostrar descriptivo asignado, el campo Production Date genera un mensaje de error mediante la siguiente plantilla de mensaje de error predeterminada:

The {0} field must be a date.

La posición del marcador de posición {0} es donde aparece el valor de la propiedad DisplayName cuando se muestra el error al usuario.

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" 
               DisplayName="Production Date" />
</label>

El campo Fecha de producción debe ser una fecha.

Asigne una plantilla personalizada a ParsingErrorMessage para proporcionar un mensaje personalizado:

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" 
               DisplayName="Production Date" 
               ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>

El campo Fecha de producción tiene un valor de fecha incorrecto.

En el formulario Starfleet Starship Database (componente FormExample2) de la sección Formulario de ejemplo se usa una plantilla de mensaje de error predeterminada:

The {0} field must be a date.

La posición del marcador de posición {0} es donde aparece el valor de la propiedad DisplayName cuando se muestra el error al usuario.

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" />
</label>

El campo ProductionDate debe ser una fecha.

Asigne una plantilla personalizada a ParsingErrorMessage para proporcionar un mensaje personalizado:

<label>
    Production Date:
    <InputDate @bind-Value="starship.ProductionDate" 
               ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>

El campo ProductionDate tiene un valor de fecha incorrecto.

Validación básica

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 FormExample4 siguiente, el método de controlador HandleValidationRequested borra los mensajes de validación existentes llamando a ValidationMessageStore.Clear antes de validar el formulario.

Pages/FormExample4.razor:

@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger

<h2>Ship Holodecks</h2>

<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
    <label>
        Type 1:
        <InputCheckbox @bind-Value="holodeck.Type1" />
    </label>

    <label>
        Type 2:
        <InputCheckbox @bind-Value="holodeck.Type2" />
    </label>

    <button type="submit">Update</button>

    <ValidationMessage For="() => holodeck.Options" />

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private EditContext? editContext;
    private Holodeck holodeck = new();
    private ValidationMessageStore? messageStore;

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

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

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

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

        // Process the form
    }

    public class Holodeck
    {
        public bool Type1 { get; set; }
        public bool Type2 { get; set; }
        public bool Options => Type1 || Type2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger

<h2>Ship Holodecks</h2>

<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
    <label>
        Type 1:
        <InputCheckbox @bind-Value="holodeck.Type1" />
    </label>

    <label>
        Type 2:
        <InputCheckbox @bind-Value="holodeck.Type2" />
    </label>

    <button type="submit">Update</button>

    <ValidationMessage For="() => holodeck.Options" />

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private EditContext? editContext;
    private Holodeck holodeck = new();
    private ValidationMessageStore? messageStore;

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

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

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

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

        // Process the form
    }

    public class Holodeck
    {
        public bool Type1 { get; set; }
        public bool Type2 { get; set; }
        public bool Options => Type1 || Type2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger

<h2>Ship Holodecks</h2>

<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
    <label>
        Type 1:
        <InputCheckbox @bind-Value="holodeck.Type1" />
    </label>

    <label>
        Type 2:
        <InputCheckbox @bind-Value="holodeck.Type2" />
    </label>

    <button type="submit">Update</button>

    <ValidationMessage For="() => holodeck.Options" />

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private EditContext editContext;
    private Holodeck holodeck = new();
    private ValidationMessageStore messageStore;

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

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

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

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

        // Process the form
    }

    public class Holodeck
    {
        public bool Type1 { get; set; }
        public bool Type2 { get; set; }
        public bool Options => Type1 || Type2;
    }

    public void Dispose()
    {
        editContext.OnValidationRequested -= HandleValidationRequested;
    }
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger

<h2>Ship Holodecks</h2>

<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
    <label>
        Type 1:
        <InputCheckbox @bind-Value="holodeck.Type1" />
    </label>

    <label>
        Type 2:
        <InputCheckbox @bind-Value="holodeck.Type2" />
    </label>

    <button type="submit">Update</button>

    <ValidationMessage For="() => holodeck.Options" />

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private EditContext editContext;
    private Holodeck holodeck = new Holodeck();
    private ValidationMessageStore messageStore;

    protected override void OnInitialized()
    {
        editContext = new EditContext(holodeck);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new ValidationMessageStore(editContext);
    }

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

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

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

        // Process the form
    }

    public class Holodeck
    {
        public bool Type1 { get; set; }
        public bool Type2 { get; set; }
        public bool Options => Type1 || Type2;
    }

    public void Dispose()
    {
        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 usa 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.

CustomValidation.cs (si se usa en una aplicación de prueba, cambie el espacio de nombres, BlazorSample, para que coincida con el espacio de nombres de la aplicación):

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();
    }
}
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();
    }
}
using System;
using System.Collections.Generic;
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 == 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)
        {
            foreach (var err in errors)
            {
                messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
            }
    
            CurrentEditContext.NotifyValidationStateChanged();
        }
    
        public void ClearErrors()
        {
            messageStore.Clear();
            CurrentEditContext.NotifyValidationStateChanged();
        }
    }
}
using System;
using System.Collections.Generic;
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 == 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 ValidationMessageStore(CurrentEditContext);
    
            CurrentEditContext.OnValidationRequested += (s, e) => 
                messageStore.Clear();
            CurrentEditContext.OnFieldChanged += (s, e) => 
                messageStore.Clear(e.FieldIdentifier);
        }
    
        public void DisplayErrors(Dictionary<string, List<string>> errors)
        {
            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 usa una versión abreviada del formulario Starfleet Starship Database (componente FormExample2) de la sección Formulario de ejemplo que solo acepta la clasificación y descripción de Starship. 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.

Pages/FormExample5.razor:

@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

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

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

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

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

            // Process the valid form
        }
    }
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

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

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

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

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

            // Process the valid form
        }
    }
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private CustomValidation customValidation;
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        customValidation.ClearErrors();

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

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

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

            // Process the valid form
        }
    }
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" />
        </label>
    </p>

    <button type="submit">Submit</button>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        ©1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private CustomValidation customValidation;
    private Starship starship = new Starship() { ProductionDate = DateTime.UtcNow };

    private void HandleValidSubmit()
    {
        customValidation.ClearErrors();

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

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

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

            // Process the valid 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 usa 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

La validación del 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 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 (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 el mismo lado cliente de validación cuando el formulario se envía al servidor. La validación del lado servidor sin validación del lado cliente es común en las aplicaciones que requieren la validación de lógica de negocios privada de la entrada del 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 obtener una versión del controlador que funcione con Microsoft Identity 1.0 y ASP.NET Core anteriores a la versión 5.0, consulte una versión anterior de este artículo.

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

Controllers/StarshipValidation.cs:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
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 starship)
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

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

                // Process the valid form
                // async ...

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

        return BadRequest(ModelState);
    }
}
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
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 starship)
        {
            HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

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

                    // Process the valid form
                    // async ...

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

            return BadRequest(ModelState);
        }
    }
}

Si usa el controlador anterior en una aplicación Blazor WebAssembly hospedada, actualice el espacio de nombres (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": {
    "Identifier": ["The Identifier 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ío o usar una herramienta para enviar una solicitud directamente a la API del servidor, como Firefox Browser Developer o Postman.

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:

{
  "Identifier": ["The Identifier 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 Program.cs. 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.cs en la aplicación Server :

using Microsoft.AspNetCore.Mvc;

En Program.cs, 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));
            }
        };
    });

Para obtener más información, vea Controlar errores en API web de ASP.NET Core.

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 FormExample6 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.

Pages/FormExample6.razor:

@page "/form-example-6"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<FormExample6> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.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>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" disabled="@disabled" />
        </label>
    </p>

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

    <p style="@messageStyles">
        @message
    </p>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        &copy;1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private bool disabled;
    private string? message;
    private string? messageStyles = "visibility:hidden";
    private CustomValidation? customValidation;
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private async Task HandleValidSubmit(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.";
        }
    }
}
@page "/form-example-6"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<FormExample6> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="starship.Description" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="starship.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>
    </p>
    <p>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="starship.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="starship.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </p>
    <p>
        <label>
            Production Date:
            <InputDate @bind-Value="starship.ProductionDate" disabled="@disabled" />
        </label>
    </p>

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

    <p style="@messageStyles">
        @message
    </p>

    <p>
        <a href="http://www.startrek.com/">Star Trek</a>,
        &copy;1966-2019 CBS Studios, Inc. and
        <a href="https://www.paramount.com">Paramount Pictures</a>
    </p>
</EditForm>

@code {
    private bool disabled;
    private string message;
    private string messageStyles = "visibility:hidden";
    private CustomValidation customValidation;
    private Starship starship = new() { ProductionDate = DateTime.UtcNow };

    private async Task HandleValidSubmit(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>>>();

            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 usa 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 hospedadas de Blazor WebAssembly 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.

En el ejemplo siguiente se usa la clase ExampleModel.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

public class ExampleModel
{
    [Required]
    [StringLength(10, ErrorMessage = "Name is too long.")]
    public string? Name { get; set; }
}
using System.ComponentModel.DataAnnotations;

public class ExampleModel
{
    [Required]
    [StringLength(10, ErrorMessage = "Name is too long.")]
    public string? Name { get; set; }
}
using System.ComponentModel.DataAnnotations;

public class ExampleModel
{
    [Required]
    [StringLength(10, ErrorMessage = "Name is too long.")]
    public string Name { get; set; }
}
using System.ComponentModel.DataAnnotations;

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

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

Shared/CustomInputText.razor:

@inherits InputText

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

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

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />
@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 FormExample7 siguiente usa el componente CustomInputText compartido.

Pages/FormExample7.razor:

@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <CustomInputText @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

<p>
    CurrentValue: @exampleModel.Name
</p>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <CustomInputText @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

<p>
    CurrentValue: @exampleModel.Name
</p>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <CustomInputText @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

<p>
    CurrentValue: @exampleModel.Name
</p>

@code {
    private ExampleModel exampleModel = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <CustomInputText @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

<p>
    CurrentValue: @exampleModel.Name
</p>

@code {
    private ExampleModel exampleModel = new ExampleModel();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

Botones de radio

El ejemplo de esta sección se basa en el formulario Starfleet Starship Database de la sección Formulario de ejemplo de este artículo.

Agregue los siguientes tipos enum a la aplicación. Cree un nuevo archivo para que los contenga o agréguelos al archivo Starship.cs.

public class ComponentEnums
{
    public enum Manufacturer { SpaceX, NASA, ULA, VirginGalactic, Unknown }
    public enum Color { ImperialRed, SpacecruiserGreen, StarshipBlue, VoyagerOrange }
    public enum Engine { Ion, Plasma, Fusion, Warp }
}

Haga que enums sea accesible al:

  • Modelo Starship en Starship.cs (por ejemplo, using static ComponentEnums; si la clase enums se denomina ComponentEnums).
  • Formulario Starfleet Starship Database (por ejemplo, @using static ComponentEnums si la clase enums se denomina ComponentEnums).

Use componentes InputRadio<TValue> con el componente InputRadioGroup<TValue> para crear un grupo de botones de radio. En el ejemplo siguiente, las propiedades se agregan al modelo Starship descrito en la sección Formulario de ejemplo:

[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX), 
    nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color? Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine? Engine { get; set; } = null;
[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX), 
    nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine Engine { get; set; } = null;

Actualice el formulario Starfleet Starship Database (componente FormExample2) de la sección Formulario de ejemplo. Agregue los componentes para generar lo siguiente:

  • Un grupo de botones de radio para el fabricante del envío.
  • Un grupo de botones de radio anidados para el motor y el color de envío.

Nota

Los grupos de botones de radio anidados no se suelen usar en formularios porque pueden dar lugar a un diseño desorganizado de controles de formulario que pueden confundir a los usuarios. Sin embargo, hay casos en los que tienen sentido en el diseño de la interfaz de usuario, como en el ejemplo siguiente que combina recomendaciones para dos entradas de usuario, el motor de envío y el color del envío. La validación del formulario requiere un motor y un color. El diseño del formulario usa objetos anidados InputRadioGroup<TValue> para emparejar las recomendaciones de motor y color. Sin embargo, el usuario puede combinar cualquier motor con cualquier color para enviar el formulario.

<fieldset>
    <legend>Manufacturer</legend>
    <InputRadioGroup @bind-Value="starship.Manufacturer">
        @foreach (var manufacturer in (Manufacturer[])Enum
            .GetValues(typeof(Manufacturer)))
        {
            <label>
                <InputRadio Value="@manufacturer" />
                <text>&nbsp;</text>@manufacturer
            </label>
        }
    </InputRadioGroup>
<fieldset>

<p>
    Select one engine and one color. Recommendations are paired but any 
    combination of engine and color is allowed:<br>
    <InputRadioGroup Name="engine" @bind-Value="starship.Engine">
        <InputRadioGroup Name="color" @bind-Value="starship.Color">
            <InputRadio Name="engine" Value="@Engine.Ion" />
            Engine: Ion<br>
            <InputRadio Name="color" Value="@Color.ImperialRed" />
            Color: Imperial Red<br><br>
            <InputRadio Name="engine" Value="@Engine.Plasma" />
            Engine: Plasma<br>
            <InputRadio Name="color" Value="@Color.SpacecruiserGreen" />
            Color: Spacecruiser Green<br><br>
            <InputRadio Name="engine" Value="@Engine.Fusion" />
            Engine: Fusion<br>
            <InputRadio Name="color" Value="@Color.StarshipBlue" />
            Color: Starship Blue<br><br>
            <InputRadio Name="engine" Value="@Engine.Warp" />
            Engine: Warp<br>
            <InputRadio Name="color" Value="@Color.VoyagerOrange" />
            Color: Voyager Orange
        </InputRadioGroup>
    </InputRadioGroup>
</p>

Nota

Si se omite Name, los componentes de InputRadio<TValue> los agrupa su antecesor más reciente.

Al trabajar con botones de radio en un formulario, el enlace de datos se controla de manera diferente que otros elementos, ya que los botones de radio se evalúan como un grupo. El valor de cada botón de radio es fijo, pero el valor del grupo de botones de radio es el valor del botón de radio seleccionado. El ejemplo siguiente muestra cómo:

  • Controlar el enlace de datos en un grupo de botones de radio
  • Admitir la validación usando un componente InputRadio<TValue> personalizado

Shared/InputRadio.razor:

@using System.Globalization
@inherits InputBase<TValue>
@typeparam TValue

<input @attributes="AdditionalAttributes" type="radio" value="@SelectedValue" 
       checked="@(SelectedValue.Equals(Value))" @onchange="OnChange" />

@code {
    [Parameter]
    public TValue SelectedValue { get; set; }

    private void OnChange(ChangeEventArgs args)
    {
        CurrentValueAsString = args.Value.ToString();
    }

    protected override bool TryParseValueFromString(string value, 
        out TValue result, out string errorMessage)
    {
        var success = BindConverter.TryConvertTo<TValue>(
            value, CultureInfo.CurrentCulture, out var parsedValue);
        if (success)
        {
            result = parsedValue;
            errorMessage = null;

            return true;
        }
        else
        {
            result = default;
            errorMessage = $"{FieldIdentifier.FieldName} field isn't valid.";

            return false;
        }
    }
}

Para obtener más información sobre los parámetros de tipo genérico (@typeparam), consulte los siguientes artículos:

El componente RadioButtonExample siguiente usa el componente InputRadio anterior para obtener y validar una clasificación del usuario:

Pages/RadioButtonExample.razor:

@page "/radio-button-example"
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@inject ILogger<RadioButtonExample> Logger

<h1>Radio Button Example</h1>

<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    @for (int i = 1; i <= 5; i++)
    {
        <label>
            <InputRadio name="rate" SelectedValue="@i" @bind-Value="model.Rating" />
            @i
        </label>
    }

    <button type="submit">Submit</button>
</EditForm>

<p>You chose: @model.Rating</p>

@code {
    private Model model = new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }

    public class Model
    {
        [Range(1, 5)]
        public int Rating { 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="@starship" />

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="@(() => starship.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;
}

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 fruta aprobada para una ensalada.

SaladChef.cs:

public class SaladChef
{
    public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", 
        "Honeydew", "Watermelon", "Grapes" };
}

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

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;

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

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

        return new ValidationResult("You should not put that in a salad!");
    }
}
using System.ComponentModel.DataAnnotations;

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

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

        return new ValidationResult("You should not put that in a salad!");
    }
}

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

Pages/ValidationWithDI.razor:

@page "/validation-with-di"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

<EditForm Model="@this" autocomplete="off">
    <DataAnnotationsValidator />

    <p>
        Name something you can put in a salad:
        <input @bind="SaladIngredient" />
    </p>

    <button type="submit">Submit</button>

    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>

</EditForm>

@code {
    [SaladChefValidator]
    public string? SaladIngredient { get; set; }
}
@page "/validation-with-di"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms

<EditForm Model="@this" autocomplete="off">
    <DataAnnotationsValidator />

    <p>
        Name something you can put in a salad:
        <input @bind="SaladIngredient" />
    </p>

    <button type="submit">Submit</button>

    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>

</EditForm>

@code {
    [SaladChefValidator]
    public string SaladIngredient { get; set; }
}

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.

En el ejemplo siguiente se usa la clase ExampleModel.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

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).

wwwroot/css/app.css (Blazor WebAssembly) o wwwroot/css/site.css (Blazor Server):

.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.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.

Pages/FormExample8.razor:

@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();
    private EditContext? editContext;

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

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.

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.GetValidationMessages(fieldIdentifier).Any();

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

        return string.Empty;
    }
}

Agregue una propiedad adicional a ExampleModel, por ejemplo:

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

Agregue una Description al formulario del componente ExampleForm7:

<InputText id="description" @bind-Value="exampleModel.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 (id="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.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.

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

En el ejemplo siguiente se usa la clase ExampleModel.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

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).

wwwroot/css/app.css (Blazor WebAssembly) o wwwroot/css/site.css (Blazor Server):

.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.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.

Pages/FormExample8.razor:

@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();
    private EditContext? editContext;

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

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.

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.GetValidationMessages(fieldIdentifier).Any();

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

        return string.Empty;
    }
}

Agregue una propiedad adicional a ExampleModel, por ejemplo:

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

Agregue una Description al formulario del componente ExampleForm7:

<InputText id="description" @bind-Value="exampleModel.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 (id="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.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.

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

En el ejemplo siguiente se usa la clase ExampleModel.

ExampleModel.cs:

using System.ComponentModel.DataAnnotations;

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

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).

wwwroot/css/app.css (Blazor WebAssembly) o wwwroot/css/site.css (Blazor Server):

.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 System.Linq;
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.

Pages/FormExample8.razor:

@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@code {
    private ExampleModel exampleModel = new();
    private EditContext editContext;

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }
}

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.

CustomFieldClassProvider2.cs:

using System.Linq;
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;
    }
}

Agregue una propiedad adicional a ExampleModel, por ejemplo:

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

Agregue una Description al formulario del componente ExampleForm7:

<InputText id="description" @bind-Value="exampleModel.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 (id="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 System.Linq;
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 repositorio dotnet/aspnetcore 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 Model="@model" OnValidSubmit="@HandleValidSubmit">
    <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();

    ...
}
using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    ...

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

    ...
}

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; }
}
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:

  • Se usa una versión abreviada del formulario Starfleet Starship Database anterior (componente FormExample2) que solo acepta un valor para el identificador del envío. 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.

Pages/FormExample9.razor:

@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

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

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private bool formInvalid = false;
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

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

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private bool formInvalid = false;
    private EditContext? editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

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

@code {
    private Starship starship = 
        new()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private bool formInvalid = false;
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }

    public void Dispose()
    {
        editContext.OnFieldChanged -= HandleFieldChanged;
    }
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger

<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p>
        <label>
            Identifier:
            <InputText @bind-Value="starship.Identifier" />
        </label>
    </p>

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

@code {
    private Starship starship = 
        new Starship()
        {
            Identifier = "NCC-1701",
            Classification = "Exploration",
            MaximumAccommodation = 150,
            IsValidatedDesign = true,
            ProductionDate = new DateTime(2245, 4, 11)
        };
    private bool formInvalid = false;
    private EditContext editContext;

    protected override void OnInitialized()
    {
        editContext = new EditContext(starship);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

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

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");

        // Process the valid form
    }

    public void Dispose()
    {
        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 HandleValidSubmit).
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary style="@displaySummary" />

    ...

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

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

    ...

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

Solucionar problemas

InvalidOperationException: EditForm requiere un parámetro Model o un parámetro EditContext, pero no ambos.

Confirme que el componente EditForm asigna un componente ModeloEditContext. No use los dos para el mismo formulario.

Al asignar a Model, confirme que se crea una instancia del tipo de modelo, como se muestra en el ejemplo siguiente:

private ExampleModel exampleModel = new();
private ExampleModel exampleModel = new ExampleModel();

Recursos adicionales