Привязка к данным в ASP.NET Core Blazor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 8 этой статьи.
В этой статье описываются функции привязки данных для Razor компонентов и элементов DOM в Blazor приложениях.
Функции привязки
Razor компоненты предоставляют функции привязки данных с @bind
Razor атрибутом директивы с значением поля, свойства или 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"
<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
привязывается к атрибутам value
и onchange
change
второго элемента <input>
. Второй элемент <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"
<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
атрибутом атрибута <input>
и обрабатывает изменения с помощью зарегистрированного обработчика.
Привязка свойства или поля к другим событиям DOM путем включения @bind:event="{EVENT}"
атрибута с событием DOM для {EVENT}
заполнителя. В следующем примере показана привязка свойства InputValue
к значению элемента <input>
при активации события oninput
(input
) элемента. В отличие от события onchange
(change
), которое происходит, когда элемент утрачивает фокус, событие oninput
(input
) происходит при изменении значения в текстовом поле.
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"
<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; }
}
Razor Привязка атрибута учитывает регистр:
@bind
и@bind:event
являются допустимыми.@Bind
/@Bind:Event
(прописные буквыB
иE
) или@BIND
/@BIND:EVENT
(все прописные буквы) недопустимы.
Чтобы выполнить асинхронную логику после привязки, используйте @bind:after="{EVENT}"
с событием DOM вместо заполнителя {EVENT}
. Назначенный метод 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
: указывает значение для привязки.@bind:set
: указывает обратный вызов при изменении значения.
@bind:get
Модификаторы и @bind:set
модификаторы всегда используются вместе.
Примеры
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 = "";
private void Set(string value)
{
text = value;
}
private Task SetAsync(string value)
{
text = value;
return Task.CompletedTask;
}
}
Дополнительные сведения о компоненте InputText
см. в разделе ASP.NET Основные Blazor входные компоненты.
Другой пример использования и @bind:set
см. в разделе "Привязка@bind:get
" более двух компонентов далее в этой статье.
Razor Привязка атрибута учитывает регистр:
@bind
,@bind:event
и@bind:after
допустимы.@Bind
/@bind:Event
/@bind:aftEr
(прописными буквами) или@BIND
/@BIND:EVENT
/@BIND:AFTER
(строчными буквами полностью) недопустимы.
Использование модификаторов и избегайте @bind:get
/@bind:set
обработчиков событий для двусторонней привязки данных
Двусторонняя привязка данных невозможно реализовать с помощью обработчика событий. Используйте @bind:get
/@bind: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 или более поздней версии @bind:get
/@bind:set
синтаксис модификатора используется для реализации двусторонней привязки данных, как показано в следующем примере.
@bind:set
@bind:get
/для двусторонней привязки данных:
<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;
}
}
Используя @bind:get
/@bind:set
модификаторы, оба управляют базовым значением inputValue
через @bind:set
и привязывают значение к значению inputValue
элемента через @bind: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
<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
средствами доступа требует отмены карта возвращаемого.TaskEventCallback.InvokeAsync Для двусторонней привязки данных рекомендуется использовать @bind:get
/@bind:set
модификаторы. Дополнительные сведения см @bind:get
/@bind:set
. в руководстве, приведенном ранее в этой статье.
Примечание.
Двусторонняя привязка к свойству с get
/set
средствами доступа требует отмены карта возвращаемого.TaskEventCallback.InvokeAsync Для двусторонней привязки данных в ASP.NET Core в .NET 7 или более поздней версии рекомендуется использовать @bind:get
/@bind:set
модификаторы, описанные в версии 7.0 или более поздней версии этой статьи.
Выбор нескольких вариантов с элементами <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; } = 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 "/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
и изменяет фокус, при активации события onchange
восстанавливается значение 123
элемента. Когда значение 123.45
отклоняется и вместо него используется исходное значение 123
, пользователь понимает, что его значение не было принято.
Для события oninput
(@bind:event="oninput"
) исходное значение восстанавливается после любого нажатия клавиши, которое приводит к вводу не поддающегося анализу значения. При привязке типа int
к событию oninput
пользователь не может ввести символ точки (.
). Символ точки (.
) немедленно удаляется, так что пользователь сразу понимает, что допустимы только целые числа. В некоторых ситуациях восстанавливать исходное значение при возникновении события oninput
нежелательно, например, когда пользователю следует дать возможность самому удалять не поддающиеся анализу значения <input>
. Ниже представлены возможные альтернативы.
- Не используйте событие
oninput
. Используйте вместо этого событиеonchange
по умолчанию. В этом случае недопустимое значение не будет меняться на исходное, пока элемент не потеряет фокус. - Привязывайтесь к типу, допускающего значение NULL, например
int?
или используйте@bind:set
/@bind:get
модификаторы (описанные ранее в этой статье) илиstring
привязывайтесь к свойству с помощью пользовательской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"
<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 есть встроенная поддержка форматирования дат. Если все же для типа поля date
указывается формат, для правильной работы привязки используйте только формат даты yyyy-MM-dd
.
<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 {
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"
<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.
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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
Компонент 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"
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
private void ToggleShowPassword()
{
showPassword = !showPassword;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
private void ToggleShowPassword()
{
showPassword = !showPassword;
}
}
В следующем примере 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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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 != null && password.Contains(' '))
{
validationMessage = "Spaces not allowed!";
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
private void ToggleShowPassword()
{
showPassword = !showPassword;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
<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="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>
@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;
}
}
Привязка через более чем два компонента
Привязку параметров можно выполнять через любое число вложенных компонентов, но необходимо соблюдать односторонний поток данных:
- уведомления об изменениях передаются вверх по иерархии;
- новые значения параметров передаются вниз по иерархии.
Распространенный и рекомендуемый подход заключается в хранении базовых данных только в родительском компоненте, что позволяет избежать путаницы в отношении того, какое состояние необходимо обновить, как показано в следующем примере.
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"
<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
.
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="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}");
}
}
Описание альтернативного подхода к совместному использованию данных в памяти и между компонентами, которые не обязательно являются вложенными, см. в статье Управление состоянием ASP.NET Core Blazor.
Дополнительные ресурсы
- Обнаружение изменений параметров и дополнительные рекомендации по отрисовке компонентов Razor
- Blazor Обзор основных форм ASP.NET
- Привязка к переключателям в форме
- Привязка параметров
InputSelect
к значениямnull
объекта C# - Обработка событий Blazor в ASP.NET Core: раздел
EventCallback
- Blazorпримеры репозитория GitHub (
dotnet/blazor-samples
как скачать)
ASP.NET Core
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по