Liaison de données ASP.NET Core Blazor
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 9 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 9 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 9 de cet article.
Cet article décrit les fonctionnalités de liaison de données pour les composants Razor et les éléments DOM (Document Object Model) dans les applications Blazor.
Fonctionnalités de liaison
Les composants Razor fournissent des fonctionnalités de liaison de données via l’attribut de directive @bind
Razor avec un champ, une propriété ou une valeur d’expression Razor.
L’exemple suivant lie :
- Une valeur d’élément
<input>
au champ C#inputValue
. - Une deuxième valeur d’élément
<input>
à la propriété C#InputValue
.
Quand un élément <input>
perd le focus, son champ ou sa propriété lié est mis à jour.
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; }
}
La zone de texte est mise à jour dans l’IU uniquement au moment du rendu du composant, et non en réponse au changement de valeur du champ ou de la propriété. Dans la mesure où les composants s’affichent après l’exécution du code du gestionnaire d’événements, les mises à jour de champs et de propriétés sont généralement visibles dans l’IU immédiatement après le déclenchement d’un gestionnaire d’événements.
Pour illustrer la composition de la liaison de données en HTML, l’exemple suivant lie la propriété InputValue
aux attributs value
et onchange
(change
) du deuxième élément <input>
. Le deuxième élément <input>
dans l’exemple suivant est une démonstration de concept. Il n’est pas destiné à suggérer la façon dont vous devez lier les données dans les composants 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; }
}
Quand le composant BindTheory
est affiché, le value
de l’élément de démonstration <input>
HTML provient de la propriété InputValue
. Quand l’utilisateur entre une valeur dans la zone de texte et qu’il change le focus de l’élément, l’événement onchange
se déclenche, et la propriété InputValue
est définie en fonction de la valeur changée. En réalité, l’exécution de code est plus complexe, car @bind
gère les cas où des conversions de types sont effectuées. En règle générale, @bind
associe la valeur actuelle d’une expression à l’attribut value
de <input>
, et gère les changements à l’aide du gestionnaire inscrit.
Liez une propriété ou un champ à d’autres événements DOM (Document Object Model) en incluant un attribut @bind:event="{EVENT}"
avec un événement DOM pour l’espace réservé {EVENT}
. L’exemple suivant lie la propriété InputValue
à la valeur de l’élément <input>
quand l’événement oninput
(input
) de l’élément se déclenche. Contrairement à l’événement onchange
(change
), qui se déclenche quand l’élément perd le focus, oninput
(input
) se déclenche quand la valeur de la zone de texte change.
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; }
}
La liaison d’attribut Razor respecte la casse :
@bind
et@bind:event
sont valides.@Bind
/@Bind:Event
(en majusculesB
etE
) ou@BIND
/@BIND:EVENT
(tout en majuscules) ne sont pas valides.
Pour exécuter une logique asynchrone après la liaison, utilisez @bind:after="{DELEGATE}"
, où l’espace {DELEGATE}
réservé est un délégué C# (méthode). Un délégué C# affecté n’est pas exécuté tant que la valeur liée n’est pas affectée de façon synchrone.
L’utilisation d’un paramètre de rappel d’événement (EventCallback
/EventCallback<T>
) avec @bind:after
n’est pas prise en charge. À la place, passez une méthode qui retourne Action ou Task à @bind:after
.
Dans l’exemple suivant :
- La
value
de chaque élément<input>
est liée de manière synchrone au champsearchText
. - La méthode
PerformSearch
s’exécute de manière asynchrone :- Lorsque la première zone perd le focus (événement
onchange
) après la modification de la valeur. - Après chaque séquence de touches (événement
oninput
) dans la deuxième zone.
- Lorsque la première zone perd le focus (événement
PerformSearch
appelle un service avec une méthode asynchrone (FetchAsync
) pour retourner les résultats de la recherche.
@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);
}
Exemples supplémentaires
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; }
}
Pour plus d’informations sur le composant InputText
, consultez Composants d’entrée Blazor ASP.NET Core.
Les composants prennent en charge la liaison de données bidirectionnelle en définissant une paire de paramètres :
@bind:get
: spécifie la valeur à lier.@bind:set
: spécifie un rappel au moment où la valeur change.
Les modificateurs @bind:get
et @bind:set
sont toujours utilisés ensemble.
Exemples
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;
}
}
Pour plus d’informations sur le composant InputText
, consultez Composants d’entrée Blazor ASP.NET Core.
Pour obtenir un autre exemple d’utilisation de @bind:get
et de @bind:set
, consultez la section Liaison sur plus de deux composants plus loin dans cet article.
La liaison d’attribut Razor respecte la casse :
@bind
,@bind:event
et@bind:after
sont valides.@Bind
/@bind:Event
/@bind:aftEr
(en majuscules) ou@BIND
/@BIND:EVENT
/@BIND:AFTER
(tout en majuscules) ne sont pas valides.
Utilisez les modificateurs @bind:get
/@bind:set
, et évitez les gestionnaires d’événements pour la liaison de données bidirectionnelle
La liaison de données bidirectionnelle ne peut pas être implémentée avec un gestionnaire d’événements. Utilisez les modificateurs @bind:get
/@bind:set
pour une liaison de données bidirectionnelle.
Tenez compte de l’approche dysfonctionnelle suivante pour une liaison de données bidirectionnelle à l’aide d’un gestionnaire d’événements :
<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;
}
}
Le gestionnaire d’événements OnInput
met à jour la valeur de inputValue
à Long!
, une fois qu’un quatrième caractère a été fourni. Toutefois, l’utilisateur peut continuer à ajouter des caractères à la valeur d’élément dans l’IU. La valeur de inputValue
n’est pas liée à la valeur de l’élément à chaque frappe. L’exemple précédent est uniquement capable d’établir une liaison de données unidirectionnelle.
Ce comportement est dû au fait que Blazor ne sait pas que votre code a pour but de modifier la valeur de inputValue
dans le gestionnaire d’événements. Blazor n’essaie pas de forcer la correspondance des valeurs des éléments DOM et des valeurs des variables .NET, sauf si elles sont liées par la syntaxe @bind
. Dans les versions antérieures de Blazor, la liaison de données bidirectionnelle est implémentée en liant l’élément à une propriété et en contrôlant la valeur de la propriété avec sa méthode setter. Dans ASP.NET Core dans .NET 7 ou une version ultérieure, la syntaxe du modificateur @bind:get
/@bind:set
est utilisée pour implémenter la liaison de données bidirectionnelle, comme le montre l’exemple suivant.
@bind:get
/@bind:set
pour une liaison de données bidirectionnelle :
<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;
}
}
L’utilisation des modificateurs @bind:get
/@bind:set
contrôle la valeur sous-jacente de inputValue
via @bind:set
, et lie la valeur de inputValue
à la valeur de l’élément via @bind:get
. L’exemple précédent illustre l’approche appropriée pour implémenter la liaison de données bidirectionnelle.
Liaison à une propriété avec les accesseurs C# get
et set
Les accesseurs C# get
et set
peuvent être utilisés pour créer un comportement de format de liaison personnalisé, comme le montre le composant DecimalBinding
suivant. Le composant lie une décimale positive ou négative ayant jusqu’à trois décimales à un élément <input>
à l’aide d’une propriété 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);
}
}
}
}
Remarque
La liaison bidirectionnelle à une propriété avec des accesseurs get
/set
nécessite l’abandon du Task retourné par EventCallback.InvokeAsync. Pour la liaison de données bidirectionnelle, nous vous recommandons d’utiliser des modificateurs @bind:get
/@bind:set
. Pour plus d’informations, consultez les instructions @bind:get
/@bind:set
décrites plus haut dans cet article.
Remarque
La liaison bidirectionnelle à une propriété avec des accesseurs get
/set
nécessite l’abandon du Task retourné par EventCallback.InvokeAsync. Pour la liaison de données bidirectionnelle dans ASP.NET Core dans .NET 7 ou une version ultérieure, nous recommandons d’utiliser les modificateurs @bind:get
/@bind:set
décrits dans les versions 7.0 et ultérieures de cet article.
Sélection d’options multiples avec des éléments <select>
La liaison prend en charge la sélection d’options multiples via l’attribut multiple
avec les éléments <select>
. L’événement @onchange
fournit un tableau des éléments sélectionnés via des arguments d’événement (ChangeEventArgs
). La valeur doit être liée à un type tableau.
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;
}
}
}
Pour plus d’informations sur la façon dont les chaînes vides et les valeurs null
sont gérées dans la liaison de données, consultez la section Liaison des options de l’élément <select>
aux valeurs null
d’un objet C#.
Liaison des options de l’élément <select>
aux valeurs null
d’un objet C#
Il n’existe aucun moyen judicieux de représenter une valeur d’option d’élément <select>
en tant que valeur null
d’un objet C#, car :
- Les attributs HTML ne peuvent pas avoir de valeurs
null
. L’équivalent le plus proche denull
en HTML est l’absence de l’attribut HTMLvalue
dans l’élément<option>
. - Quand vous sélectionnez un
<option>
sans attributvalue
, le navigateur traite la valeur en tant que contenu textuel de l’élément de<option>
.
Le framework Blazor ne tente pas de supprimer le comportement par défaut, car cela impliquerait les effets suivants :
- Création d’une chaîne de solutions de contournement pour les cas particuliers dans le framework.
- Changements cassants apportés au comportement actuel du framework.
L’équivalent le plus plausible de null
en HTML est une chaîne vide value
. Le framework Blazor gère les conversions de null
en chaînes vides pour une liaison bidirectionnelle avec une valeur de <select>
.
Valeurs non analysables
Quand un utilisateur fournit une valeur non analysable à un élément lié aux données, la valeur précédente de la valeur non analysable est automatiquement rétablie au moment où l’événement de liaison se déclenche.
Prenons le cas du composant suivant, où un élément <input>
est lié à un type int
avec la valeur initiale 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;
}
La liaison s’applique à l’événement onchange
de l’élément. Si l’utilisateur met à jour la valeur de l’entrée de la zone de texte en la remplaçant par 123.45
, puis change le focus, la valeur de l’élément est rétablie à 123
au moment où onchange
se déclenche. Quand la valeur 123.45
est rejetée au profit de la valeur d’origine de 123
, l’utilisateur comprend que sa valeur n’a pas été acceptée.
Pour l’événement oninput
(@bind:event="oninput"
), un rétablissement de valeur se produit après toute séquence de touches qui introduit une valeur non analysable. En cas de ciblage de l’événement oninput
avec un type lié à int
, l’utilisateur ne peut pas taper de point (.
). Le point (.
) est instantanément supprimé, et l’utilisateur reçoit immédiatement un commentaire indiquant que seuls les nombres entiers sont autorisés. Il existe des scénarios où le rétablissement de la valeur au moment où l’événement oninput
se produit n’est pas idéal, par exemple quand l’utilisateur doit être autorisé à effacer une valeur <input>
non analysable. Il existe des alternatives :
- N’utilisez pas l’événement
oninput
. Utilisez l’événementonchange
par défaut, où une valeur non valide n’est pas rétablie tant que l’élément n’a pas perdu le focus. - Effectuez une liaison à un type Nullable, par exemple
int?
oustring
, et utilisez les modificateurs@bind:get
/@bind:set
(décrits plus haut dans cet article), ou effectuez une liaison à une propriété avec une logique d’accesseursget
etset
personnalisée pour gérer les entrées non valides. - Utilisez un composant d’entrée, tel que InputNumber<TValue> ou InputDate<TValue>, avec une validation de formulaire. Les composants d’entrée associés à des composants de validation de formulaire fournissent une prise en charge intégrée de la gestion des entrées non valides :
- Permettez à l’utilisateur de fournir des entrées non valides, et de recevoir des erreurs de validation sur le EditContext associé.
- Affichez les erreurs de validation dans l’IU sans interférer avec l’utilisateur qui entre des données de formulaire web supplémentaires.
Chaînes de format
La liaison de données fonctionne avec une seule chaîne de format DateTime à l’aide de @bind:format="{FORMAT STRING}"
, où l’espace réservé {FORMAT STRING}
correspond à la chaîne de format. Les autres expressions de format, par exemple pour les formats monétaires ou numériques, ne sont pas disponibles, mais elles seront peut-être ajoutées dans une prochaine version.
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);
}
Dans le code précédent, le type de champ de l’élément <input>
(attribut type
) a la valeur par défaut text
.
Les types Nullable System.DateTime et System.DateTimeOffset sont pris en charge :
private DateTime? date;
private DateTimeOffset? dateOffset;
La spécification d’un format pour le type de champ date
n’est pas recommandée, car Blazor offre une prise en charge intégrée du format des dates. Malgré la recommandation, utilisez uniquement le format de date yyyy-MM-dd
pour que la liaison fonctionne correctement si un format est fourni avec le type de champ date
:
<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">
Liaison à des paramètres de composant
La liaison d’une propriété d’un composant enfant à une propriété de son composant parent est un scénario courant. Ce scénario est appelé liaison chaînée, car plusieurs niveaux de liaison sont impliqués simultanément.
Vous ne pouvez pas implémenter de liaisons chaînées avec la syntaxe @bind
dans un composant enfant. Vous devez spécifier séparément un gestionnaire d’événements et une valeur pour permettre la prise en charge de la mise à jour de la propriété dans le parent à partir du composant enfant. Le composant parent tire tout de même profit de la syntaxe @bind
pour configurer la liaison de données avec le composant enfant.
Le composant ChildBind
suivant a un paramètre de composant Year
et un EventCallback<TValue>. Par convention, le EventCallback<TValue> du paramètre doit avoir un nom qui correspond au nom de paramètre du composant suivi du suffixe « Changed
». La syntaxe de nommage est {PARAMETER NAME}Changed
, où l’espace réservé {PARAMETER NAME}
correspond au nom de paramètre. Dans l’exemple suivant, EventCallback<TValue> est nommé YearChanged
.
EventCallback.InvokeAsync appelle le délégué associé à la liaison avec l’argument fourni, et distribue une notification d’événement pour la propriété changée.
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));
}
}
Pour plus d’informations sur les événements et EventCallback<TValue>, consultez la section EventCallback de l’article Gestion des événements ASP.NET Core Blazor.
Dans le composant Parent1
suivant, le champ year
est lié au paramètre Year
du composant enfant. Le paramètre Year
peut être lié, car il a un événement compagnon YearChanged
qui correspond au type du paramètre 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);
}
}
La liaison d’un paramètre de composant peut également déclencher des événements @bind:after
. Dans l’exemple suivant, la méthode YearUpdated
s’exécute de manière asynchrone après la liaison du paramètre de composant Year
.
<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" />
@code {
...
private async Task YearUpdated()
{
... = await ...;
}
}
Par convention, vous pouvez lier une propriété à un gestionnaire d’événements correspondant en incluant un attribut @bind-{PROPERTY}:event
affecté au gestionnaire, où l’espace réservé {PROPERTY}
représente la propriété. <ChildBind @bind-Year="year" />
équivaut à écrire :
<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />
Dans un exemple plus sophistiqué et plus réaliste, le composant PasswordEntry
suivant :
- Affecte un champ
password
à la valeur d’un élément<input>
. - Expose les changements d’une propriété
Password
à un composant parent avec unEventCallback
qui passe la valeur actuelle du champpassword
de l’enfant en tant qu’argument. - Utilise l’événement
onclick
pour déclencher la méthodeToggleShowPassword
. Pour plus d’informations, consultez Gestion des événements ASP.NET Core Blazor.
Avertissement
Ne stockez pas les secrets d’application, les chaîne de connexion, les informations d’identification, les mots de passe, les numéros d’identification personnels (PIN), le code C#/.NET privé ou les clés/jetons privés dans le code côté client, qui est toujours non sécurisé. Dans les environnements de test/intermédiaire et de production, le code côté Blazor serveur et les API web doivent utiliser des flux d’authentification sécurisés qui évitent de conserver les informations d’identification dans le code du projet ou les fichiers de configuration. En dehors des tests de développement locaux, nous vous recommandons d’éviter l’utilisation de variables d’environnement pour stocker des données sensibles, car les variables d’environnement ne sont pas l’approche la plus sécurisée. Pour les tests de développement locaux, l’outil Secret Manager est recommandé pour sécuriser les données sensibles. Pour plus d’informations, consultez Gestion sécurisée des données sensibles et des informations d’identification.
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;
}
}
<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;
}
}
Le composant PasswordEntry
est utilisé dans un autre composant, à l’image de l’exemple de composant PasswordBinding
suivant.
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";
}
Quand le composant PasswordBinding
est affiché initialement, la valeur password
de Not set
s’affiche dans l’IU. Après le rendu initial, la valeur de password
reflète les changements apportés à la valeur du paramètre de composant Password
dans le composant PasswordEntry
.
Remarque
L’exemple précédent lie le mot de passe unidirectionnel du composant enfant PasswordEntry
au composant parent PasswordBinding
. La liaison bidirectionnelle n’est pas obligatoire dans ce scénario si l’objectif est que l’application dispose d’un composant d’entrée de mot de passe partagé réutilisable dans l’application, qui transmet simplement le mot de passe au parent. Pour une approche qui permet d’effectuer une liaison bidirectionnelle sans écrire directement dans le paramètre du composant enfant, consultez l’exemple de composant NestedChild
dans la section Lier plus de deux composants de cet article.
Effectuez des vérifications ou interceptez les erreurs dans le gestionnaire. Le composant PasswordEntry
révisé suivant fournit une indication immédiate à l’utilisateur si un espace est utilisé dans la valeur du mot de passe.
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;
}
<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;
}
}
Dans l’exemple suivant, la méthode PasswordUpdated
s’exécute de manière asynchrone après la liaison du paramètre de composant 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;
}
}
Lier plus de deux composants
Vous pouvez lier des paramètres via un nombre illimité de composants imbriqués, mais vous devez respecter le flux de données unidirectionnel :
- Le flux des notifications de changement va vers le haut de la hiérarchie.
- Le flux des nouvelles valeurs de paramètre va vers le bas de la hiérarchie.
Une approche courante et recommandée consiste à stocker uniquement les données sous-jacentes dans le composant parent pour éviter toute confusion sur l’état à mettre à jour, comme le montre l’exemple suivant.
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}";
}
}
Dans le composant NestedChild
suivant, le composant NestedGrandchild
:
- Affecte la valeur de
ChildMessage
àGrandchildMessage
avec la syntaxe@bind:get
. - Met à jour
GrandchildMessage
quandChildMessageChanged
s’exécute avec la syntaxe@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}");
}
}
Warning
En règle générale, évitez de créer des composants qui écrivent directement dans leurs propres paramètres de composant. Le composant NestedChild
précédent utilise une propriété BoundValue
au lieu d’écrire directement dans son paramètre ChildMessage
. Pour plus d’informations, consultez Éviter le remplacement de paramètres dans 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}");
}
}
Pour une autre approche adaptée au partage des données en mémoire et entre des composants qui ne sont pas nécessairement imbriqués, consultez Gestion d’état ASP.NET Core Blazor.
Champ lié ou arborescence de l’expression de propriété
Pour faciliter les interactions plus approfondies avec une liaison, Blazor vous permet de capturer l’arborescence d’expressions d’une propriété ou d’un champ lié. Pour ce faire, définissez une propriété avec le nom de champ ou de propriété au suffixe Expression
. Pour tout champ ou propriété donné nommé {FIELD OR PROPERTY NAME}
, la propriété de l’arborescence d’expressions correspondante est nommée {FIELD OR PROPERTY NAME}Expression
.
Le composant ChildParameterExpression
suivant identifie le modèle et le nom de champ de l’expression Year
. Un FieldIdentifier, utilisé pour obtenir le modèle et le nom de champ, identifie de manière unique un champ qui peut être modifié. Cela peut correspondre à une propriété sur un objet modèle ou cela peut être n’importe quelle autre valeur nommée. L’utilisation de l’expression d’un paramètre est utile lors de la création de composants de validation personnalisés, qui n’est pas couverte par la documentation Microsoft Blazor, mais par de nombreuses ressources tierces.
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;
}
Ressources supplémentaires
- Détection des changements de paramètres et conseils d’aide supplémentaires sur le rendu des composants Razor
- Vue d’ensemble des formulaires Blazor ASP.NET Core
- Liaison à des cases d’option dans un formulaire
- Liaison des options
InputSelect
aux valeursnull
d’un objet C# - Gestion des événements ASP.NET Core Blazor : section
EventCallback
- Blazor exemples du référentiel GitHub (
dotnet/blazor-samples
) (procédure de téléchargement)