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

Примечание.

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

Внимание

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

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

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

Модель EditForm/EditContext

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

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

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

Примечание.

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

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

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

Назначайте элементу 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 основном проекте веб-приложения.

В следующем примере 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]
    public 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]
    public string? Name { get; set; }

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

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

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

Для атрибута можно указать следующие параметры привязки [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")]
    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; }
    }
}

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

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

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

ShipDetails.cs:

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

Следующая подформа используется для редактирования значений 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>

Основная форма привязана к классу 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]
    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);
    }
}

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

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

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

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

Пример в этом разделе основан на 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");
    }
}