Comparteix a través de


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, consulta la versión .NET 8 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta 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]
    private 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]
    private 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 EditContext o 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.

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, proporciona un ámbito de nombre de formulario con el componente FormMappingScope en el proyecto principal del objeto Blazor Web App.

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]
    private 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]
    private string? Name { get; set; }

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

Proporciona 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. A diferencia de las propiedades del parámetro de componente ([Parameter]), no es necesario marcar las propiedades anotadas public con [SupplyParameterFromForm].

Puedes 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")]
    private Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    private 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]
    private 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);
    }
}

Inicialización de datos de formulario con SSR estática

Cuando un componente adopta una SSR estática, el método de ciclo de vida OnInitialized{Async} y el método de ciclo de vida OnParametersSet{Async} se activan cuando el componente se representa inicialmente y en cada formulario POST en el servidor. Para inicializar los valores del modelo de formulario, confirma si el modelo ya tiene datos antes de asignar nuevos valores de modelo en OnParametersSet{Async}, como se muestra en el ejemplo siguiente.

StarshipInit.razor:

@page "/starship-init"
@inject ILogger<StarshipInit> Logger

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

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

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

    protected override void OnParametersSet()
    {
        if (Model!.Id == default)
        {
            LoadData();
        }
    }

    private void LoadData()
    {
        Model!.Id = "Set by LoadData";
    }

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

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

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.

Componentes de entrada personalizados

Para escenarios de procesamiento de entrada personalizados, las subsecciones siguientes muestran componentes de entrada personalizados:

Se recomienda derivar los componentes de entrada personalizados InputBase<TValue> a menos que los requisitos específicos le impidan hacerlo. El equipo de ASP.NET Core mantiene activamente la clase InputBase<TValue>, lo que garantiza que se mantenga actualizada con las características Blazor y los cambios más recientes del marco.

Componente de entrada basado en InputBase<T>

En el ejemplo de componente siguiente:

  • Se hereda de InputBase<TValue>. Los componentes que heredan de InputBase<TValue> deben usarse en un formulario Blazor (EditForm).
  • Toma la entrada booleana de una casilla.
  • Establece el color de fondo de su contenedor <div> en función del estado de la casilla, que se produce cuando se ejecuta el método AfterChange después del enlace (@bind:after).
  • Es necesario para invalidar el método TryParseValueFromString de la clase base, pero no procesa los datos de entrada de cadena porque una casilla no proporciona datos de cadena. Las implementaciones de ejemplo de TryParseValueFromString para otros tipos de componentes de entrada que procesan la entrada de cadena están disponibles en el origen de referencia de ASP.NET Core.

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, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

EngineeringApprovalInputDerived.razor:

@using System.Diagnostics.CodeAnalysis
@inherits InputBase<bool>

<div class="@divCssClass">
    <label>
        Engineering Approval:
        <input @bind="CurrentValue" @bind:after="AfterChange" class="@CssClass" 
            type="checkbox" />
    </label>
</div>

@code {
    private string? divCssClass;

    private void AfterChange()
    {
        divCssClass = CurrentValue ? "bg-success text-white" : null;
    }

    protected override bool TryParseValueFromString(
        string? value, out bool result, 
        [NotNullWhen(false)] out string? validationErrorMessage)
            => throw new NotSupportedException(
                "This component does not parse string inputs. " +
                $"Bind to the '{nameof(CurrentValue)}' property, " +
                $"not '{nameof(CurrentValueAsString)}'.");
}

Para usar el componente anterior en el formulario de ejemplo de nave (Starship3.razor/Starship.cs) reemplaza el bloque <div> del campo de aprobación de ingeniería por una instancia de componente EngineeringApprovalInputDerived enlazada a la propiedad IsValidatedDesign del modelo:

- <div>
-     <label>
-         Engineering Approval: 
-         <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
-     </label>
- </div>
+ <EngineeringApprovalInputDerived @bind-Value="Model!.IsValidatedDesign" />

Componente de entrada con control de desarrollador total

En el ejemplo de componente siguiente:

  • No hereda de InputBase<TValue>. El componente toma el control total del procesamiento de entrada, incluidos enlace, devoluciones de llamada y validación. El componente se puede usar dentro o fuera de un formulario Blazor (EditForm).
  • Toma la entrada booleana de una casilla.
  • Cambia el color de fondo si la casilla está activada.

El código del componente incluye:

  • La propiedad Value se usa con enlace bidireccional para obtener o establecer el valor de la entrada. ValueChanged es la devolución de llamada que actualiza el valor enlazado.

  • Cuando se usa en un formulario Blazor:

    • EditContext es un valor en cascada.
    • fieldCssClass diseña el campo en función del resultado de la validación EditContext.
    • ValueExpression es una expresión (Expression<Func<T>>) asignada por el marco que identifica el valor enlazado.
    • FieldIdentifier identifica de forma única un solo campo que se puede editar, normalmente correspondiente a una propiedad de modelo. El identificador de campo se crea con la expresión que identifica el valor enlazado (ValueExpression).
  • En el controlador de eventos OnChange:

    • El valor de la entrada de casilla se obtiene de InputFileChangeEventArgs.
    • Se establecen el color de fondo y el color de texto del elemento <div> de contenedor.
    • EventCallback.InvokeAsync invoca al delegado asociado al enlace y envía una notificación de evento a los consumidores de que el valor ha cambiado.
    • Si el componente se usa en EditForm (la propiedad EditContext no es null), se lla a a EditContext.NotifyFieldChanged para desencadenar la validación.

EngineeringApprovalInputStandalone.razor:

@using System.Globalization
@using System.Linq.Expressions

<div class="@divCssClass">
    <label>
        Engineering Approval:
        <input class="@fieldCssClass" @onchange="OnChange" type="checkbox" 
            value="@Value" />
    </label>
</div>

@code {
    private string? divCssClass;
    private FieldIdentifier fieldIdentifier;
    private string? fieldCssClass => EditContext?.FieldCssClass(fieldIdentifier);

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

    [Parameter]
    public bool? Value { get; set; }

    [Parameter]
    public EventCallback<bool> ValueChanged { get; set; }

    [Parameter]
    public Expression<Func<bool>>? ValueExpression { get; set; }

    protected override void OnInitialized()
    {
        fieldIdentifier = FieldIdentifier.Create(ValueExpression!);
    }

    private async Task OnChange(ChangeEventArgs args)
    {
        BindConverter.TryConvertToBool(args.Value, CultureInfo.CurrentCulture, 
            out var value);

        divCssClass = value ? "bg-success text-white" : null;

        await ValueChanged.InvokeAsync(value);
        EditContext?.NotifyFieldChanged(fieldIdentifier);
    }
}

Para usar el componente anterior en el formulario de ejemplo de nave (Starship3.razor/Starship.cs), reemplaza el bloque <div> del campo de aprobación de ingeniería por una instancia de componente EngineeringApprovalInputStandalone enlazada a la propiedad IsValidatedDesign del modelo:

- <div>
-     <label>
-         Engineering Approval: 
-         <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
-     </label>
- </div>
+ <EngineeringApprovalInputStandalone @bind-Value="Model!.IsValidatedDesign" />

El componente EngineeringApprovalInputStandalone también funciona fuera de un objeto EditForm:

<EngineeringApprovalInputStandalone @bind-Value="ValidDesign" />

<div>
    <b>ValidDesign:</b> @ValidDesign
</div>

@code {
    private bool ValidDesign { get; set; }
}

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.

Agrega los siguientes tipos enum a la aplicación. Crea un nuevo archivo para que los contenga o agrégalos 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 }
}

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

Usa 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;

Actualiza el formulario de Starfleet Starship Database (componenteStarship3 ) del formulario de ejemplo de sección del artículo Componentes de entrada. Agrega 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úrate 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 implementaste el marcado Razor anterior en el componente Starship3 de la sección Formulario de ejemplo del artículo Componentes de entrada, actualiza 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), consulta los siguientes artículos:

Usa 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");
    }
}