Blazor : les composants
Le composant .razor et son passage de paramètres : types variés, attribute splatting, paramètres en cascade, data binding, et un aperçu de EventCallback pour la communication enfant vers parent.
Blazor : les composants
Un composant Blazor, c'est l'équivalent .NET d'un composant React : une brique d'interface réutilisable. Techniquement, un fichier .razor est compilé en une classe qui hérite de ComponentBase. On repart de la syntaxe Razor.
# Un composant = un fichier .razor
Un fichier Salutation.razor combine le markup (Razor) et la logique (@code). Son nom, en PascalCase, devient une balise.
@* Salutation.razor *@
<h1>Bonjour @Nom !</h1>
@code {
[Parameter] public string Nom { get; set; } = "";
}@* Salutation.razor *@<h1>Bonjour @Nom !</h1> @code { [Parameter] public string Nom { get; set; } = "";}Utilisation, exactement comme une balise HTML :
<Salutation Nom="Baptiste" />
<Salutation Nom="Stefano" /><Salutation Nom="Baptiste" /><Salutation Nom="Stefano" /># Passer des paramètres
Pour qu'un composant s'adapte, on lui passe des données via des propriétés marquées [Parameter], l'équivalent des props de React. Un paramètre accepte n'importe quel type, pas seulement des chaînes :
@* CarteProduit.razor *@
<article class="carte @(EnPromo ? "promo" : "")">
<h2>@Produit.Nom</h2>
<p>@Produit.Prix.ToString("C")</p>
<ul>
@foreach (var tag in Tags)
{
<li>@tag</li>
}
</ul>
</article>
@code {
[Parameter, EditorRequired] public Produit Produit { get; set; } = default!;
[Parameter] public IReadOnlyList<string> Tags { get; set; } = [];
[Parameter] public bool EnPromo { get; set; }
}@* CarteProduit.razor *@<article class="carte @(EnPromo ? "promo" : "")"> <h2>@Produit.Nom</h2> <p>@Produit.Prix.ToString("C")</p> <ul> @foreach (var tag in Tags) { <li>@tag</li> } </ul></article> @code { [Parameter, EditorRequired] public Produit Produit { get; set; } = default!; [Parameter] public IReadOnlyList<string> Tags { get; set; } = []; [Parameter] public bool EnPromo { get; set; }}On lui passe un objet, une liste et un booléen :
<CarteProduit Produit="monProduit" Tags="etiquettes" EnPromo="true" /><CarteProduit Produit="monProduit" Tags="etiquettes" EnPromo="true" />Ce qu'il faut retenir sur le passage :
- Un paramètre accepte tout type :
string,int,record, collection,enum, délégué. - Les booléens sont explicites :
EnPromo="true". Il n'y a pas de raccourci HTML<CarteProduit EnPromo>. [EditorRequired]fait avertir l'éditeur quand un paramètre obligatoire est oublié.- Les paramètres sont en lecture seule côté enfant : le parent les réécrit à chaque rendu.
# Attribute splatting (@attributes)
Pour laisser passer des attributs HTML arbitraires (class, id, data-*, aria-*, @onclick) jusqu'à l'élément racine, capture-les puis déverse-les avec @attributes :
@* Bouton.razor *@
<button class="btn" @attributes="Extra">@ChildContent</button>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public IReadOnlyDictionary<string, object>? Extra { get; set; }
}@* Bouton.razor *@<button class="btn" @attributes="Extra">@ChildContent</button> @code { [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? Extra { get; set; }}@* id, data-role et @onclick ne sont pas déclarés : ils atterrissent sur le <button> *@
<Bouton id="valider" data-role="submit" @onclick="Valider">Valider</Bouton>@* id, data-role et @onclick ne sont pas déclarés : ils atterrissent sur le <button> *@<Bouton id="valider" data-role="submit" @onclick="Valider">Valider</Bouton>CaptureUnmatchedValues = true récupère tout ce que le composant ne déclare pas. Indispensable pour des composants d'UI génériques (boutons, champs) qui doivent rester ouverts aux attributs standards.
# Passer un paramètre en cascade
Quand une donnée doit descendre à travers plusieurs niveaux de composants, la threader à chaque étage est pénible. CascadingValue la rend disponible à tous les descendants, sans la passer explicitement :
@* Layout.razor : on diffuse le thème vers tout le sous-arbre *@
<CascadingValue Value="theme">
@Body
</CascadingValue>
@code {
private string theme = "sombre";
}@* Layout.razor : on diffuse le thème vers tout le sous-arbre *@<CascadingValue Value="theme"> @Body</CascadingValue> @code { private string theme = "sombre";}@* N'importe quel composant en dessous, même profond, le reçoit *@
@code {
[CascadingParameter] private string Theme { get; set; } = "";
}@* N'importe quel composant en dessous, même profond, le reçoit *@@code { [CascadingParameter] private string Theme { get; set; } = "";}C'est l'équivalent du Context de React. À réserver aux données transverses et stables (thème, utilisateur connecté, culture) ; pour un passage local, un [Parameter] classique reste plus lisible.
# Le data binding (@bind)
Un raccourci fréquent : lier un champ à une variable dans les deux sens.
<input @bind="nom" @bind:event="oninput" />
<p>Bonjour @nom</p>
@code { private string nom = ""; }<input @bind="nom" @bind:event="oninput" /><p>Bonjour @nom</p> @code { private string nom = ""; }@bind génère la lecture et l'écriture. Le sujet est vaste (événement, format, culture, binding entre composants) : il a sa page dédiée, le data binding.
# Remonter au parent : EventCallback
Un enfant ne modifie pas le parent directement : il le prévient via un EventCallback. Sans valeur, c'est un simple signal :
@* Bouton.razor *@
<button @onclick="() => OnClic.InvokeAsync()">@Libelle</button>
@code {
[Parameter] public string Libelle { get; set; } = "";
[Parameter] public EventCallback OnClic { get; set; }
}@* Bouton.razor *@<button @onclick="() => OnClic.InvokeAsync()">@Libelle</button> @code { [Parameter] public string Libelle { get; set; } = ""; [Parameter] public EventCallback OnClic { get; set; }}Un EventCallback<T> peut aussi transporter une valeur (la note choisie, l'article cliqué, un record de plusieurs champs) et redéclenche le rendu du parent. Le détail, les exemples et le choix face à Action sont sur sa page dédiée : EventCallback.
# La composition (RenderFragment)
Un composant peut recevoir du contenu balisé via ChildContent, de type RenderFragment :
@* Carte.razor *@
<div class="carte">@ChildContent</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}@* Carte.razor *@<div class="carte">@ChildContent</div> @code { [Parameter] public RenderFragment? ChildContent { get; set; }}<Carte>
<h2>Titre</h2>
<p>N'importe quel balisage passe ici.</p>
</Carte><Carte> <h2>Titre</h2> <p>N'importe quel balisage passe ici.</p></Carte>Pour aller plus loin, un RenderFragment<T> permet au parent de fournir un gabarit paramétré (chaque élément d'une liste, par exemple) via la variable implicite @context. Comme React, Blazor privilégie la composition plutôt que l'héritage.
# À retenir
[Parameter]accepte tout type (objet, collection,enum) ; les booléens sont explicites ;[EditorRequired]marque les obligatoires.@attributes+CaptureUnmatchedValuestransmettent les attributs HTML non déclarés au composant.CascadingValue/[CascadingParameter]diffusent une donnée transverse à tous les descendants.- L'enfant remonte au parent via
EventCallback(une valeur avecEventCallback<T>) ; détails et exemples sur sa page dédiée.
Prochaine étape : où tout ce code s'exécute-t-il réellement ? L'hébergement et les modes de rendu.
Pour aller plus loin
Blazor : le data binding
@bind en profondeur : événement de déclenchement, format et culture, @bind:get/set et @bind:after, et comment rendre un composant bindable avec la convention Value / ValueChanged.
Blazor : EventCallback
La communication enfant vers parent : EventCallback sans valeur, EventCallback<T> avec une valeur (int, objet, record pour plusieurs champs), et pourquoi EventCallback plutôt qu'Action ou Func.
Blazor : le cycle de vie
Les méthodes de cycle de vie d'un composant : OnInitialized, OnParametersSet, OnAfterRender (firstRender), ShouldRender et StateHasChanged, Dispose, plus le piège du rendu asynchrone et du prerendering.
Blazor : formulaires & validation
Construire un formulaire avec EditForm, lier les champs via les composants Input*, valider avec les DataAnnotations, afficher les erreurs (ValidationMessage) et réagir à la soumission.