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.

Parent Count: 0
ParamChild
Title: Hello from Parent
Count: 0
Xem code
CHA (Pages/ParametersDemo.razor)

<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;
}
        
CON (Components/ParamChild.razor)

<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].

ParamChild
Title: Chỉ truyền Title (Count sẽ default)
Count: 100
Xem code
CHA (Pages/ParametersDemo.razor)

<!-- Không truyền Count -->
<ParamChild Title="Chỉ truyền Title (Count sẽ default)" />
        
CON (Components/ParamChild.razor)

<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áukhô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].

ParamChildCascading
Theme (from Cascading): Light
Xem code
CHA (Pages/ParametersDemo.razor)

<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";
}
        
CON (Components/ParamChildCascading.razor)

<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.

ParamChildWithAttrs: Component nhận attrs
Xem code
CHA (Pages/ParametersDemo.razor)

<ParamChildWithAttrs class="border p-2 rounded"
style="margin-top:8px;"
data-test="abc"
Text="Component nhận attrs" />
        
CON (Components/ParamChildWithAttrs.razor)

<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
Gọi thẳng không qua InvokeAsync

// 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; }
}
        
InvokeAsync (KHÔNG đối số – CON xử lý trước)

// 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();
    }
}
        
InvokeAsync<T> (CÓ đối số – CON gửi dữ liệu)

// 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);
    }
}
        
async Task (CON await InvokeAsync chờ CHA xử lý xong)

// 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

    }
}
        
Callback động (EventCallbackFactory) + kiểm tra HasDelegate

// 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.

Card Title

Đây là nội dung truyền từ Parent xuống.

Xem code
CHA (Pages/ParametersDemo.razor)

<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;
}
        
CON (Components/ParamChildContent.razor)

<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.

Alice (Age: 22)
Bob (Age: 30)
Charlie (Age: 27)

SelectedUser: none

Xem code
Models/User.cs

namespace BlazorSample.Models;

public record User(string Name, int Age);
        
CHA (Pages/ParametersDemo.razor)

<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";
}
        
CON (Components/ParamChildTemplate.razor)
@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
CHA (Pages/ParametersDemo.razor)

<ParamTwoWay @bind-Value="TwoWayText" />
<p><b>TwoWayText:</b> @TwoWayText</p>

@code {
private string? TwoWayText = "hello";
}
        
CON (Components/ParamTwoWay.razor)

<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;
    }
}
    


An error has occurred. This application may no longer respond until reloaded. Reload 🗙
Web hosting by Somee.com