Blazor Parameters – Demo đầy đủ
Cover: Route parameter, [Parameter], default + EditorRequired, CascadingParameter, CaptureUnmatchedValues, EventCallback, RenderFragment/ChildContent, RenderFragment<T>, Two-way parameter pattern.
1- Route Parameter (tham số trên URL)
Route param là route của một page. Bên dưới chỉ là code sample để xem (không chạy trong page này).
Xem code (route param sample)
@page "/product/{Id:int}"
<h3>Product Id: @Id</h3>
@code {
[Parameter]
public int Id { get; set; }
}
2- [Parameter] cơ bản (Parent → Child)
CHA truyền dữ liệu xuống CON qua attribute.
Xem code
<button class="btn btn-primary" @onclick="() => Count++">Count++</button>
<span><b>Parent Count:</b> @Count</span>
<ParamChild Title="Hello from Parent" Count="@Count" />
@code {
private int Count = 0;
}
<div class="border rounded p-2">
<div><b>ParamChild</b></div>
<div>Title: @Title</div>
<div>Count: @Count</div>
</div>
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public int Count { get; set; }
}
3- Optional / Default & EditorRequired
Parameter có thể có default. Nếu muốn “bắt buộc truyền”, dùng [EditorRequired].
Xem code
<!-- Không truyền Count -->
<ParamChild Title="Chỉ truyền Title (Count sẽ default)" />
<div>Title: @Title</div>
<div>Count: @Count</div>
@code {
[Parameter, EditorRequired]
public string Title { get; set; } = "";
// Default value nếu CHA không truyền
[Parameter]
public int Count { get; set; } = 100;
}
4- CascadingParameter (truyền dữ liệu ngầm xuống nhiều cấp)
CascadingParameter dùng khi bạn muốn truyền dữ liệu dùng chung từ component cha xuống tất cả component con/cháu mà không cần truyền qua từng cấp.
Component cha “phát” dữ liệu bằng <CascadingValue>,
component con nhận bằng [CascadingParameter].
Xem code
<select class="form-select w-25" @bind="Theme">
<option>Light</option>
<option>Dark</option>
<option>Blue</option>
</select>
<CascadingValue Value="@Theme">
<ParamChildCascading />
</CascadingValue>
@code {
private string Theme = "Light";
}
<div class="border rounded p-2">
<div><b>ParamChildCascading</b></div>
<div>Theme: <b>@Theme</b></div>
</div>
@code {
[CascadingParameter]
public string? Theme { get; set; }
}
5- CaptureUnmatchedValues (nhận attribute dư)
CHA truyền thêm class/style/data-*, CON nhận bằng AdditionalAttributes.
Xem code
<ParamChildWithAttrs class="border p-2 rounded"
style="margin-top:8px;"
data-test="abc"
Text="Component nhận attrs" />
<div @attributes="AdditionalAttributes">
<b>ParamChildWithAttrs:</b> @Text
</div>
@code {
[Parameter] public string Text { get; set; } = "";
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
}
6- EventCallback (Child → Parent)
CON bắn sự kiện lên CHA qua EventCallback.
Last click: none
Xem code
// CHA
<ParamChildEvent OnClicked="HandleChildClicked" />
@code {
private string LastClick = "none";
private void HandleChildClicked()
{
LastClick = $"Child clicked at {DateTime.Now:HH:mm:ss}";
}
}
// CON
<button class="btn btn-outline-success" @onclick="OnClicked"> Child Click Me </button>
@code {
[Parameter] public EventCallback OnClicked { get; set; }
}
// CHA
<ParamChildEvent OnClicked="HandleChildClicked" />
@code {
private void HandleChildClicked()
{
LastClick = $"Child clicked at {DateTime.Now:HH:mm:ss}";
}
}
// CON
<button class="btn btn-outline-success" @onclick="HandleClick"> Child Click Me </button>
@code {
[Parameter] public EventCallback OnClicked { get; set; }
private async Task HandleClick()
{
// CON xử lý trước khi gọi CHA
await Task.Delay(150);
await OnClicked.InvokeAsync();
}
}
// CHA
<ParamChildEvent OnClicked="HandleChildClicked" />
@code {
private void HandleChildClicked(string message)
{
LastClick = message;
}
}
// CON
<button class="btn btn-outline-success" @onclick="HandleClick"> Child Click Me </button>
@code {
[Parameter] public EventCallback<string> OnClicked { get; set; }
private async Task HandleClick()
{
var msg = $"Child clicked at {DateTime.Now:HH:mm:ss}";
await OnClicked.InvokeAsync(msg);
}
}
// CHA (async)
<ParamChildEvent OnClicked="HandleChildClickedAsync" />
@code {
private async Task HandleChildClickedAsync()
{
// CHA xử lý async (API / DB / JS / delay...)
await Task.Delay(300);
LastClick = $"Child clicked at {DateTime.Now:HH:mm:ss}";
}
}
// CON (InvokeAsync chờ CHA xong)
<button class="btn btn-outline-success" @onclick="HandleClick"> Child Click Me </button>
@code {
[Parameter] public EventCallback OnClicked { get; set; }
private async Task HandleClick()
{
// CON xử lý trước
await Task.Delay(150);
// ĐỢI CHA xử lý xong
await OnClicked.InvokeAsync();
// Code ở đây chỉ chạy SAU khi CHA xong
}
}
// CHA
// Gán callback bằng C# (không bó cứng trong markup)
<ParamChildEvent OnClicked="clickCallback" />
@code {
private EventCallback clickCallback;
private bool IsAdmin = true;
protected override void OnInitialized()
{
clickCallback = EventCallback.Factory.Create(
this,
IsAdmin ? HandleAdminClick : HandleUserClick
);
}
private void HandleAdminClick()
{
LastClick = "Admin handled click";
}
private void HandleUserClick()
{
LastClick = "User handled click";
}
}
// CON
<button class="btn btn-outline-success" @onclick="HandleClick"> Child Click Me </button>
@code {
[Parameter] public EventCallback OnClicked { get; set; }
private async Task HandleClick()
{
// Kiểm tra CHA có gán handler hay không
if (OnClicked.HasDelegate)
{
// ĐỢI CHA xử lý xong (nếu CHA là async Task)
await OnClicked.InvokeAsync();
}
// else: callback optional → CON tự xử lý tiếp
}
}
7- RenderFragment / ChildContent (truyền nội dung UI)
CHA truyền khối UI vào CON thông qua ChildContent.
Đây là nội dung truyền từ Parent xuống.
Xem code
<ParamChildContent Title="Card Title">
<p>Đây là nội dung truyền từ Parent xuống.</p>
<button class="btn btn-sm btn-outline-primary" @onclick="() => ContentClicks++"> Content button (clicks: @ContentClicks) </button>
</ParamChildContent>
@code {
private int ContentClicks = 0;
}
<div class="border rounded p-2">
<div class="mb-2"><b>@Title</b></div>
<div>
@ChildContent
</div>
</div>
@code {
[Parameter] public string Title { get; set; } = "";
[Parameter] public RenderFragment? ChildContent { get; set; }
}
8- RenderFragment<T> (Template parameter)
CON nhận list + template, CHA quyết định UI hiển thị của từng item bằng ItemTemplate.
SelectedUser: none
Xem code
namespace BlazorSample.Models;
public record User(string Name, int Age);
<ParamChildTemplate Items="@Users">
<ItemTemplate Context="u">
<div class="d-flex justify-content-between border rounded p-2 mb-1">
<span><b>@u.Name</b> (Age: @u.Age)</span>
<button class="btn btn-sm btn-outline-secondary" @onclick="() => SelectedUser = u.Name">Select</button>
</div>
</ItemTemplate>
</ParamChildTemplate>
<p><b>SelectedUser:</b> @SelectedUser</p>
@code {
private List<User> Users = new()
{
new("Alice", 22),
new("Bob", 30),
new("Charlie", 27)
};
private string SelectedUser = "none";
}
@using BlazorSample.Models
@if (Items?.Count > 0)
{
foreach (var item in Items)
{
@ItemTemplate?.Invoke(item)
}
}
else
{
<p class="text-muted">No items</p>
}
@code {
[Parameter] public List<User> Items { get; set; } = new();
[Parameter] public RenderFragment<User>? ItemTemplate { get; set; }
}
9- Two-way parameter pattern (bindable component)
CON hỗ trợ @bind-Value bằng Value + ValueChanged.
TwoWayText: hello
Xem code
<ParamTwoWay @bind-Value="TwoWayText" />
<p><b>TwoWayText:</b> @TwoWayText</p>
@code {
private string? TwoWayText = "hello";
}
<input class="form-control"value="@Value"@oninput="OnInput" />
@code {
[Parameter] public string? Value { get; set; }
[Parameter] public EventCallback<string?> ValueChanged { get; set; }
private async Task OnInput(ChangeEventArgs e)
{
var v = e.Value?.ToString();
Value = v;
await ValueChanged.InvokeAsync(v);
}
}
10- @ref (CHA gọi trực tiếp method của CON)
@ref cho phép component CHA giữ tham chiếu đến component CON
và gọi trực tiếp method public của CON.
Đây là ngoại lệ trong Blazor (không dùng cho data flow).
Xem code (@ref sample)
<!-- Parent (Page) -->
<RefChild @ref="childRef" />
<button @onclick="CallIncrement">Increment (CHA → CON)</button>
<button @onclick="CallReset">Reset</button>
@code {
private RefChild? childRef;
private void CallIncrement()
{
childRef?.Increment();
}
private void CallReset()
{
childRef?.Reset();
}
}
<!-- Child component file name [RefChild.razor] -->
<div>Counter: @Counter</div>
@code {
private int Counter = 0;
public void Increment()
{
Counter++;
}
public void Reset()
{
Counter = 0;
}
}