привязка Blazor основных форм ASP.NET

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье объясняется, как использовать привязку в Blazor формах.

Модель EditForm/EditContext

EditContext Создается EditForm на основе назначенного объекта в виде каскадного значения для других компонентов в форме. Отслеживает EditContext метаданные процесса редактирования, включая измененные поля формы и текущие сообщения проверки. Назначение элементу EditForm.Model или EditForm.EditContext может привязывать форму к данным.

Привязка модели

Назначение элементу 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();
}

Примечание.

Большинство примеров модели форм этой статьи привязывают формы к свойствам C#, но привязка полей C# также поддерживается.

Привязка контекста

Назначение элементу 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);
    }
}

Назначайте элементу EditForm либо EditContext, либо Model. Если оба назначены, возникает ошибка среды выполнения.

Поддерживаемые типы

Привязка поддерживает:

  • Примитивные типы
  • Коллекции
  • Сложные типы
  • Рекурсивные типы
  • Типы с конструкторами
  • Перечисления

Вы также можете использовать [DataMember] атрибуты и [IgnoreDataMember] атрибуты для настройки привязки модели. Используйте эти атрибуты, чтобы переименовать свойства, игнорировать свойства и пометить их как обязательные.

Дополнительные параметры привязки

Дополнительные параметры привязки модели доступны при RazorComponentsServiceOptions вызове AddRazorComponents:

  • MaxFormMappingCollectionSize: максимальное количество элементов, разрешенных в коллекции форм.
  • MaxFormMappingRecursionDepth: максимальная глубина, разрешенная при рекурсивном сопоставлении данных формы.
  • MaxFormMappingErrorCount: максимальное количество ошибок, разрешенных при сопоставлении данных формы.
  • MaxFormMappingKeySize: максимальный размер буфера, используемого для чтения ключей данных формы.

Ниже показаны значения по умолчанию, назначенные платформой:

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

Имена форм

FormName Используйте параметр для назначения имени формы. Имена форм должны быть уникальными для привязки данных модели. Следующая форма называется RomulanAle:

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

Укажите имя формы:

  • Требуется для всех форм, отправленных статически отрисованными на стороне сервера компонентами.
  • Не требуется для форм, отправленных интерактивными компонентами, которые включают формы в Blazor WebAssembly приложениях и компонентах с интерактивным режимом отрисовки. Однако мы рекомендуем указать уникальное имя формы для каждой формы, чтобы предотвратить публикацию ошибок формы во время выполнения, если интерактивность когда-либо удаляется для формы.

Имя формы проверяется только при публикации формы в конечную точку в качестве традиционного HTTP-запроса POST из статического отрисованного серверного компонента. Платформа не создает исключение в точке отрисовки формы, но только в момент прибытия HTTP POST и не указывает имя формы.

Над корневым компонентом приложения находится область формы без имени (пустая строка), которая достаточно, если в приложении нет конфликтов имен форм. Если возможны столкновения имен форм, например при включении формы из библиотеки, и у вас нет элемента управления именем формы, используемого разработчиком библиотеки, укажите область имени формы с FormMappingScope компонентом в Blazor Web Appосновном проекте.

В следующем примере HelloFormFromLibrary компонент имеет форму с именем Hello и находится в библиотеке.

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

NamedFormsWithScope Следующий компонент использует компонент библиотекиHelloFormFromLibrary, а также имеет форму с именемHello. FormMappingScope Имя области компонента предназначено ParentContext для любых форм, предоставляемых компонентомHelloFormFromLibrary. Хотя обе формы в этом примере имеют имя формы (Hello), имена форм не сталкиваются и события направляются в правильную форму для событий POST формы.

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

Укажите параметр из формы ([SupplyParameterFromForm])

Атрибут [SupplyParameterFromForm] указывает, что значение связанного свойства должно быть предоставлено из данных формы для формы. Данные в запросе, который соответствует имени свойства, привязан к свойству. Входные данные на InputBase<TValue> основе создания имен значений формы, которые соответствуют именам Blazor , которые используются для привязки модели. В отличие от свойств параметра компонента ([Parameter]), свойства, аннотированные с [SupplyParameterFromForm] меткой, не требуются public.

Для атрибута можно указать следующие параметры привязки [SupplyParameterFromForm] формы:

  • Name: возвращает или задает имя параметра. Имя используется для определения префикса для сопоставления данных формы и определения необходимости привязки значения.
  • FormName: возвращает или задает имя обработчика. Имя используется для сопоставления параметра с формой по имени формы для определения необходимости привязки значения.

Следующий пример независимо привязывает две формы к их моделям по имени формы.

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

Вложенные и привязки формы

В следующем руководстве показано, как вложить и привязать дочерние формы.

Следующий класс сведений о корабле (ShipDetails) содержит описание и длину для подчиненной формы.

ShipDetails.cs:

namespace BlazorSample;

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

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

Ship Следующий класс называет идентификатор (Id) и содержит сведения о доставке.

Ship.cs:

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

Следующая подформа используется для редактирования значений ShipDetails типа. Это реализуется путем Editor<T> наследования в верхней части компонента. Editor<T>гарантирует, что дочерний компонент создает правильные имена полей формы на основе модели (T), где T в следующем примере.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>
@inherits Editor<ShipDetails>

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

Основная форма привязана к классу Ship . Компонент StarshipSubform используется для редактирования сведений о доставке, привязанных как 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);
}
@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);
}

Инициализация данных формы с помощью статического SSR

Когда компонент принимает статический SSR, OnInitialized{Async} метод жизненного цикла и OnParametersSet{Async} метод жизненного цикла срабатывает при первоначальной отрисовки компонента и каждой форме POST на сервере. Чтобы инициализировать значения модели формы, убедитесь, что модель уже имеет данные перед назначением новых значений модели в , как показано в OnParametersSet{Async}следующем примере.

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

Сценарии ошибок сопоставления расширенных форм

Платформа создает экземпляр и заполняет FormMappingContext форму, которая является контекстом, связанным с операцией сопоставления данной формы. Каждая область сопоставления (определяемая компонентом FormMappingScope ) создает FormMappingContextэкземпляры. Каждый раз при [SupplyParameterFromForm] запросе контекста значения платформа заполняет FormMappingContext попытку значения и любые ошибки сопоставления.

Разработчики не должны взаимодействовать FormMappingContext напрямую, так как это главным образом источник данных для InputBase<TValue>, EditContextа также другие внутренние реализации для отображения ошибок сопоставления в виде ошибок проверки. В расширенных пользовательских сценариях разработчики могут напрямую [CascadingParameter] обращаться FormMappingContext к пользовательскому коду, который использует попытки значений и ошибок сопоставления.

Пользовательские входные компоненты

В сценариях пользовательской обработки входных данных в следующих подразделах демонстрируются пользовательские компоненты ввода:

Рекомендуется наследовать пользовательские входные компоненты, InputBase<TValue> если только определенные требования не препятствуют этому. Класс InputBase<TValue> активно поддерживается командой ASP.NET Core, обеспечивая актуальность последних Blazor функций и изменений платформы.

Входной компонент на основе InputBase<T>

В следующем примере компонент:

  • Наследует от InputBase<TValue>. Компоненты, наследуемые от InputBase<TValue> них, должны использоваться в Blazor форме (EditForm).
  • Принимает логические входные данные из флажка.
  • Задает цвет фона контейнера <div> на основе состояния флажка, который возникает при AfterChange выполнении метода после привязки (@bind:after).
  • Требуется переопределить метод базового класса TryParseValueFromString , но не обрабатывает входные данные строки, так как флажок не предоставляет строковые данные. Примеры TryParseValueFromString реализации для других типов входных компонентов, обрабатывающих входные данные строки, доступны в источнике ссылок ASP.NET Core.

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода 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)}'.");
}

Чтобы использовать предыдущий компонент в форме примера звездочки (Starship.cs/Starship3.razor), замените <div> блок для поля утверждения инженерии экземпляром EngineeringApprovalInputDerived компонента, привязанным к свойству модели:IsValidatedDesign

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

Входной компонент с полным элементом управления разработчиком

В следующем примере компонент:

  • Не наследуется от InputBase<TValue>. Компонент принимает полный контроль над обработкой входных данных, включая привязку, обратные вызовы и проверку. Компонент можно использовать внутри или за пределами Blazor формы (EditForm).
  • Принимает логические входные данные из флажка.
  • Изменяет цвет фона, если установлен флажок.

Код в компоненте включает:

  • Свойство Value используется с двусторонней привязкой для получения или задания значения входных данных. ValueChanged — обратный вызов, обновляющий привязанное значение.

  • При использовании в Blazor форме:

    • Это EditContext каскадное значение.
    • fieldCssClass стили поля на основе результата EditContext проверки.
    • ValueExpression — это выражение (Expression<Func<T>>) , назначенное платформой, определяющей привязанное значение.
    • FieldIdentifier уникально идентифицирует одно поле, которое можно изменить, обычно соответствующее свойству модели. Идентификатор поля создается с помощью выражения, определяющего привязанное значение (ValueExpression).
  • В обработчике OnChange событий:

    • Значение входных данных флажка получено из InputFileChangeEventArgs.
    • Задается цвет фона и цвет текста элемента контейнера <div> .
    • EventCallback.InvokeAsync вызывает делегат, связанный с привязкой, и отправляет уведомление о событии потребителям, которые изменились.
    • Если компонент используется в объекте EditForm ( EditContext свойство не nullявляется), EditContext.NotifyFieldChanged вызывается для активации проверки.

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

Чтобы использовать предыдущий компонент в форме примера звездочки (Starship.cs/Starship3.razor), замените <div> блок для поля утверждения инженерии экземпляром EngineeringApprovalInputStandalone компонента, привязанным к свойству модели:IsValidatedDesign

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

Компонент EngineeringApprovalInputStandalone также работает за пределами EditForm:

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

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

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

Переключатели

Пример в этом разделе основан на Starfleet Starship Database форме (Starship3 компоненте) раздела формы примера этой статьи.

Добавьте в приложение указанные ниже типы enum. Создайте файл для их хранения или добавьте их в файл 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 }
}

Сделайте класс доступным для ComponentEnums следующих элементов:

  • Starship модель в Starship.cs (например, using static ComponentEnums;).
  • Starfleet Starship Database форма (Starship3.razorнапример, @using static ComponentEnums).

Используйте компоненты InputRadio<TValue> с компонентом InputRadioGroup<TValue>, чтобы создать группу переключателей. В следующем примере свойства добавляются в Starship модель, описанную в разделе формы "Пример" статьи "Входные компоненты".

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

Starfleet Starship Database Обновите форму (Starship3компонент) раздела "Пример формы" статьи "Входные компоненты". Добавьте компоненты для создания следующих элементов:

  • группы переключателей для выбора производителя корабля;
  • вложенной группы переключателей для выбора двигателя и цвета корабля.

Примечание.

Вложенные группы переключателей часто не используются в формах, поскольку они могут привести к неупорядоченному расположению элементов управления формы, которое может запутать пользователей. Однако бывают случаи, когда разумно использовать их в пользовательском интерфейсе, например в следующем примере, в котором объединены рекомендации для двух значений, указываемых пользователем, — двигателя и цвета корабля. По правилам проверки формы необходимо указать один двигатель и один цвет. На макете формы используются вложенные объекты InputRadioGroup<TValue> для связывания рекомендаций по двигателю и цвету. Однако пользователь может объединить любой двигатель с любым цветом для отправки формы.

Примечание.

Убедитесь, что класс доступен компоненту ComponentEnums в следующем примере:

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

Примечание.

Если опустить Name, компоненты InputRadio<TValue> будут группироваться по последнему предку.

Если вы реализовали предыдущую Razor разметку в Starship3 компоненте раздела "Пример формы" статьи "Входные компоненты", обновите ведение журнала для 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);

При работе с переключателями в форме привязка данных обрабатывается иначе, чем другие элементы, так как переключатели оцениваются как группа. Значение каждого переключателя является фиксированным, но значение группы переключателей является значением выбранного переключателя. В приведенном ниже примере показано, как выполнить следующие задачи.

  • Обработка привязки данных для группы переключателей.
  • Поддержка проверки с помощью пользовательского компонента InputRadio<TValue>.

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

Дополнительные сведения о параметрах универсального типа (@typeparam) см. в следующих статьях:

Используйте следующую модель.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

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

Следующий компонент RadioButtonExample использует предыдущий компонент InputRadio для получения и проверки оценки пользователя:

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