enlace de formularios de Blazor ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

En este artículo se explica cómo usar el enlace en formularios Blazor.

EditForm/EditContext modelo

Un componente EditForm crea un objeto EditContext basado en el objeto asignado como un valor en cascada para otros componentes del formulario. El componente EditContext hace seguimiento de los metadatos del proceso de edición, los incluidos los campos del formulario 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.

Enlace de modelos

Asignación a EditForm.Model:

<EditForm ... Model="Model" ...>
    ...
</EditForm>

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

    protected override void OnInitialized() => Model ??= new();
}
<EditForm ... Model="Model" ...>
    ...
</EditForm>

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

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

Nota

La mayoría de los ejemplos del modelo de formulario de este artículo enlazan formularios a propiedades de C#, pero también se admite el enlace de campos de C#.

Enlace de contexto

Asignación a EditForm.EditContext:

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

@code {
    private EditContext? editContext;

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

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
    }
}
<EditForm ... EditContext="editContext" ...>
    ...
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

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

Asigne un EditContexto un Model a un EditForm. Si se asignan ambos, se produce un error en el momento de ejecutar.

Tipos admitidos

El enlace admite:

  • Tipos primitivos
  • Colecciones
  • Tipos complejos
  • Tipos recursivos
  • Tipos con constructores
  • Enumeraciones

También puede usar los atributos [DataMember] y [IgnoreDataMember] para personalizar el enlace de modelos. Use estos atributos para cambiar el nombre de las propiedades, omitir las propiedades y marcar las propiedades según sea necesario.

Opciones de enlace adicionales

Hay disponibles opciones de enlace de modelos adicionales desde RazorComponentsServiceOptions al llamar a AddRazorComponents:

A continuación se muestran los valores predeterminados asignados por el marco:

builder.Services.AddRazorComponents(options =>
{
    options.FormMappingUseCurrentCulture = true;
    options.MaxFormMappingCollectionSize = 1024;
    options.MaxFormMappingErrorCount = 200;
    options.MaxFormMappingKeySize = 1024 * 2;
    options.MaxFormMappingRecursionDepth = 64;
}).AddInteractiveServerComponents();

Nombres de formulario

Use el parámetro FormName para asignar un nombre de formulario. Los nombres de los formularios deben ser únicos para poder enlazar los datos del modelo. El siguiente formulario se denomina RomulanAle:

<EditForm ... FormName="RomulanAle" ...>
    ...
</EditForm>

Proporcionar un nombre de formulario:

  • Es necesario para todos los formularios enviados por componentes del lado servidor representados estáticamente.
  • No es necesario para los formularios enviados por componentes representados de forma interactiva, que incluye formularios en aplicaciones y componentes de Blazor WebAssembly con un modo de representación interactivo. Sin embargo, se recomienda proporcionar un nombre de formulario único para cada formulario para evitar errores de contabilización de formularios en tiempo de ejecución si alguna vez se quita la interactividad de un formulario.

El nombre del formulario solo se comprueba cuando el formulario se publica en un punto de conexión como una solicitud HTTP POST tradicional desde un componente del lado servidor representado estáticamente. El marco no produce una excepción en el punto de representación de un formulario, sino solo en el momento en el que llega un HTTP POST y no especifica un nombre de formulario.

De forma predeterminada, hay un ámbito de formulario sin nombre (cadena vacía) encima del componente raíz de la aplicación, lo que basta cuando no hay colisiones de nombres de formulario en la aplicación. Si es posible que existan colisiones de nombres de formularios, como cuando se incluye un formulario de una biblioteca y no se tiene control sobre el nombre del formulario utilizado por el desarrollador de la biblioteca, proporcione un alcance de nombre de formulario con el componente FormMappingScope en el proyecto principal de la aplicación web Blazor.

En el ejemplo siguiente, el componente HelloFormFromLibrary tiene un formulario denominado Hello y está en una biblioteca.

HelloFormFromLibrary.razor:

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the library's form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

El siguiente componente de NamedFormsWithScope usa el componente HelloFormFromLibrary de la biblioteca y también tiene un formulario denominado Hello. El FormMappingScope nombre del ámbito del componente es ParentContext para los formularios proporcionados por el HelloFormFromLibrary componente. Aunque ambos formularios de este ejemplo tienen el nombre del formulario (Hello), los nombres de formulario no colisionan y los eventos se enrutan al formulario correcto para los eventos POST del formulario.

NamedFormsWithScope.razor:

@page "/named-forms-with-scope"

<div>Hello form from a library</div>

<FormMappingScope Name="ParentContext">
    <HelloFormFromLibrary />
</FormMappingScope>

<div>Hello form using the same form name</div>

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the app form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

Proporcione un parámetro del formulario ([SupplyParameterFromForm])

El atributo [SupplyParameterFromForm] indica que el valor de la propiedad asociada debe proporcionarse a partir de los datos del formulario para el formulario. Los datos de la solicitud que coinciden con el nombre de la propiedad están enlazados a la propiedad . Las entradas basadas en InputBase<TValue> generan nombres de valor de formulario que coinciden con los nombres que Blazor usa para el enlace de modelos.

Puede especificar los siguientes parámetros de enlace de formulario para el atributo [SupplyParameterFromForm]:

  • Name: se obtiene o establece para el nombre del parámetro. El nombre se usa para determinar el prefijo que se va a usar para buscar coincidencias con los datos del formulario y decidir si es necesario enlazar o no el valor.
  • FormName: se obtiene o establece para el nombre del controlador. El nombre se usa para hacer coincidir el parámetro con el formulario por el nombre del formulario y poder así decidir si es necesario enlazar o no el valor.

En el ejemplo siguiente se enlazan de forma independiente dos formularios a sus modelos por nombre de formulario.

Starship6.razor:

@page "/starship-6"
@inject ILogger<Starship6> Logger

<EditForm Model="Model1" OnSubmit="Submit1" FormName="Holodeck1">
    <div>
        <label>
            Holodeck 1 Identifier: 
            <InputText @bind-Value="Model1!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<EditForm Model="Model2" OnSubmit="Submit2" FormName="Holodeck2">
    <div>
        <label>
            Holodeck 2 Identifier: 
            <InputText @bind-Value="Model2!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm(FormName = "Holodeck1")]
    public Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    public Holodeck? Model2 { get; set; }

    protected override void OnInitialized()
    {
        Model1 ??= new();
        Model2 ??= new();
    }

    private void Submit1()
    {
        Logger.LogInformation("Submit1: Id = {Id}", Model1?.Id);
    }

    private void Submit2()
    {
        Logger.LogInformation("Submit2: Id = {Id}", Model2?.Id);
    }

    public class Holodeck
    {
        public string? Id { get; set; }
    }
}

Anidar y enlazar formularios

En las instrucciones siguientes se muestra cómo anidar y enlazar formularios secundarios.

La siguiente clase de detalles de envío (ShipDetails) contiene una descripción y una longitud para un subformulario.

ShipDetails.cs:

namespace BlazorSample;

public class ShipDetails
{
    public string? Description { get; set; }
    public int? Length { get; set; }
}

Los siguientes nombres de clase de Ship asigna un nombre a un identificador (Id) e incluye los detalles del envío.

Ship.cs:

namespace BlazorSample
{
    public class Ship
    {
        public string? Id { get; set; }
        public ShipDetails Details { get; set; } = new();
    }
}

El siguiente subformulario se usa para editar valores del tipo ShipDetails. Esto se implementa heredando Editor<T> en la parte superior del componente. Editor<T> garantiza que el componente secundario genere los nombres de campo de formulario correctos basados en el modelo (T), donde T en el ejemplo siguiente es ShipDetails.

StarshipSubform.razor:

@inherits Editor<ShipDetails>

<div>
    <label>
        Description: 
        <InputText @bind-Value="Value!.Description" />
    </label>
</div>
<div>
    <label>
        Length: 
        <InputNumber @bind-Value="Value!.Length" />
    </label>
</div>

El formulario principal está enlazado a la clase Ship. El componente StarshipSubform se usa para editar los detalles del envío, enlazados como Model!.Details.

Starship7.razor:

@page "/starship-7"
@inject ILogger<Starship7> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship7">
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <StarshipSubform @bind-Value="Model!.Details" />
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

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

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

    private void Submit()
    {
        Logger.LogInformation("Id = {Id} Desc = {Description} Length = {Length}",
            Model?.Id, Model?.Details?.Description, Model?.Details?.Length);
    }
}

Escenarios avanzados de error de asignación de formularios

El marco crea una instancia de y rellena el FormMappingContext de un formulario, que es el contexto asociado a la operación de asignación de un formulario determinado. Cada ámbito de asignación (definido por un componente de FormMappingScope) crea instancias de FormMappingContext. Cada vez que un [SupplyParameterFromForm] solicita el contexto de un valor, el marco rellena el FormMappingContext con el valor intentado y los errores de asignación.

No se espera que los desarrolladores interactúen directamente con FormMappingContext, ya que es principalmente un origen de datos para InputBase<TValue>, EditContext y otras implementaciones internas para mostrar errores de asignación como errores de validación. En escenarios personalizados avanzados, los desarrolladores pueden acceder a FormMappingContext directamente como un [CascadingParameter] para escribir código personalizado que consume los valores intentados y los errores de asignación.

Botones de radio

El ejemplo de esta sección se basa en el formulario Starfleet Starship Database (componente Starship3) 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 la clase ComponentEnums sea accesible al:

  • modelo Starship en Starship.cs (por ejemplo, using static ComponentEnums;).
  • formulario Starfleet Starship Database (Starship3.razor) (por ejemplo, @using static 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 de Starship descrito en la sección Formulario ejemplo del artículo Componentes de entrada:

[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 de Starfleet Starship Database (componenteStarship3 ) del formulario de ejemplo de sección del artículo Componentes de entrada. 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.

Nota

Asegúrese de que la clase ComponentEnums esté disponible para el componente para el ejemplo siguiente:

@using static ComponentEnums
<fieldset>
    <legend>Manufacturer</legend>
    <InputRadioGroup @bind-Value="Model!.Manufacturer">
        @foreach (var manufacturer in Enum.GetValues<Manufacturer>())
        {
            <div>
                <label>
                    <InputRadio Value="manufacturer" />
                    @manufacturer
                </label>
            </div>
        }
    </InputRadioGroup>
</fieldset>

<fieldset>
    <legend>Engine and Color</legend>
    <p>
        Engine and color pairs are recommended, but any
        combination of engine and color is allowed.
    </p>
    <InputRadioGroup Name="engine" @bind-Value="Model!.Engine">
        <InputRadioGroup Name="color" @bind-Value="Model!.Color">
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Ion" />
                        Ion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.ImperialRed" />
                        Imperial Red
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Plasma" />
                        Plasma
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.SpacecruiserGreen" />
                        Spacecruiser Green
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Fusion" />
                        Fusion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.StarshipBlue" />
                        Starship Blue
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Warp" />
                        Warp
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.VoyagerOrange" />
                        Voyager Orange
                    </label>
                </div>
            </div>
        </InputRadioGroup>
    </InputRadioGroup>
</fieldset>

Nota

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

Si implementó el marcado Razor anterior en el componente Starship3 del formulario de ejemplo de sección del artículo Componentes de entrada, actualice el registro del método Submit:

Logger.LogInformation("Id = {Id} Description = {Description} " +
    "Classification = {Classification} MaximumAccommodation = " +
    "{MaximumAccommodation} IsValidatedDesign = " +
    "{IsValidatedDesign} ProductionDate = {ProductionDate} " +
    "Manufacturer = {Manufacturer}, Engine = {Engine}, " +
    "Color = {Color}",
    Model?.Id, Model?.Description, Model?.Classification,
    Model?.MaximumAccommodation, Model?.IsValidatedDesign,
    Model?.ProductionDate, Model?.Manufacturer, Model?.Engine, 
    Model?.Color);

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

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 = "The 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:

Use el siguiente modelo de ejemplo.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorServer80
{
    public class Model
    {
        [Range(1, 5)]
        public int Rating { get; set; }
    }
}

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

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++)
    {
        <div>
            <label>
                <InputRadio name="rate" SelectedValue="i" 
                    @bind-Value="Model.Rating" />
                @i
            </label>
        </div>
    }

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

<div>@Model.Rating</div>

@code {
    public StarshipModel Model { get; set; }

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

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