# .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. - Published: 2026-04-26 - Updated: 2026-04-26 - Author: SharpSkill - Tags: dotnet, blazor, aspnet-core, webassembly, full-stack - Reading time: 8 min --- 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. > **Tóm tắt nhanh về Blazor United** > > 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. ```bash # Create a new Blazor Web App with all render modes enabled dotnet new blazorweb -n FullStackApp --interactivity Auto --all-interactive false cd FullStackApp dotnet run ``` Tham 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()` 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ẻ `
` 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. ```csharp // Components/Pages/Dashboard.razor @page "/dashboard" @rendermode InteractiveServer Dashboard

Real-Time Metrics

Updated every 5 seconds
``` 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: ```csharp // Components/Shared/AdaptiveComponent.razor @if (RendererInfo.IsInteractive) { } else { Static Fallback Link } @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. ```csharp // Components/Pages/Products.razor @page "/products" @attribute [StreamRendering] Product Catalog @if (products is null) {

Loading products...

} else {
@foreach (var product in products) { }
} @code { private List? 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. ```csharp // Components/Pages/OrderHistory.razor @page "/orders" @rendermode InteractiveServer

Order History

@if (orders is not null) { @foreach (var order in orders) { }
Order IDDateTotal
@order.Id @order.PlacedAt.ToShortDateString() @order.Total.ToString("C")
} @code { // Constructor injection via primary constructor [Inject] public required IOrderService OrderService { get; set; } [Inject] public required NavigationManager Nav { get; set; } private List? 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. ## 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. ```csharp // Components/Pages/Chat.razor @page "/chat" @rendermode InteractiveAuto

Live Chat

@foreach (var msg in messages) {
@msg.Author: @msg.Text
}
@code { [Inject] public required IChatService ChatService { get; set; } private List 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. ```javascript // wwwroot/app.js — Custom reconnection configuration 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. ```csharp // Components/Pages/Privacy.razor @page "/privacy" @attribute [ExcludeFromInteractiveRouting] Privacy Policy

Privacy Policy

Last updated: April 2026

``` 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. ```bash # Publish with AOT for production dotnet publish -c Release -p:RunAOTCompilation=true ``` AOT đặ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. ## 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 `RendererInfo` API đảm bảo progressive enhancement tự nhiên, ứng dụng hoạt động tốt trong mọi điều kiện --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/dotnet/dotnet-9-blazor-united-full-stack-development