Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Примечание.
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущей версии см. версию .NET 10 этой статьи.
В этой статье описываются функции привязки данных для Razor компонентов и элементов DOM в Blazor приложениях.
Функции привязки
Razor компоненты предоставляют функции привязки данных с @bindRazor атрибутом директивы со значением поля, свойства или Razor выражения.
В следующем примере показана привязка:
- Значение элемента
<input>для поля C#inputValue. - значение второго элемента
<input>к свойству C#InputValue.
Когда элемент <input> теряет фокус, привязанное поле или свойство обновляется.
Bind.razor:
@page "/bind"
<PageTitle>Bind</PageTitle>
<h1>Bind Example</h1>
<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>
<p>
<label>
InputValue:
<input @bind="InputValue" />
</label>
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string? inputValue;
private string? InputValue { get; set; }
}
@page "/bind"
<PageTitle>Bind</PageTitle>
<h1>Bind Example</h1>
<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>
<p>
<label>
InputValue:
<input @bind="InputValue" />
</label>
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string? inputValue;
private string? InputValue { get; set; }
}
@page "/bind"
<p>
<input @bind="inputValue" />
</p>
<p>
<input @bind="InputValue" />
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string? inputValue;
private string? InputValue { get; set; }
}
@page "/bind"
<p>
<input @bind="inputValue" />
</p>
<p>
<input @bind="InputValue" />
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string? inputValue;
private string? InputValue { get; set; }
}
@page "/bind"
<p>
<input @bind="inputValue" />
</p>
<p>
<input @bind="InputValue" />
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string inputValue;
private string InputValue { get; set; }
}
@page "/bind"
<p>
<input @bind="inputValue" />
</p>
<p>
<input @bind="InputValue" />
</p>
<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>
@code {
private string inputValue;
private string InputValue { get; set; }
}
Текстовое поле обновляется в пользовательском интерфейсе только при отрисовке компонента, а не в ответ на изменение значения поля или свойства. Так как компоненты отрисовываются после завершения кода обработчика событий, изменения полей и свойств обычно отражаются в пользовательском интерфейсе сразу после запуска обработчика событий.
В качестве демонстрации привязки данных в HTML в следующем примере свойство InputValue привязывается к атрибутам <input> и valueonchange второго элемента change.
Второй элемент <input> в следующем примере является демонстрацией концепции и не должен использоваться в качестве способа привязки данных в компонентах Razor.
BindTheory.razor:
@page "/bind-theory"
<PageTitle>Bind Theory</PageTitle>
<h1>Bind Theory Example</h1>
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue" @onchange="@((ChangeEventArgs __e) =>
InputValue = __e?.Value?.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-theory"
<PageTitle>Bind Theory</PageTitle>
<h1>Bind Theory Example</h1>
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue" @onchange="@((ChangeEventArgs __e) =>
InputValue = __e?.Value?.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-theory"
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-theory"
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue = __e?.Value?.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-theory"
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string InputValue { get; set; }
}
@page "/bind-theory"
<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>
<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue = __e.Value.ToString())" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string InputValue { get; set; }
}
Когда компонент BindTheory отрисовывается, значение value элемента <input> демонстрации HTML берется из свойства InputValue. Когда пользователь вводит данные в текстовом поле, а затем переводит фокус, инициируется событие onchange, и свойству InputValue присваивается измененное значение. На практике выполнение кода сложнее, так как @bind применяется в случаях, когда выполняется преобразование типов. Как правило, @bind связывает текущее значение выражения с атрибутом value и обрабатывает изменения с помощью зарегистрированного обработчика.
Привязка по умолчанию с @bind использованием change события DOM (onchange). Привязка также может происходить на input событии DOM с использованием атрибута @bind:event="{EVENT}", где заполнитель {EVENT} может быть либо oninput, либо onchange (по умолчанию).
При срабатывании события oninput в следующем примере свойство InputValue привязывается к значению элемента <input>.
onchange В отличие от события, которое запускается, когда элемент теряет фокус, oninput срабатывает при изменении значения текстового поля.
Page/BindEvent.razor:
@page "/bind-event"
<PageTitle>Bind Event</PageTitle>
<h1>Bind Event Example</h1>
<p>
<label>
InputValue:
<input @bind="InputValue" @bind:event="oninput" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-event"
<PageTitle>Bind Event</PageTitle>
<h1>Bind Event Example</h1>
<p>
<label>
InputValue:
<input @bind="InputValue" @bind:event="oninput" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-event"
<p>
<input @bind="InputValue" @bind:event="oninput" />
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-event"
<p>
<input @bind="InputValue" @bind:event="oninput" />
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
@page "/bind-event"
<p>
<input @bind="InputValue" @bind:event="oninput" />
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string InputValue { get; set; }
}
@page "/bind-event"
<p>
<input @bind="InputValue" @bind:event="oninput" />
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string InputValue { get; set; }
}
Чтобы привязаться к другим событиям DOM, используйте один из следующих подходов:
Для привязки к другим событиям DOM используйте делегаты обработки событий.
Razor Привязка атрибута чувствительна к регистру:
-
@bindи@bind:eventявляются допустимыми. -
@Bind/@Bind:Event(прописные буквыBиE) или@BIND/@BIND:EVENT(все прописные буквы) недопустимы.
Чтобы выполнить асинхронную логику после привязки, используйте @bind:after="{DELEGATE}", где плейсхолдер {DELEGATE} является делегатом C# (методом). Назначенный делегат C# не выполняется, пока привязанное значение не назначается синхронно.
Использование параметра обратного вызова события (EventCallback/EventCallback<T>) с @bind:after не поддерживается. Вместо этого передайте метод, возвращающий Action или Task в @bind:after.
В следующем примере :
- Каждый
<input>элементvalueпривязан кsearchTextполю синхронно. - Метод
PerformSearchвыполняется асинхронно:- Когда первое поле теряет фокус (
onchangeсобытие) после изменения значения. - После каждого нажатия клавиш (
oninputсобытие) во втором поле.
- Когда первое поле теряет фокус (
-
PerformSearchвызывает службу с асинхронным методом (FetchAsync) для возврата результатов поиска.
@inject ISearchService SearchService
<input @bind="searchText" @bind:after="PerformSearch" />
<input @bind="searchText" @bind:event="oninput" @bind:after="PerformSearch" />
@code {
private string? searchText;
private string[]? searchResult;
private async Task PerformSearch() =>
searchResult = await SearchService.FetchAsync(searchText);
}
Дополнительные примеры
BindAfter.razor:
@page "/bind-after"
@using Microsoft.AspNetCore.Components.Forms
<h1>Bind After Examples</h1>
<h2>Elements</h2>
<input type="text" @bind="text" @bind:after="() => { }" />
<input type="text" @bind="text" @bind:after="After" />
<input type="text" @bind="text" @bind:after="AfterAsync" />
<h2>Components</h2>
<InputText @bind-Value="text" @bind-Value:after="() => { }" />
<InputText @bind-Value="text" @bind-Value:after="After" />
<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />
@code {
private string text = "";
private void After() {}
private Task AfterAsync() { return Task.CompletedTask; }
}
Дополнительные сведения о компоненте InputText см. в разделе ASP.NET Основные Blazor входные компоненты.
Компоненты поддерживают двустороннюю привязку данных, определяя пару атрибутов @bind с модификатором :get или :set. Заполнитель {PARAMETER} в следующих примерах используется для привязки параметра компонента:
-
@bind:get/@bind-{PARAMETER}:get: указывает значение для привязки. -
@bind:set/@bind-{PARAMETER}:set. Указывает обратный вызов при изменении значения.
:get
/
:set Модификаторы всегда используются вместе.
Для привязки к HTML-элементам используйте :get/:set модификаторы, чтобы обеспечить <input> синхронизацию элемента с привязанным значением, даже если значение изменяется в обработчике:
<input @bind:get="inputValue" @bind:set="HandleValueChange" />
Примечание.
Подход, действовавший до выпуска .NET 7, использовал атрибут value с @onchange.
<input value="@inputValue" @onchange="HandleValueChange" />
Предыдущий подход может привести к ошибкам синхронизации, когда <input> элемент отображает значение, отличное от значения, удерживаемого в привязанной переменной. Выполните миграцию на модификаторы :get/:set, чтобы избежать этой проблемы.
Для параметров компонента следующие синтаксисы для гипотетического Child компонента функционально эквивалентны:
<Child Value="@inputValue" ValueChanged="@(v => inputValue = v)" />
<Child @bind-Value:get="inputValue" @bind-Value:set="@(v => inputValue = v)" />
С :get/:set помощью привязки модификатора можно реагировать на изменение значения перед применением к DOM и при необходимости изменить примененное значение. Что касается привязки атрибута @bind:event="{EVENT}", где заполнитель {EVENT} представляет собой событие DOM, уведомление вы получите после обновления DOM и не сможете изменять примененное значение при привязке.
BindGetSet Следующий компонент демонстрирует :get/:set синтаксис для элементов <input> и компонента InputText, используемых формами Blazor в синхронных (Set) и асинхронных (SetAsync) сценариях.
BindGetSet.razor:
@page "/bind-get-set"
@using Microsoft.AspNetCore.Components.Forms
<h1>Bind Get Set Examples</h1>
<h2>Elements</h2>
<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }" />
<input type="text" @bind:get="text" @bind:set="Set" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />
<h2>Components</h2>
<InputText @bind-Value:get="text" @bind-Value:set="(value) => { text = value; }" />
<InputText @bind-Value:get="text" @bind-Value:set="Set" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />
@code {
private string text = string.Empty;
private void Set(string value)
{
text = value;
}
private Task SetAsync(string value)
{
text = value;
return Task.CompletedTask;
}
}
Дополнительные сведения о компоненте InputText см. в разделе ASP.NET Основные Blazor входные компоненты.
Другой пример использования модификаторов :get и :set приведен в разделе Связывание более чем двух компонентов, который находится далее в этой статье.
Razor Привязка атрибута чувствительна к регистру:
-
@bind,@bind:eventи@bind:afterдопустимы. -
@Bind/@bind:Event/@bind:aftEr(прописными буквами) или@BIND/@BIND:EVENT/@BIND:AFTER(строчными буквами полностью) недопустимы.
Используйте модификаторы @bind:get/@bind:set и избегайте обработчиков событий для двусторонней привязки данных
Двусторонняя привязка данных невозможно реализовать с помощью обработчика событий. Используйте :get/:set модификаторы для двусторонней привязки данных.
Рассмотрим следующий неблагополуальный подход для двусторонней привязки данных с помощью обработчика событий:
<p>
<input value="@inputValue" @oninput="OnInput" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private string? inputValue;
private void OnInput(ChangeEventArgs args)
{
var newValue = args.Value?.ToString() ?? string.Empty;
inputValue = newValue.Length > 4 ? "Long!" : newValue;
}
}
Обработчик OnInput событий обновляет значение inputValue на Long! после предоставления четвертого символа. Однако пользователь может продолжать добавлять символы в значение элемента в пользовательском интерфейсе. Значение не привязано к значению inputValue элемента с каждым нажатием клавиш. Приведенный выше пример поддерживает только одностороннюю привязку данных.
Причина этого поведения заключается в том, что Blazor не известно о том, что ваш код намерен изменить значение inputValue в обработчике событий.
Blazor не пытается принудительно сопоставить значения элементов DOM и переменные .NET, если они не привязаны к синтаксису @bind . В более ранних версиях Blazorдвусторонняя привязка данных реализуется путем привязки элемента к свойству и управления значением свойства с помощью метода задания. В ASP.NET Core в .NET 7 или более поздней версии :get/:set синтаксис модификатора используется для реализации двусторонней привязки данных, как показано в следующем примере.
Рассмотрите следующее правильный подход с использованием :get/:set для двусторонней привязки данных:
<p>
<input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private string? inputValue;
private void OnInput(string value)
{
var newValue = value ?? string.Empty;
inputValue = newValue.Length > 4 ? "Long!" : newValue;
}
}
Используя :get/:set модификаторы, оба управляют базовым значением inputValue через :set и привязывают значение к значению inputValue элемента через :get. В предыдущем примере демонстрируется правильный подход к реализации двусторонней привязки данных.
Привязка к свойству с помощью C# get и set аксессоров
Аксессоры C# get и set можно использовать для создания пользовательского поведения форматирования привязки, как показано в следующем компоненте DecimalBinding. Компонент выполняет привязку положительного или отрицательного десятичного числа с максимум тремя десятичными разрядами к элементу <input> с помощью свойства string (DecimalValue).
DecimalBinding.razor:
@page "/decimal-binding"
@using System.Globalization
<PageTitle>Decimal Binding</PageTitle>
<h1>Decimal Binding Example</h1>
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
@page "/decimal-binding"
@using System.Globalization
<PageTitle>Decimal Binding</PageTitle>
<h1>Decimal Binding Example</h1>
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
@page "/decimal-binding"
@using System.Globalization
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
@page "/decimal-binding"
@using System.Globalization
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
@page "/decimal-binding"
@using System.Globalization
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
@page "/decimal-binding"
@using System.Globalization
<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>
<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
private string DecimalValue
{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}
Примечание.
В нескольких компонентах двусторонняя привязка к свойству с аксессорами get/set требует отбрасывания Task, возвращаемого EventCallback.InvokeAsync в сеттере свойства. Для двусторонней привязки данных рекомендуется использовать @bind:get/@bind:set модификаторы. Дополнительные сведения см. в руководстве @bind:get/@bind:set, ранее в этой статье.
Чтобы просмотреть пример того, как Task, возвращенный EventCallback.InvokeAsync, игнорируется в .NET 6 или более ранних версиях, до того как модификаторы @bind:get/@bind:set стали функцией платформы, см. раздел Привязка через более чем два компонента в версии .NET 6 этой статьи с компонентом NestedChild.
Примечание.
Двусторонняя привязка к свойству с get/set аксессорами требует отмены значения, возвращённого TaskEventCallback.InvokeAsync. Пример см. в статье компонент NestedChild привязки в более чем двух компонентах раздела. Для двусторонней привязки данных в .NET 7 или более поздней версии рекомендуется использовать @bind:get/@bind:set модификаторы, описанные в .NET 7 или более поздних версиях этой статьи.
Выбор нескольких вариантов с элементами <select>
Привязка поддерживает выбор опции multiple посредством элементов <select>. Событие @onchange предоставляет массив выбранных элементов через аргументы события (ChangeEventArgs). Значение должно быть привязано к типу массива.
BindMultipleInput.razor:
@page "/bind-multiple-input"
<h1>Bind Multiple <code>input</code>Example</h1>
<p>
<label>
Select one or more cars:
<select @onchange="SelectedCarsChanged" multiple>
<option value="audi">Audi</option>
<option value="jeep">Jeep</option>
<option value="opel">Opel</option>
<option value="saab">Saab</option>
<option value="volvo">Volvo</option>
</select>
</label>
</p>
<p>
Selected Cars: @string.Join(", ", SelectedCars)
</p>
<p>
<label>
Select one or more cities:
<select @bind="SelectedCities" multiple>
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>
<span>
Selected Cities: @string.Join(", ", SelectedCities)
</span>
@code {
public string[] SelectedCars { get; set; } = [];
public string[] SelectedCities { get; set; } = [ "bal", "sea" ];
private void SelectedCarsChanged(ChangeEventArgs e)
{
if (e.Value is not null)
{
SelectedCars = (string[])e.Value;
}
}
}
@page "/bind-multiple-input"
<h1>Bind Multiple <code>input</code>Example</h1>
<p>
<label>
Select one or more cars:
<select @onchange="SelectedCarsChanged" multiple>
<option value="audi">Audi</option>
<option value="jeep">Jeep</option>
<option value="opel">Opel</option>
<option value="saab">Saab</option>
<option value="volvo">Volvo</option>
</select>
</label>
</p>
<p>
Selected Cars: @string.Join(", ", SelectedCars)
</p>
<p>
<label>
Select one or more cities:
<select @bind="SelectedCities" multiple>
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>
<span>
Selected Cities: @string.Join(", ", SelectedCities)
</span>
@code {
public string[] SelectedCars { get; set; } = new string[] { };
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };
private void SelectedCarsChanged(ChangeEventArgs e)
{
if (e.Value is not null)
{
SelectedCars = (string[])e.Value;
}
}
}
Сведения о том, как пустые строки и значения null обрабатываются в привязке данных, см. в разделе Параметры элемента <select> привязки для значений null объектов C#.
Привязка параметров элемента <select> к значениям null объекта C#
Не существует целесообразного способа представить значение параметра элемента <select> в виде значения null объекта C# по следующим причинам:
- Атрибуты HTML не могут иметь значение
null. Ближайшим эквивалентомnullв HTML является отсутствие атрибута HTMLvalueв элементе<option>. - При выборе
<option>без атрибутаvalueбраузер обрабатывает такое значение как текстовое содержимое элемента<option>.
Платформа Blazor не пытается блокировать поведение по умолчанию, так как для этого потребуется:
- создать цепочку исключительных обходных решений в фреймворке.
- кардинально изменить текущее поведение платформы.
Приемлемым эквивалентом null в HTML является пустая строка (value). Платформа Blazor преобразует null в пустую строку для двусторонней привязки к значению <select>.
Значения, не поддающиеся анализу
Когда пользователь предоставляет неуправляемое значение элементу, связанному с данными, неуправляемое значение автоматически возвращается к предыдущему значению при активации события привязки.
Рассмотрим следующий компонент, в котором элемент <input> привязан к типу intс начальным значением 123.
UnparsableValues.razor:
@page "/unparsable-values"
<PageTitle>Unparsable Values</PageTitle>
<h1>Unparsable Values Example</h1>
<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
@page "/unparsable-values"
<PageTitle>Unparsable Values</PageTitle>
<h1>Unparsable Values Example</h1>
<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
@page "/unparseable-values"
<p>
<input @bind="inputValue" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
@page "/unparseable-values"
<p>
<input @bind="inputValue" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
@page "/unparseable-values"
<p>
<input @bind="inputValue" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
@page "/unparseable-values"
<p>
<input @bind="inputValue" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
Привязка применяется к событию элемента onchange. Если пользователь обновит значение текстового поля на 123.45 и изменит фокус, значение элемента вернется к 123, когда сработает onchange. Когда значение 123.45 отклоняется и вместо него используется исходное значение 123, пользователь понимает, что его значение не было принято.
Для события oninput (@bind:event="oninput") исходное значение восстанавливается после любого нажатия клавиши, которое приводит к вводу не поддающегося анализу значения. При нацеливании события oninput на тип, связанный с int, пользователь не может ввести символ точки (.). Символ точки (.) немедленно удаляется, так что пользователь сразу понимает, что допустимы только целые числа. В некоторых ситуациях восстанавливать исходное значение при возникновении события oninput нежелательно, например, когда пользователю следует дать возможность самому удалять не поддающиеся анализу значения <input>. Ниже представлены возможные альтернативы.
- Не используйте событие
oninput. Используйте вместо этого событиеonchangeпо умолчанию. В этом случае недопустимое значение не будет меняться на исходное, пока элемент не потеряет фокус. - Привязывайтесь к типу, допускающему значение NULL, например к
int?илиstring, и используйте@bind:get/@bind:setмодификаторы (описанные ранее в этой статье) или привязывайтесь к свойству с пользовательской логикой доступаgetиsetдля обработки недопустимых записей. - Используйте компонент ввода, такой как InputNumber<TValue> или InputDate<TValue>, с проверкой формы. Входные компоненты вместе с компонентами проверки формы обеспечивают встроенную поддержку для управления недопустимыми входными данными:
- Позволяет пользователю вводить недопустимые данные и получать ошибки проверки в соответствующем элементе EditContext.
- выводят ошибки проверки в пользовательском интерфейсе, не мешая пользователю вводить дополнительные данные в веб-форме.
Строки формата
Привязка данных работает с одной DateTime строкой формата с помощью @bind:format="{FORMAT STRING}", где заполнитель {FORMAT STRING} является строкой формата. Другие выражения форматирования, например денежные или числовые форматы, в настоящее время недоступны, но могут быть добавлены в будущем выпуске.
DateBinding.razor:
@page "/date-binding"
<PageTitle>Date Binding</PageTitle>
<h1>Date Binding Example</h1>
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"
<PageTitle>Date Binding</PageTitle>
<h1>Date Binding Example</h1>
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new(2020, 1, 1);
}
@page "/date-binding"
<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>
<p>
<code>startDate</code>: @startDate
</p>
@code {
private DateTime startDate = new DateTime(2020, 1, 1);
}
В приведенном выше коде типом поля элемента <input> (атрибутом type) по умолчанию является text.
Поддерживаются System.DateTime и System.DateTimeOffset, допускающие значение NULL.
private DateTime? date;
private DateTimeOffset? dateOffset;
Указывать формат для типа поля date не рекомендуется, так как в Blazor есть встроенная поддержка форматирования дат. Учитывая рекомендацию, используйте формат даты yyyy-MM-dd для корректной работы привязки только в том случае, если формат задан типом поля date.
<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">
Привязка с помощью параметров компонентов
Распространенным сценарием является привязка свойства дочернего компонента к свойству родительского компонента. Такой сценарий называется цепочкой привязки, так как одновременно имеется несколько уровней привязки.
Невозможно реализовать цепочные привязки с синтаксисом @bind в дочернем компоненте. Обработчик событий и значение должны быть указаны отдельно для поддержки изменения свойства родительского компонента из дочернего. Родительский компонент по-прежнему использует @bind синтаксис для настройки привязки данных с дочерним компонентом.
Следующий компонент ChildBind имеет параметр компонента Year и EventCallback<TValue>. По соглашению EventCallback<TValue> для параметра должен именоваться как имя параметра компонента с суффиксом "Changed". Синтаксис именования — {PARAMETER NAME}Changed, где {PARAMETER NAME} заполнитель — это имя параметра. В следующем примере EventCallback<TValue> называется YearChanged.
EventCallback.InvokeAsync вызывает делегат, связанный с привязкой с указанным аргументом, и отправляет уведомление о событии для измененного свойства.
ChildBind.razor:
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild() =>
await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild() =>
await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild()
{
await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
}
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild()
{
await YearChanged.InvokeAsync(Random.Shared.Next(1950, 2021));
}
}
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@code {
private Random r = new();
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
private async Task UpdateYearFromChild()
{
await YearChanged.InvokeAsync(r.Next(1950, 2021));
}
}
<div class="card bg-light mt-3" style="width:18rem ">
<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from Child</button>
</div>
</div>
@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> см. в разделе EventCallback статьи Обработка событий Blazor в ASP.NET Core.
В следующем компоненте Parent1 поле year привязано к параметру Year дочернего компонента. Параметр Year допускает привязку, так как он имеет сопутствующее событие YearChanged, соответствующее типу параметра Year.
Parent1.razor:
@page "/parent-1"
<PageTitle>Parent 1</PageTitle>
<h1>Parent Example 1</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private int year = 1979;
private void UpdateYear() => year = Random.Shared.Next(1950, 2021);
}
@page "/parent-1"
<PageTitle>Parent 1</PageTitle>
<h1>Parent Example 1</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private int year = 1979;
private void UpdateYear() => year = Random.Shared.Next(1950, 2021);
}
@page "/parent-1"
<h1>Parent Component</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private int year = 1979;
private void UpdateYear()
{
year = Random.Shared.Next(1950, 2021);
}
}
@page "/parent-1"
<h1>Parent Component</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private int year = 1979;
private void UpdateYear()
{
year = Random.Shared.Next(1950, 2021);
}
}
@page "/parent-1"
<h1>Parent Component</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private Random r = new();
private int year = 1979;
private void UpdateYear()
{
year = r.Next(1950, 2021);
}
}
@page "/parent-1"
<h1>Parent Component</h1>
<p>Parent <code>year</code>: @year</p>
<button @onclick="UpdateYear">Update Parent <code>year</code></button>
<ChildBind @bind-Year="year" />
@code {
private Random r = new Random();
private int year = 1979;
private void UpdateYear()
{
year = r.Next(1950, 2021);
}
}
Привязка параметров компонента также может активировать события @bind:after. В следующем примере YearUpdated метод выполняется асинхронно после привязки Year параметра компонента.
<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" />
@code {
...
private async Task YearUpdated()
{
... = await ...;
}
}
По соглашению свойство можно привязать к соответствующему обработчику событий, включив атрибут @bind-{PROPERTY}:event, назначенный обработчику, где заполнитель {PROPERTY} — это свойство. Запись <ChildBind @bind-Year="year" /> эквивалентна следующей.
<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
В более сложных и реальных примерах следующий компонент PasswordEntry:
- присваивает элементу
<input>значение поляpassword; - сообщает об изменениях свойства
Passwordродительскому компоненту с помощью вызоваEventCallback, который передает текущее значение поляpasswordдочернего компонента в качестве аргумента; - использует событие
onclickдля активации методаToggleShowPassword. Дополнительные сведения см. в статье Обработка событий Blazor в ASP.NET Core.
Предупреждение
Не сохраняйте секреты приложений, строка подключения, учетные данные, пароли, персональные идентификационные номера (ПИН-коды), частный код C#/.NET или закрытые ключи и токены в клиентском коде, который всегда небезопасн. В тестовых, промежуточных и производственных средах код на стороне Blazor сервера и веб-API должен использовать безопасные процедуры аутентификации, которые исключают хранение учетных данных в коде проекта или конфигурационных файлах. Вне локального тестирования разработки рекомендуется избегать использования переменных среды для хранения конфиденциальных данных, так как переменные среды не являются наиболее безопасным подходом. Для локального тестирования разработки средство Secret Manager рекомендуется для защиты конфиденциальных данных. Дополнительные сведения см. в разделе "Безопасное обслуживание конфиденциальных данных и учетных данных".
PasswordEntry.razor:
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
</p>
</div>
</div>
@code {
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);
}
}
Компонент PasswordEntry используется в другом компоненте, например в следующем примере компонента PasswordBinding.
PasswordBinding.razor:
@page "/password-binding"
<PageTitle>Password Binding</PageTitle>
<h1>Password Binding Example</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
@page "/password-binding"
<PageTitle>Password Binding</PageTitle>
<h1>Password Binding Example</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
@page "/password-binding"
<h1>Password Binding</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
@page "/password-binding"
<h1>Password Binding</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
@page "/password-binding"
<h1>Password Binding</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
@page "/password-binding"
<h1>Password Binding</h1>
<PasswordEntry @bind-Password="password" />
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
Когда компонент PasswordBinding изначально отрисовывается, в пользовательском интерфейсе для password отображается значение Not set. После первоначальной отрисовки значение password отражает изменения, внесенные в значение параметра компонента Password в компоненте PasswordEntry.
Примечание.
В предыдущем примере представлена односторонняя привязка пароля от дочернего компонента PasswordEntry к родительскому компоненту PasswordBinding. Двусторонняя привязка не обязательна в этом сценарии, если целью является наличие в приложении общего компонента ввода пароля для многократного использования, который просто передает пароль родительскому элементу. Сведения о подходе, который разрешает двустороннюю привязку без записи непосредственно в параметр дочернего компонента, см. в примере компонента NestedChild в разделе Привязка через более чем два компонента этой статьи.
Выполните проверки или перехватите ошибки в обработчике. Следующий измененный компонент PasswordEntry немедленно сообщает пользователю о том, что в пароле есть пробел.
PasswordEntry.razor:
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
}
В следующем примере PasswordUpdated метод выполняется асинхронно после привязки Password параметра компонента:
<PasswordEntry @bind-Password="password" @bind-Password:after="PasswordUpdated" />
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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);
}
}
}
<div class="card bg-light mt-3" style="width:22rem ">
<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged" required type="password"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
</div>
</div>
@code {
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);
}
}
}
Связывание между более чем двумя компонентами
Привязку параметров можно выполнять через любое число вложенных компонентов, но необходимо соблюдать односторонний поток данных:
- уведомления об изменениях передаются вверх по иерархии;
- новые значения параметров передаются вниз по иерархии.
Распространенный и рекомендуемый подход заключается в хранении базовых данных только в родительском компоненте, что позволяет избежать путаницы в отношении того, какое состояние необходимо обновить, как показано в следующем примере.
Parent2.razor:
@page "/parent-2"
<PageTitle>Parent 2</PageTitle>
<h1>Parent Example 2</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue() => parentMessage = $"Set in Parent {DateTime.Now}";
}
@page "/parent-2"
<PageTitle>Parent 2</PageTitle>
<h1>Parent Example 2</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue() => parentMessage = $"Set in Parent {DateTime.Now}";
}
@page "/parent-2"
<h1>Parent Component</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue()
{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}
@page "/parent-2"
<h1>Parent Component</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue()
{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}
@page "/parent-2"
<h1>Parent Component</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue()
{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}
@page "/parent-2"
<h1>Parent Component</h1>
<p>Parent Message: <b>@parentMessage</b></p>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
<NestedChild @bind-ChildMessage="parentMessage" />
@code {
private string parentMessage = "Initial value set in Parent";
private void ChangeValue()
{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}
В следующем компоненте NestedChild находится компонент NestedGrandchild:
- присваивает значение
ChildMessageдляGrandchildMessageс использованием синтаксиса@bind:get; - обновляет
GrandchildMessage, когдаChildMessageChangedвыполняется с использованием синтаксиса@bind:set.
До выпуска .NET 7 двусторонняя привязка компонентов использует get/set аксессоры с третьим свойством, которое отбрасывает Task, возвращенный EventCallback.InvokeAsync в своем установщике. Чтобы просмотреть пример этого подхода для .NET 6 или более ранних версий, прежде чем @bind:get/@bind:set модификаторы стали функцией платформы, см. раздел NestedChild в версии .NET 6 этой статьи.
Причина в том, чтобы избежать непосредственного изменения значения параметра компонента, заключается в том, что он эффективно мутирует состояние родительского элемента из дочернего компонента. Это может повлиять на Blazorпроцесс обнаружения изменений и активировать дополнительные циклы отрисовки, так как параметры предназначены для входных данных, они не предназначены для изменения состояния. В цепочках сценариев, когда данные передаются между компонентами, прямая запись в параметр компонента может привести к непреднамеренным эффектам, таким как бесконечные rerenders, которые зависают в приложении.
@bind:get
/
@bind:set синтаксис позволяет:
- Избегайте создания дополнительного свойства, которое существует только для пересылки значений и обратных вызовов между цепочками компонентов, которые были необходимы до выпуска .NET 7.
- Перехват и преобразование значений перед применением.
- Сохраняйте параметр неизменяемым в дочернем элементе, при этом поддерживая двухстороннюю привязку.
Полезной аналогией является элемент HTML<input>, который отслеживает следующие состояния значений:
-
defaultValue: как и параметр компонента, полученный от родительского элемента. -
value: Как текущее состояние внутри компонента.
Если вы изменяете defaultValue напрямую, вы нарушаете контракт. Вместо этого эти состояния хранятся отдельно, и только value обновляется контролируемым образом после первоначальной отрисовки элемента. То же обоснование применяется к параметрам компонента, и использование @bind:get/@bind:set синтаксиса позволяет избежать потенциальных непреднамеренных эффектов отрисовки, связанных с записью непосредственно в параметры компонента.
NestedChild.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>
<NestedGrandchild @bind-GrandchildMessage:get="ChildMessage"
@bind-GrandchildMessage:set="ChildMessageChanged" />
</div>
@code {
[Parameter]
public string? ChildMessage { get; set; }
[Parameter]
public EventCallback<string?> ChildMessageChanged { get; set; }
private async Task ChangeValue() =>
await ChildMessageChanged.InvokeAsync($"Set in Child {DateTime.Now}");
}
<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>
<NestedGrandchild @bind-GrandchildMessage:get="ChildMessage"
@bind-GrandchildMessage:set="ChildMessageChanged" />
</div>
@code {
[Parameter]
public string? ChildMessage { get; set; }
[Parameter]
public EventCallback<string?> ChildMessageChanged { get; set; }
private async Task ChangeValue() =>
await ChildMessageChanged.InvokeAsync($"Set in Child {DateTime.Now}");
}
<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>
<NestedGrandchild @bind-GrandchildMessage:get="ChildMessage"
@bind-GrandchildMessage:set="ChildMessageChanged" />
</div>
@code {
[Parameter]
public string? ChildMessage { get; set; }
[Parameter]
public EventCallback<string?> ChildMessageChanged { get; set; }
private async Task ChangeValue()
{
await ChildMessageChanged.InvokeAsync(
$"Set in Child {DateTime.Now}");
}
}
<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>
<NestedGrandchild @bind-GrandchildMessage="BoundValue" />
</div>
@code {
[Parameter]
public string? ChildMessage { get; set; }
[Parameter]
public EventCallback<string> ChildMessageChanged { get; set; }
private string BoundValue
{
get => ChildMessage ?? string.Empty;
set => ChildMessageChanged.InvokeAsync(value);
}
private async Task ChangeValue()
{
await ChildMessageChanged.InvokeAsync(
$"Set in Child {DateTime.Now}");
}
}
<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>
<NestedGrandchild @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}");
}
}
<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>
<NestedGrandchild @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}");
}
}
Предупреждение
Как правило, необходимо избегать создания компонентов, которые осуществляют запись непосредственно в собственные параметры компонентов. Предыдущий компонент NestedChild использует свойство BoundValue вместо записи непосредственно в свой параметр ChildMessage. Дополнительные сведения см. в разделе "Избегайте перезаписи параметров" в ASP.NET Core Blazor.
NestedGrandchild.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}");
}
<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}");
}
<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}");
}
}
<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}");
}
}
<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}");
}
}
<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 CoreBlazor.
Связанное поле или дерево выражений свойств
Чтобы упростить более глубокое взаимодействие с привязкой, Blazor позволяет получить дерево выражений связанного поля или свойства. Это достигается путем определения свойства, имя которого является названием поля или свойства с добавлением суффикса Expression. Для любого заданного поля или свойства с именем {FIELD OR PROPERTY NAME}соответствующее свойство дерева выражений называется {FIELD OR PROPERTY NAME}Expression.
ChildParameterExpression Следующий компонент определяет Year модель выражения и имя поля. Элемент FieldIdentifier, который позволяет получить имя модели и поле, однозначно определяет единственное редактируемое поле. Это может соответствовать свойству объекта модели или может быть любым другим именованным значением. Использование выражения параметра полезно при создании настраиваемых компонентов проверки, которые не рассматриваются документацией Майкрософт Blazor , но рассматриваются многочисленными сторонними ресурсами.
ChildParameterExpression.razor:
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized() =>
yearField = FieldIdentifier.Create(YearExpression);
}
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized() =>
yearField = FieldIdentifier.Create(YearExpression);
}
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized()
{
yearField = FieldIdentifier.Create(YearExpression);
}
}
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized()
{
yearField = FieldIdentifier.Create(YearExpression);
}
}
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized()
{
yearField = FieldIdentifier.Create(YearExpression);
}
}
@using System.Linq.Expressions
<ul>
<li>Year model: @yearField.Model</li>
<li>Year field name: @yearField.FieldName</li>
</ul>
@code {
private FieldIdentifier yearField;
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
[Parameter]
public Expression<Func<int>> YearExpression { get; set; } = default!;
protected override void OnInitialized()
{
yearField = FieldIdentifier.Create(YearExpression);
}
}
Parent3.razor:
@page "/parent-3"
<PageTitle>Parent 3</PageTitle>
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
@page "/parent-3"
<PageTitle>Parent 3</PageTitle>
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
@page "/parent-3"
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
@page "/parent-3"
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
@page "/parent-3"
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
@page "/parent-3"
<h1>Parent Example 3</h1>
<p>Parent <code>year</code>: @year</p>
<ChildParameterExpression @bind-Year="year" />
@code {
private int year = 1979;
}
Дополнительные ресурсы
- Обнаружение изменений параметров и дополнительные рекомендации по отрисовке компонентов Razor
- Blazor Обзор основных форм ASP.NET
- Привязка к радиокнопкам в форме
-
Привязка параметров
InputSelectк значениямnullобъекта C# -
Обработка событий Blazor в ASP.NET Core: раздел
EventCallback -
Blazorпримеры репозитория GitHub (
dotnet/blazor-samplesкак скачать)
ASP.NET Core