Привязка к данным в ASP.NET Core BlazorASP.NET Core Blazor data binding

Авторы: Люк Латэм (Luke Latham), Дэниэл Рот (Daniel Roth) и Стив Сандерсон (Steve Sanderson)By Luke Latham, Daniel Roth, and Steve Sanderson

Компоненты Razor реализуют функции привязки данных для поля, свойства или значения выражения Razor с помощью атрибута HTML-элемента @bind.Razor components provide data binding features via an HTML element attribute named @bind with a field, property, or Razor expression value.

В следующем примере выполняется привязка элемента <input> к полю currentValue, а элемента <input> — к свойству CurrentValue.The following example binds an <input> element to the currentValue field and an <input> element to the CurrentValue property:

<p>
    <input @bind="currentValue" /> Current value: @currentValue
</p>

<p>
    <input @bind="CurrentValue" /> Current value: @CurrentValue
</p>

@code {
    private string currentValue;

    private string CurrentValue { get; set; }
}

Когда один из элементов теряет фокус, привязанное поле или свойство обновляется.When one of the elements loses focus, its bound field or property is updated.

Текстовое поле обновляется в пользовательском интерфейсе только при отрисовке компонента, а не в ответ на изменение значения поля или свойства.The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Так как компоненты отрисовываются после выполнения кода обработчика событий, изменения полей и свойств обычно отражаются в пользовательском интерфейсе сразу после активации обработчика событий.Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered.

Использование @bind со свойством CurrentValue (<input @bind="CurrentValue" />) фактически эквивалентно следующему:Using @bind with the CurrentValue property (<input @bind="CurrentValue" />) is essentially equivalent to the following:

<input value="@CurrentValue"
    @onchange="@((ChangeEventArgs __e) => CurrentValue = 
        __e.Value.ToString())" />

@code {
    private string CurrentValue { get; set; }
}

Когда компонент отрисовывается, значение value элемента input берется из свойства CurrentValue.When the component is rendered, the value of the input element comes from the CurrentValue property. Когда пользователь вводит данные в текстовом поле, а затем переводит фокус, инициируется событие onchange, и свойству CurrentValue присваивается измененное значение.When the user types in the text box and changes element focus, the onchange event is fired and the CurrentValue property is set to the changed value. На практике код обычно сложнее этого, так как @bind применяется в случаях, когда выполняется преобразование типов.In reality, the code generation is more complex than that because @bind handles cases where type conversions are performed. Как правило, @bind связывает текущее значение выражения с атрибутом value и обрабатывает изменения с помощью зарегистрированного обработчика.In principle, @bind associates the current value of an expression with a value attribute and handles changes using the registered handler.

Чтобы привязать свойство или поле к другим событиям, можно использовать атрибут @bind:event с параметром event.Bind a property or field on other events by also including an @bind:event attribute with an event parameter. В следующем примере свойство CurrentValue привязывается к событию oninput:The following example binds the CurrentValue property on the oninput event:

<input @bind="CurrentValue" @bind:event="oninput" />

@code {
    private string CurrentValue { get; set; }
}

В отличие от события onchange, которое происходит, когда элемент теряет фокус, событие oninput происходит при изменении значения в текстовом поле.Unlike onchange, which fires when the element loses focus, oninput fires when the value of the text box changes.

Привязка атрибута учитывает регистр:Attribute binding is case sensitive:

  • @bind является допустимым;@bind is valid.
  • @Bind и @BIND является недопустимым.@Bind and @BIND are invalid.

Значения, не поддающиеся анализуUnparsable values

Когда пользователь присваивает не поддающееся анализу значение элементу, привязанному к данным, при активации события привязки автоматически восстанавливается предыдущее значение.When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.

Рассмотрим следующий сценарий.Consider the following scenario:

  • Элемент <input> привязан к типу int с начальным значением 123:An <input> element is bound to an int type with an initial value of 123:

    <input @bind="inputValue" />
    
    @code {
        private int inputValue = 123;
    }
    
  • Пользователь изменяет значение элемента на 123.45 на странице и переводит фокус.The user updates the value of the element to 123.45 in the page and changes the element focus.

В приведенном выше сценарии восстанавливается значение элемента 123.In the preceding scenario, the element's value is reverted to 123. Когда значение 123.45 отклоняется и вместо него используется исходное значение 123, пользователь понимает, что его значение не было принято.When the value 123.45 is rejected in favor of the original value of 123, the user understands that their value wasn't accepted.

По умолчанию привязка применяется к событию onchange элемента (@bind="{PROPERTY OR FIELD}").By default, binding applies to the element's onchange event (@bind="{PROPERTY OR FIELD}"). Чтобы выполнить привязку к другому событию, используйте @bind="{PROPERTY OR FIELD}" @bind:event={EVENT}.Use @bind="{PROPERTY OR FIELD}" @bind:event={EVENT} to trigger binding on a different event. Для события oninput (@bind:event="oninput") исходное значение восстанавливается после любого нажатия клавиши, которое приводит к вводу не поддающегося анализу значения.For the oninput event (@bind:event="oninput"), the reversion occurs after any keystroke that introduces an unparsable value. При привязке типа int к событию oninput пользователь не может ввести символ ..When targeting the oninput event with an int-bound type, a user is prevented from typing a . character. Символ . немедленно удаляется, так что пользователь сразу понимает, что допустимы только целые числа.A . character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. В некоторых ситуациях восстанавливать исходное значение при возникновении события oninput нежелательно, например, когда пользователю следует дать возможность самому удалять не поддающиеся анализу значения <input>.There are scenarios where reverting the value on the oninput event isn't ideal, such as when the user should be allowed to clear an unparsable <input> value. Ниже представлены возможные альтернативы.Alternatives include:

  • Не используйте событие oninput.Don't use the oninput event. Используйте вместо этого событие onchange по умолчанию (укажите просто @bind="{PROPERTY OR FIELD}"). В этом случае недопустимое значение не будет меняться на исходное, пока элемент не потеряет фокус.Use the default onchange event (only specify @bind="{PROPERTY OR FIELD}"), where an invalid value isn't reverted until the element loses focus.
  • Выполните привязку к типу, допускающему значение NULL, например int? или string, и предоставьте пользовательскую логику для обработки недопустимых значений.Bind to a nullable type, such as int? or string and provide custom logic to handle invalid entries.
  • Используйте компонент для проверки формы, например InputNumber<TValue> или InputDate<TValue>.Use a form validation component, such as InputNumber<TValue> or InputDate<TValue>. Компоненты для проверки форм имеют встроенную поддержку управления недопустимыми входными данными.Form validation components have built-in support to manage invalid inputs. Дополнительные сведения см. в разделе Формы и проверка ASP.NET Core Blazor.For more information, see Формы и проверка ASP.NET Core Blazor. Компоненты для проверки форм:Form validation components:
    • позволяют пользователю вводить недопустимые данные и получать ошибки проверки в соответствующем контексте EditContext;Permit the user to provide invalid input and receive validation errors on the associated EditContext.
    • выводят ошибки проверки в пользовательском интерфейсе, не мешая пользователю вводить дополнительные данные в веб-форме.Display validation errors in the UI without interfering with the user entering additional webform data.

Строки форматаFormat strings

Привязка данных работает со строками формата DateTime с использованием @bind:format.Data binding works with DateTime format strings using @bind:format. Другие выражения форматирования, например денежные или числовые форматы, в настоящее время недоступны.Other format expressions, such as currency or number formats, aren't available at this time.

<input @bind="startDate" @bind:format="yyyy-MM-dd" />

@code {
    private DateTime startDate = new DateTime(2020, 1, 1);
}

В приведенном выше коде типом поля элемента <input> (type) по умолчанию является text.In the preceding code, the <input> element's field type (type) defaults to text. @bind:format поддерживается для привязки следующих типов .NET:@bind:format is supported for binding the following .NET types:

Атрибут @bind:format указывает формат даты, применяемый к значению value элемента <input>.The @bind:format attribute specifies the date format to apply to the value of the <input> element. Формат также используется для синтаксического анализа значения при возникновении события onchange.The format is also used to parse the value when an onchange event occurs.

Указывать формат для типа поля date не рекомендуется, так как в Blazor есть встроенная поддержка форматирования дат.Specifying a format for the date field type isn't recommended because Blazor has built-in support to format dates. Если все же для типа поля date указывается формат, для правильной работы привязки используйте только формат даты yyyy-MM-dd.In spite of the recommendation, only use the yyyy-MM-dd date format for binding to function correctly if a format is supplied with the date field type:

<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">

Привязка с помощью параметров компонентовBinding with component parameters

Распространенным сценарием является привязка свойства дочернего компонента к свойству родительского компонента.A common scenario is binding a property in a child component to a property in its parent. Такой сценарий называется цепочкой привязки , так как одновременно имеется несколько уровней привязки.This scenario is called a chained bind because multiple levels of binding occur simultaneously.

Параметры компонента позволяют привязывать свойства и поля родительского компонента с помощью синтаксиса @bind-{PROPERTY OR FIELD}.Component parameters permit binding properties and fields of a parent component with @bind-{PROPERTY OR FIELD} syntax.

Цепочку привязки нельзя реализовать с помощью синтаксиса @bind в дочернем компоненте.Chained binds can't be implemented with @bind syntax in the child component. Обработчик событий и значение должны быть указаны отдельно для поддержки изменения свойства родительского компонента из дочернего.An event handler and value must be specified separately to support updating the property in the parent from the child component.

Родительский компонент по-прежнему использует синтаксис @bind для настройки привязки данных к дочернему компоненту.The parent component still leverages the @bind syntax to set up the data-binding with the child component.

Следующий компонент Child (Shared/Child.razor) имеет параметр компонента Year и обратный вызов YearChanged.The following Child component (Shared/Child.razor) has a Year component parameter and YearChanged callback:

<div class="card bg-light mt-3" style="width:18rem ">
    <div class="card-body">
        <h3 class="card-title">Child Component</h3>
        <p class="card-text">Child <code>Year</code>: @Year</p>
    </div>
</div>

<button @onclick="UpdateYearFromChild">Update Year from Child</button>

@code {
    private Random r = new Random();

    [Parameter]
    public int Year { get; set; }

    [Parameter]
    public EventCallback<int> YearChanged { get; set; }

    private async Task UpdateYearFromChild()
    {
        await YearChanged.InvokeAsync(r.Next(1950, 2021));
    }
}

Имя обратного вызова (EventCallback<TValue>) должно включать имя параметра компонента, за которым следует суффикс "Changed" ({PARAMETER NAME}Changed).The callback (EventCallback<TValue>) must be named as the component parameter name followed by the "Changed" suffix ({PARAMETER NAME}Changed). В предыдущем примере обратный вызов назывался YearChanged.In the preceding example, the callback is named YearChanged. EventCallback.InvokeAsync вызывает делегат, связанный с привязкой с указанным аргументом, и отправляет уведомление о событии для измененного свойства.EventCallback.InvokeAsync invokes the delegate associated with the binding with the provided argument and dispatches an event notification for the changed property.

В следующем компоненте Parent (Parent.razor) поле year привязано к параметру Year дочернего компонента.In the following Parent component (Parent.razor), the year field is bound to the Year parameter of the child component:

@page "/Parent"

<h1>Parent Component</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<Child @bind-Year="year" />

@code {
    private Random r = new Random();
    private int year = 1979;

    private void UpdateYear()
    {
        year = r.Next(1950, 2021);
    }
}

Параметр Year допускает привязку, так как он имеет сопутствующее событие YearChanged, соответствующее типу параметра Year.The Year parameter is bindable because it has a companion YearChanged event that matches the type of the Year parameter.

По соглашению свойство можно привязать к соответствующему обработчику событий, включив атрибут @bind-{PROPERTY}:event, назначенный обработчику.By convention, a property can be bound to a corresponding event handler by including an @bind-{PROPERTY}:event attribute assigned to the handler. Запись <Child @bind-Year="year" /> эквивалентна следующей.<Child @bind-Year="year" /> is equivalent to writing:

<Child @bind-Year="year" @bind-Year:event="YearChanged" />

В более сложных и реальных примерах следующий компонент PasswordField(PasswordField.razor):In a more sophisticated and real-world example, the following PasswordField component (PasswordField.razor):

  • присваивает элементу <input> значение поля password;Sets an <input> element's value to a password field.
  • сообщает об изменениях свойства Password родительскому компоненту с помощью вызова EventCallback, который передает текущее значение поля password дочернего компонента в качестве аргумента;Exposes changes of a Password property to a parent component with an EventCallback that passes in the current value of the child's password field as its argument.
  • использует событие onclick для активации метода ToggleShowPassword.Uses the onclick event to trigger the ToggleShowPassword method. Для получения дополнительной информации см. Обработка событий Blazor в ASP.NET Core.For more information, see Обработка событий Blazor в ASP.NET Core.
<h1>Provide your password</h1>

Password:

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

@code {
    private bool showPassword;
    private string password;

    [Parameter]
    public string Password { get; set; }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private async Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e.Value.ToString();

        await PasswordChanged.InvokeAsync(password);
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

Компонент PasswordField используется в другом компоненте:The PasswordField component is used in another component:

@page "/Parent"

<h1>Parent Component</h1>

<PasswordField @bind-Password="password" />

@code {
    private string password;
}

Выполняйте проверки или перехватывайте ошибки в методе, который вызывает делегат привязки.Perform checks or trap errors in the method that invokes the binding's delegate. В следующем примере пользователю немедленно сообщается о том, что в пароле есть пробел:The following example provides immediate feedback to the user if a space is used in the password's value:

<h1>Child Component</h1>

Password: 

<input @oninput="OnPasswordChanged" 
       required 
       type="@(showPassword ? "text" : "password")" 
       value="@password" />

<button class="btn btn-primary" @onclick="ToggleShowPassword">
    Show password
</button>

<span class="text-danger">@validationMessage</span>

@code {
    private bool showPassword;
    private string password;
    private string validationMessage;

    [Parameter]
    public string Password { get; set; }

    [Parameter]
    public EventCallback<string> PasswordChanged { get; set; }

    private Task OnPasswordChanged(ChangeEventArgs e)
    {
        password = e.Value.ToString();

        if (password.Contains(' '))
        {
            validationMessage = "Spaces not allowed!";

            return Task.CompletedTask;
        }
        else
        {
            validationMessage = string.Empty;

            return PasswordChanged.InvokeAsync(password);
        }
    }

    private void ToggleShowPassword()
    {
        showPassword = !showPassword;
    }
}

Дополнительные сведения о методе EventCallback<TValue> см. в разделе Обработка событий Blazor в ASP.NET Core.For more information on EventCallback<TValue>, see Обработка событий Blazor в ASP.NET Core.

Привязка через более чем два компонентаBind across more than two components

Привязку можно выполнять через любое число вложенных компонентов, но необходимо соблюдать односторонний поток данных:You can bind through any number of nested components, but you must respect the one-way flow of data:

  • уведомления об изменениях передаются вверх по иерархии ;Change notifications flow up the hierarchy.
  • новые значения параметров передаются вниз по иерархии.New parameter values flow down the hierarchy.

Распространенный и рекомендуемый подход заключается в хранении базовых данных только в родительском компоненте, что позволяет избежать путаницы в отношении того, какое состояние необходимо обновить.A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated.

Эти принципы демонстрируются на примере следующих компонентов:The following components demonstrate the preceding concepts:

ParentComponent.razor:ParentComponent.razor:

<h1>Parent Component</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
    <button @onclick="ChangeValue">Change from Parent</button>
</p>

<ChildComponent @bind-ChildMessage="parentMessage" />

@code {
    private string parentMessage = "Initial value set in Parent";

    private void ChangeValue()
    {
        parentMessage = $"Set in Parent {DateTime.Now}";
    }
}

ChildComponent.razor:ChildComponent.razor:

<div class="border rounded m-1 p-1">
    <h2>Child Component</h2>

    <p>Child Message: <b>@ChildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Child</button>
    </p>

    <GrandchildComponent @bind-GrandchildMessage="BoundValue" />
</div>

@code {
    [Parameter]
    public string ChildMessage { get; set; }

    [Parameter]
    public EventCallback<string> ChildMessageChanged { get; set; }

    private string BoundValue
    {
        get => ChildMessage;
        set => ChildMessageChanged.InvokeAsync(value);
    }

    private async Task ChangeValue()
    {
        await ChildMessageChanged.InvokeAsync(
            $"Set in Child {DateTime.Now}");
    }
}

GrandchildComponent.razor:GrandchildComponent.razor:

<div class="border rounded m-1 p-1">
    <h3>Grandchild Component</h3>

    <p>Grandchild Message: <b>@GrandchildMessage</b></p>

    <p>
        <button @onclick="ChangeValue">Change from Grandchild</button>
    </p>
</div>

@code {
    [Parameter]
    public string GrandchildMessage { get; set; }

    [Parameter]
    public EventCallback<string> GrandchildMessageChanged { get; set; }

    private async Task ChangeValue()
    {
        await GrandchildMessageChanged.InvokeAsync(
            $"Set in Grandchild {DateTime.Now}");
    }
}

Описание альтернативного подхода, подходящего для совместного использования данными в памяти компонентами, которые не обязательно являются вложенными, см. в разделе Управление состоянием ASP.NET Core Blazor.For an alternative approach suited to sharing data in-memory across components that aren't necessarily nested, see Управление состоянием ASP.NET Core Blazor.

Дополнительные ресурсыAdditional resources