.NET 9 Blazor: Xây dựng Ứng dụng Full-Stack với Blazor United năm 2026
Khám phá kiến trúc Blazor United trong .NET 9 — nền tảng hợp nhất Static SSR, Blazor Server và WebAssembly trong một dự án duy nhất. Bài viết hướng dẫn thực hành với render modes, streaming rendering, constructor injection và các kỹ thuật tối ưu hiệu suất cho production.

Trong nhiều năm, cộng đồng .NET phải đối mặt với một lựa chọn khó khăn khi xây dựng ứng dụng web tương tác: Blazor Server với khả năng phản hồi nhanh nhưng phụ thuộc kết nối mạng, hoặc Blazor WebAssembly với trải nghiệm client-side mượt mà nhưng thời gian tải ban đầu chậm. Với .NET 9, Microsoft đã chấm dứt cuộc tranh luận này bằng Blazor United — một kiến trúc cho phép kết hợp tất cả các mô hình render trong cùng một ứng dụng.
Blazor United không chỉ đơn thuần gộp hai hosting models lại với nhau. Nền tảng này giới thiệu một cách tiếp cận hoàn toàn mới cho phát triển web full-stack bằng C#, nơi mỗi trang, thậm chí mỗi component, có thể chọn chiến lược render phù hợp nhất. Một trang marketing có thể sử dụng Static SSR để tối ưu SEO, trong khi dashboard quản trị cùng ứng dụng chạy trên Interactive Server để cập nhật dữ liệu thời gian thực, và module chat sử dụng WebAssembly để hoạt động độc lập khỏi server.
Bài viết này trình bày chi tiết cách xây dựng ứng dụng full-stack với Blazor United, bao gồm các kỹ thuật quan trọng như streaming rendering, constructor injection, adaptive components, và AOT compilation — tất cả thông qua các ví dụ mã nguồn thực tế có thể áp dụng ngay vào dự án production.
Blazor United là kiến trúc hợp nhất trong .NET 8/9 cho phép sử dụng đồng thời Static SSR, Interactive Server (SignalR) và Interactive WebAssembly trong cùng một dự án Blazor Web App. Thay vì chọn một hosting model duy nhất cho toàn bộ ứng dụng, lập trình viên có thể chỉ định render mode ở cấp trang hoặc component. .NET 9 bổ sung thêm streaming rendering nâng cao, constructor injection, RendererInfo API và cải thiện đáng kể hiệu suất AOT compilation cho WebAssembly.
Khởi tạo dự án Blazor Web App với .NET 9
Bước đầu tiên là cài đặt .NET 9 SDK và tạo dự án sử dụng template blazorweb. Đây là template mới thay thế cho blazorserver và blazorwasm trước đây, được thiết kế để hỗ trợ đầy đủ kiến trúc Blazor United ngay từ đầu.
# Create a new Blazor Web App with all render modes enabled
dotnet new blazorweb -n FullStackApp --interactivity Auto --all-interactive false
cd FullStackApp
dotnet runTham số --interactivity Auto kích hoạt chế độ tự động chuyển đổi giữa Server và WebAssembly, mang lại trải nghiệm tốt nhất cho người dùng cuối. Tham số --all-interactive false thiết lập mặc định là Static SSR cho toàn bộ ứng dụng — chỉ những trang hoặc component được đánh dấu rõ ràng mới sử dụng interactive mode. Đây là chiến lược được khuyến nghị vì nó buộc lập trình viên suy nghĩ có chủ đích về nhu cầu tương tác của từng phần giao diện.
Sau khi chạy lệnh, cấu trúc dự án bao gồm thư mục Components chứa các Razor components, wwwroot cho tài nguyên tĩnh, và Program.cs — nơi đăng ký services và cấu hình middleware. Điểm quan trọng trong Program.cs là ba phương thức: AddRazorComponents() cho SSR cơ bản, AddInteractiveServerComponents() cho SignalR, và AddInteractiveWebAssemblyComponents() cho WASM. Middleware MapRazorComponents<App>() kết nối tất cả lại với nhau.
Khi truy cập https://localhost:5001, View Source sẽ hiển thị HTML đầy đủ — khác biệt hoàn toàn so với Blazor WebAssembly truyền thống vốn chỉ trả về một thẻ <div> rỗng. Đây là nền tảng cho khả năng SEO vượt trội của Blazor United.
Các Render Modes trong Blazor United
Việc hiểu rõ từng render mode và áp dụng đúng ngữ cảnh là yếu tố quyết định chất lượng của một ứng dụng Blazor United. Bảng sau tóm tắt bốn render modes chính:
| Render Mode | Hosting | Interactivity | Best For | |---|---|---|---| | Static SSR | Server | None | Marketing pages, docs, SEO content | | Interactive Server | Server via SignalR | Full | Dashboards, CRUD, forms | | Interactive WebAssembly | Browser via WASM | Full | Offline, heavy client-side logic | | Interactive Auto | Server then WASM | Full | Best of both — fast load + independence |
Static SSR tạo ra HTML thuần túy trên server cho mỗi HTTP request, không kèm theo bất kỳ JavaScript runtime hay WebSocket connection nào. TTFB (Time to First Byte) ở mức thấp nhất, lý tưởng cho trang chính sách bảo mật, điều khoản sử dụng, blog posts, hoặc trang giới thiệu sản phẩm. Hạn chế duy nhất là không thể xử lý sự kiện phía client — mọi tương tác cần form submission hoặc navigation truyền thống.
Interactive Server thiết lập kết nối SignalR persistent giữa trình duyệt và server. Mọi thao tác của người dùng — click, nhập liệu, scroll — được truyền qua WebSocket, server xử lý logic và gửi về DOM diff để cập nhật giao diện. Mode này phù hợp cho các trang cần truy cập database liên tục mà không muốn xây dựng API riêng, như dashboard phân tích, form nhập liệu phức tạp, hoặc trang quản trị nội dung.
Interactive WebAssembly tải toàn bộ .NET runtime và assemblies xuống trình duyệt, cho phép code C# chạy trực tiếp phía client. Sau lần tải đầu tiên (khoảng 2-3MB compressed), ứng dụng hoạt động hoàn toàn độc lập khỏi server, hỗ trợ offline mode và phản hồi tức thời. Phù hợp cho công cụ tính toán, trình soạn thảo ảnh, hoặc game browser-based.
Interactive Auto kết hợp ưu điểm của cả hai: lần truy cập đầu sử dụng Server mode để tải nhanh, đồng thời tải ngầm WASM runtime ở background. Từ lần truy cập thứ hai trở đi, ứng dụng tự động chuyển sang WebAssembly. Đây là render mode được khuyến nghị mặc định cho hầu hết các trang ứng dụng.
@page "/dashboard"
@rendermode InteractiveServer
<PageTitle>Dashboard</PageTitle>
<h1>Real-Time Metrics</h1>
<!-- This component renders interactively via SignalR -->
<MetricsChart />
<!-- Static content below renders as plain HTML -->
<footer>Updated every 5 seconds</footer>Directive @rendermode InteractiveServer áp dụng cho toàn bộ trang Dashboard. Component con MetricsChart kế thừa mode này và có thể nhận dữ liệu cập nhật theo thời gian thực qua SignalR. Phần footer không cần interactivity nên vẫn render như HTML tĩnh, giảm thiểu overhead không cần thiết.
Một tính năng đáng chú ý trong .NET 9 là RendererInfo API, cho phép component tự phát hiện môi trường render hiện tại và điều chỉnh giao diện phù hợp:
@if (RendererInfo.IsInteractive)
{
<button @onclick="HandleClick">Interactive Action</button>
}
else
{
<a href="/fallback">Static Fallback Link</a>
}
@code {
private void HandleClick()
{
// Runs only in interactive modes (Server or WASM)
}
}Pattern adaptive component này cho phép xây dựng shared UI libraries hoạt động chính xác trong mọi render mode. Thay vì crash hoặc hiển thị nút không phản hồi trong Static SSR, component tự động cung cấp fallback phù hợp — trong trường hợp này là một hyperlink thay cho button. Đây là nền tảng của progressive enhancement trong Blazor United.
Streaming Rendering cho trang có nhiều dữ liệu bất đồng bộ
Streaming Rendering giải quyết một vấn đề phổ biến: người dùng phải đợi toàn bộ dữ liệu tải xong trước khi thấy bất kỳ nội dung nào trên trang. Với attribute [StreamRendering], Blazor gửi ngay phần HTML đã sẵn sàng (layout, navigation, placeholder) đến trình duyệt, sau đó tiếp tục stream phần còn lại khi dữ liệu async hoàn thành.
@page "/products"
@attribute [StreamRendering]
<PageTitle>Product Catalog</PageTitle>
@if (products is null)
{
<p>Loading products...</p>
}
else
{
<div class="product-grid">
@foreach (var product in products)
{
<ProductCard Item="@product" />
}
</div>
}
@code {
private List<Product>? products;
// Data loads asynchronously; streaming pushes updates to the browser
protected override async Task OnInitializedAsync()
{
products = await ProductService.GetAllAsync();
}
}Khi người dùng mở /products, trình duyệt nhận ngay header và placeholder "Loading products..." trong vài chục milliseconds. Khi ProductService.GetAllAsync() trả về kết quả (có thể sau 200-500ms tùy database), Blazor stream HTML mới xuống và tự động thay thế placeholder bằng product grid thực tế. Toàn bộ quá trình diễn ra trên một HTTP response duy nhất, không cần JavaScript để polling hoặc fetch thêm dữ liệu.
Kỹ thuật này phát huy hiệu quả tối đa khi kết hợp với HTTP/2 hoặc HTTP/3, nhờ khả năng multiplexing và header compression của các giao thức hiện đại. Lưu ý rằng streaming rendering chỉ áp dụng cho Static SSR — các interactive modes đã có cơ chế cập nhật DOM riêng thông qua SignalR hoặc WASM runtime, nên không cần streaming.
Một chiến lược hiệu quả là tách trang thành nhiều component con, mỗi component có async operation riêng biệt. Ví dụ, một trang dashboard có thể chứa RevenueChart, RecentOrders, và SystemAlerts — ba component stream độc lập, hiển thị ngay khi dữ liệu tương ứng sẵn sàng thay vì đợi tất cả cùng lúc.
Constructor Injection trong Blazor Components
Blazor truyền thống sử dụng [Inject] attribute để inject dependencies vào component properties. .NET 9 mở rộng khả năng này với constructor injection — pattern quen thuộc trong toàn bộ hệ sinh thái .NET, giúp code rõ ràng hơn, dễ kiểm thử hơn, và tương thích tốt hơn với các công cụ phân tích tĩnh.
@page "/orders"
@rendermode InteractiveServer
<h2>Order History</h2>
@if (orders is not null)
{
<table>
<thead>
<tr><th>Order ID</th><th>Date</th><th>Total</th></tr>
</thead>
<tbody>
@foreach (var order in orders)
{
<tr>
<td>@order.Id</td>
<td>@order.PlacedAt.ToShortDateString()</td>
<td>@order.Total.ToString("C")</td>
</tr>
}
</tbody>
</table>
}
@code {
// Constructor injection via primary constructor
[Inject] public required IOrderService OrderService { get; set; }
[Inject] public required NavigationManager Nav { get; set; }
private List<Order>? orders;
protected override async Task OnInitializedAsync()
{
orders = await OrderService.GetRecentOrdersAsync();
}
}Keyword required trong C# 11 đảm bảo compiler cảnh báo nếu dependency không được gán giá trị trước khi sử dụng, loại bỏ các lỗi NullReferenceException tiềm ẩn. Với primary constructor syntax của C# 12, code có thể ngắn gọn hơn nữa, nhưng pattern [Inject] required vẫn là cách tiếp cận phổ biến nhất trong Blazor components do tương thích ngược.
Khi viết unit tests, mỗi dependency có thể được mock một cách độc lập. IOrderService được mock để trả về dữ liệu test, NavigationManager được mock để kiểm tra navigation logic — tất cả đều rõ ràng nhờ constructor injection thay vì magic property injection.
Services cần được đăng ký trong Program.cs với lifecycle phù hợp: AddScoped cho Blazor Server (mỗi circuit một instance), AddSingleton cho shared caches, và AddTransient cho lightweight stateless services.
Sẵn sàng chinh phục phỏng vấn .NET?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Interactive Auto Mode cho trải nghiệm Progressive Enhancement
Interactive Auto là render mode thể hiện rõ nhất triết lý của Blazor United: cung cấp trải nghiệm tốt nhất có thể tại mọi thời điểm, tự động nâng cấp khi điều kiện cho phép. Không cần thêm configuration hay client-side logic — Blazor runtime xử lý toàn bộ quá trình chuyển đổi.
@page "/chat"
@rendermode InteractiveAuto
<h2>Live Chat</h2>
<div class="chat-messages">
@foreach (var msg in messages)
{
<div class="message">@msg.Author: @msg.Text</div>
}
</div>
<input @bind="newMessage" @onkeydown="HandleKey" placeholder="Type a message..." />
@code {
[Inject] public required IChatService ChatService { get; set; }
private List<ChatMessage> messages = new();
private string newMessage = string.Empty;
protected override async Task OnInitializedAsync()
{
messages = await ChatService.GetRecentMessagesAsync();
}
private async Task HandleKey(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(newMessage))
{
await ChatService.SendAsync(newMessage);
newMessage = string.Empty;
}
}
}Quy trình hoạt động của Auto mode diễn ra như sau: khi người dùng mới truy cập /chat lần đầu tiên, Blazor sử dụng Server mode — kết nối SignalR được thiết lập ngay lập tức, giao diện tương tác đầy đủ trong vài trăm milliseconds. Đồng thời, trình duyệt bắt đầu tải WASM runtime ở background mà không ảnh hưởng đến trải nghiệm hiện tại. Khi người dùng navigate đến trang khác rồi quay lại, hoặc khi mở lại ứng dụng trong phiên tiếp theo, Blazor tự động phát hiện WASM runtime đã sẵn sàng và chạy component hoàn toàn phía client.
Sự chuyển đổi này hoàn toàn trong suốt — không có flash, không cần reload, state có thể preserve thông qua PersistentComponentState. Người dùng không nhận ra sự khác biệt giữa hai modes, nhưng ứng dụng đã giảm tải đáng kể cho server trong các phiên sử dụng tiếp theo.
Xử lý Reconnection và đảm bảo Resilience
Với Interactive Server mode, kết nối SignalR là yếu tố sống còn. Mất kết nối — do chuyển mạng, tạm ngưng tab, hoặc server restart — có thể làm gián đoạn trải nghiệm người dùng nghiêm trọng nếu không được xử lý đúng cách. .NET 9 cung cấp API cấu hình chi tiết cho reconnection logic.
Blazor.start({
circuit: {
reconnectionOptions: {
retryIntervalMilliseconds: (retryNumber) => {
// Exponential backoff: 200ms, 400ms, 800ms, max 30s
return Math.min(200 * Math.pow(2, retryNumber), 30000);
},
maxRetries: 15
}
}
});Cấu hình exponential backoff bắt đầu với 200ms và tăng gấp đôi sau mỗi lần thử, tối đa 30 giây. Chiến lược này cân bằng hai mục tiêu: reconnect nhanh nhất có thể khi mất kết nối ngắn (người dùng chuyển tab, mạng wifi chập chờn), và không tạo áp lực không cần thiết lên server khi có sự cố kéo dài. Giới hạn 15 lần retry cho phép khoảng 2-3 phút cố gắng kết nối lại trước khi từ bỏ.
Blazor hiển thị UI indicator tự động khi đang reconnect, cho phép người dùng biết trạng thái kết nối. Nếu circuit bị disposed hoàn toàn (ví dụ sau server restart), Blazor tự động reload trang để thiết lập circuit mới. Để bảo toàn state qua quá trình này, service PersistentComponentState cho phép serialize dữ liệu quan trọng vào HTML trước khi circuit đóng, sau đó restore khi trang tải lại — tương tự cơ chế dehydration/rehydration trong các framework JavaScript hiện đại.
Static SSR với Selective Interactivity
Một trong những pattern kiến trúc mạnh nhất của Blazor United là kết hợp Static SSR cho phần lớn nội dung với interactive islands cho các tính năng cần tương tác. Cách tiếp cận này tối ưu hóa performance và SEO, đồng thời vẫn cung cấp trải nghiệm phong phú khi cần thiết.
@page "/privacy"
@attribute [ExcludeFromInteractiveRouting]
<PageTitle>Privacy Policy</PageTitle>
<!-- This page always renders as static HTML -->
<!-- Even if global interactivity is enabled -->
<article>
<h1>Privacy Policy</h1>
<p>Last updated: April 2026</p>
<!-- Content -->
</article>Attribute [ExcludeFromInteractiveRouting] là công cụ quan trọng để đảm bảo một trang luôn render dưới dạng static HTML, kể cả khi toàn bộ ứng dụng đã bật interactive routing. Khi người dùng navigate từ một interactive page (ví dụ Dashboard) đến Privacy page, Blazor sẽ thực hiện full page navigation thay vì client-side routing, đảm bảo trang nhận được HTML thuần túy từ server.
Pattern "islands of interactivity" mở ra khả năng thiết kế tinh vi: một trang sản phẩm có thể render description và hình ảnh dưới dạng static HTML (tốt cho SEO), nhưng nút "Thêm vào giỏ hàng" là một interactive component. Một bài blog có toàn bộ nội dung static, nhưng phần bình luận cuối trang sử dụng Interactive Server để cho phép gửi comment theo thời gian thực. Sự kết hợp này mang lại hiệu suất vượt trội so với việc interactive hóa toàn bộ trang.
Tối ưu hiệu suất với AOT Compilation
Ahead-of-Time (AOT) Compilation chuyển đổi mã IL (Intermediate Language) của .NET thành mã WebAssembly gốc trong quá trình build, thay vì thông dịch tại runtime. Kết quả là hiệu suất thực thi tăng 2-4 lần cho các tác vụ tính toán nặng, và thời gian khởi động giảm đáng kể do không cần khởi tạo JIT/interpreter.
# Publish with AOT for production
dotnet publish -c Release -p:RunAOTCompilation=trueAOT đặc biệt hiệu quả cho các ứng dụng có nhiều tính toán phía client: xử lý JSON lớn, mã hóa/giải mã, thuật toán tìm kiếm và sắp xếp, hoặc xử lý hình ảnh. Benchmark cho thấy parsing một JSON payload 1MB giảm từ 800ms (interpreter) xuống 200ms (AOT) trên thiết bị di động trung bình.
Trade-off cần cân nhắc là kích thước bundle tăng khoảng 1.5-2x do mã WASM gốc lớn hơn mã IL. Tuy nhiên, với brotli compression trên CDN và HTTP/2 push, overhead thực tế trên đường truyền thường dưới 500KB — một khoản đầu tư hợp lý cho mức cải thiện performance đạt được. Thời gian build cũng tăng đáng kể (từ 30 giây lên 5-10 phút), nên AOT chỉ nên bật cho production builds, không phải development.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Kết luận
Blazor United trong .NET 9 đánh dấu sự trưởng thành của nền tảng phát triển web full-stack bằng C#. Kiến trúc hợp nhất này cung cấp sự linh hoạt và hiệu suất ngang tầm với các framework JavaScript hàng đầu, đồng thời mang lại lợi thế đặc trưng của hệ sinh thái .NET: type safety, tooling mạnh mẽ, và khả năng tái sử dụng code giữa frontend và backend.
Những điểm chính cần ghi nhớ:
- Static SSR là lựa chọn mặc định cho trang nội dung, trang SEO, và trang pháp lý — tải nhanh nhất, không cần JavaScript runtime
- Interactive Server phù hợp cho dashboard, form phức tạp, và các trang cần truy cập database thường xuyên mà không muốn xây dựng REST API riêng
- Interactive WebAssembly dành cho các tính năng cần hoạt động offline hoặc yêu cầu xử lý nặng phía client
- Interactive Auto nên là render mode mặc định cho các trang ứng dụng — tải nhanh lần đầu, tự động nâng cấp lên WASM cho các lần sau
- Streaming Rendering cải thiện đáng kể perceived performance cho trang có nhiều async operations
- Constructor Injection giúp code rõ ràng và dễ kiểm thử, tương thích với toàn bộ hệ sinh thái .NET
- Cấu hình Reconnection là bắt buộc cho production apps sử dụng Interactive Server mode
- AOT Compilation tăng hiệu suất 2-4 lần cho WebAssembly, nên bật cho mọi production build
- Adaptive Components với
RendererInfoAPI đảm bảo progressive enhancement tự nhiên, ứng dụng hoạt động tốt trong mọi điều kiện
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Entity Framework Core: Tối Ưu Hiệu Năng và Các Phương Pháp Tốt Nhất Năm 2026
Hướng dẫn toàn diện về tối ưu hiệu năng Entity Framework Core 10 trên .NET 10. Tìm hiểu AsNoTracking, compiled queries, batch updates, split queries và toán tử LeftJoin.

Clean Architecture với .NET: Hướng Dẫn Thực Tiễn
Làm chủ Clean Architecture trong .NET với C#. Tìm hiểu các nguyên tắc SOLID, phân tách lớp và mẫu triển khai cho ứng dụng dễ bảo trì.

Câu Hỏi Phỏng Vấn C# và .NET: Hướng Dẫn Đầy Đủ 2026
17 câu hỏi phỏng vấn C# và .NET thường gặp nhất. LINQ, async/await, dependency injection, Entity Framework và các phương pháp tốt nhất với đáp án chi tiết.