Blazor'da .NET 8'den .NET 11'e Gelenler: Web App Modeli, RendererInfo, PersistentState, NotFound ve .NET 11 Yenilikleri
.NET 8 Blazor'u component başına render mode'lu tek bir Web App modeline taşıdı; .NET 9 ve 10 bu modelin boşluklarını kapattı, .NET 11 ise Static SSR'ı, URL tabanlı QuickGrid'i ve circuit auto-pause'u getirdi. Bookstore panelini .NET 11'e yükselttik: [ValidatableType] için bir using, yeni BL0013/BL0016 analyzer'ları, UseWebAssemblyDebugging'in kalkması ve .NET 10 LTS ile .NET 11 STS'nin aynı gün bitmesi.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.404, ASP.NET Core 10.0.15) ile derlenip denenmiştir; .NET 11 bölümleri .NET 11 SDK 11.0.100 ve 11.0.0 paketleriyle (Microsoft.AspNetCore.Components.Server.AutoPause 11.0.0 dahil) denenmiş, testlerde bUnit 2.15.2 kullanılmış, tarayıcı davranışı Playwright 1.65'in headless Chromium'uyla gözlenmiştir.
Önce Tahmin Et
Serinin on iki yazısında sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) yönetim panelini .NET 10 üzerinde kurduk. Bu son yazıda geriye bakıyoruz: Blazor .NET 8'den bu yana nasıl değişti, .NET 11 neler getirdi ve panel .NET 11'e yükseltildiğinde ne oluyor. Önce üç soru. On iki yazı boyunca uyarısız derlenen panel, yalnız TFM (Target Framework Moniker; projenin hedeflediği .NET sürümünün kısa adı) ve paket sürümleri değişince derlenir mi? Yazarlar sayfasında ikinci sayfaya geçip tarayıcının geri düğmesine basınca .NET 10'da ve .NET 11'de nereye dönersin? .NET 10 LTS (Long Term Support; uzun süreli destek) ve .NET 11 STS (Standard Term Support; standart süreli destek) sürümü; hangisinin desteği daha önce biter?
.NET 8: Blazor Web App ve Component Başına Render Mode

.NET 8'den önce Blazor iki ayrı hosting modeliydi: Blazor Server ve Blazor WebAssembly. Seçimi proje açarken yapıyordun ve bütün uygulama o modelle çalışıyordu. .NET 8 bunları tek bir Blazor Web App template'inde birleştirdi ve render mode'u component başına bir karar yaptı: Static SSR (Static Server-Side Rendering; sayfanın her istekte sunucuda düz HTML olarak üretilmesi), InteractiveServer, InteractiveWebAssembly ve InteractiveAuto. Serinin 01–06 arası bu modelin üstüne kurulu. Aynı sürümde streaming rendering, enhanced navigation ve enhanced form'lar, [SupplyParameterFromForm] ile form binding, root seviyesinde cascading value'lar, QuickGrid ve Identity'nin Blazor arayüzü geldi. WebAssembly tarafında Jiterpreter ve assembly'lerin Webcil biçiminde .wasm olarak paketlenmesi de .NET 8'in işi; 12'de ölçtüğümüz dosyalar bu biçimde.
.NET 9: RendererInfo, Auth State Serialization ve Static Asset'ler
.NET 9 modeli değiştirmedi, eksiklerini kapattı. RendererInfo ve AssignedRenderMode ile component çalışma anında nerede ve hangi modda çalıştığını öğrenebiliyor (04 ve 09'da kullandık). AddAuthenticationStateSerialization ve AddAuthenticationStateDeserialization, .NET 8 template'indeki elle yazılmış persisting provider'ların yerini aldı; 11'deki WebAssembly claim'leri bu yolla geldi. Yeniden bağlanma deneyimi iyileşti, global interaktif bir uygulamada tek tek sayfaları Static SSR bırakmak için [ExcludeFromInteractiveRouting] geldi, component'lerde constructor injection mümkün oldu. Statik dosyalar tarafında MapStaticAssets, ImportMap ve fingerprint'li adlar .NET 9'dan; 12'deki bir yıllık cache header'ı ve CSP (Content Security Policy; tarayıcıya hangi script'lerin çalışabileceğini söyleyen header) nonce'u bunların üstünde duruyor.
.NET 10: PersistentState, NotFound, ReconnectModal ve Diğerleri
Serinin tabanı olan .NET 10'un Blazor tarafındaki yenilikleri yazılara dağıldı: [PersistentState] ve circuit state persistence (06), NavigationManager.NotFound() ve NotFoundPage (05), nested validation için AddValidation ve [ValidatableType] (07), InvokeConstructorAsync, GetValueAsync ve SetValueAsync (10), template'teki passkey desteği (11), ReconnectModal, ResourcePreloader, dotnet.js'e gömülen boot configuration ve Blazor meter'ları (12). Breaking change'lerden ikisi bizi doğrudan etkiledi: WebAssembly'de HttpClient'ın response streaming'i varsayılan oldu (09'daki senkron okuma hatası) ve cookie authentication bilinen API endpoint'lerinde login'e yönlendirmek yerine 401 dönmeye başladı (11). Template'ten <NotFound> fragment'i ve BlazorCacheBootResources property'si kalktı.
.NET 11: Static SSR, QuickGrid ve Circuit Tarafı
.NET 11'in Blazor yeniliklerinin büyük kısmı Static SSR'ı interaktivite olmadan daha kullanışlı yapıyor: TempData ve [SupplyParameterFromSession] (05'te TempData ile mesaj taşımayı denemiştik), Static SSR form'larında varsayılan client-side validation (07'de bir alandan çıkınca mesajın POST olmadan göründüğünü gördük), async validation ve bir component alt ağacının çıktısını cache'leyen yeni bir component. QuickGrid sayfalama ve sıralama state'ini URL'de tutuyor. Panelin yazarlar sayfasında aynı denemeyi iki sürümde yaptım:
.NET 10: next-page control is <button>, first author Aldous Huxley -> F. Scott Fitzgerald, URL /authors
after browser Back: URL /, first author (no grid on this page)
.NET 11: next-page control is <a>, first author Aldous Huxley -> F. Scott Fitzgerald, URL /authors?page=2
after browser Back: URL /authors, first author Aldous Huxley
İkinci sorunun cevabı: .NET 10'da paginator bir <button>, sayfa değişince adres aynı kalıyor ve geri düğmesi kullanıcıyı yazarlar sayfasından çıkarıyor. .NET 11'de kontrol bir link, adres ?page=2 oluyor, geri düğmesi birinci sayfaya dönüyor ve bağlantı paylaşılabiliyor. Bu bir breaking change: düğmeyi hedefleyen CSS ya da testler güncellenmeli, aynı sayfada iki grid varsa QueryParameterNameOptions ile ayrı parametre adları vermek gerekiyor.
Circuit tarafında iki yenilik 12'deki bellek hesabını değiştiriyor. Birincisi auto-pause: sekme bir süre gizli kalınca circuit duraklatılıyor ve sunucudaki belleği ile bağlantısı bırakılıyor. Ayrı bir paketle geliyor:
// dotnet add src/Bookstore.Admin package Microsoft.AspNetCore.Components.Server.AutoPause --version 11.0.0
using Microsoft.AspNetCore.Components;
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(options => options.ContentSecurityFrameAncestorsPolicy = "'none'")
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Bookstore.Admin.Client._Imports).Assembly)
.WithBrowserOptions(options => options.AddAutoPause(p => p.HiddenDelay = TimeSpan.FromSeconds(5)));
Headless Chromium sekmeleri hiç gizlemediği için Page Visibility API'yi elle "hidden" yapıp visibilitychange event'ini gönderdim:
0.2 s tab hidden (visibilitychange)
5.2 s components-reconnect-state-changed: show
5.2 s components-reconnect-state-changed: paused
9.2 s tab visible again (visibilitychange)
9.2 s components-reconnect-state-changed: hide
13.2 s rows: 20
Beş saniye sonra circuit duraklatıldı, sekme görünür olunca kendiliğinden devam etti ve stok listesi yerindeydi. 06'da Blazor.pauseCircuit()'i elle çağırmıştık; .NET 11'de sunucu da Circuit.RequestCircuitPauseAsync ile istemciden duraklamasını isteyebiliyor, örneğin bir deploy öncesinde. İkincisi authentication refresh: SignalR bağlantısı authentication'ını yenilediğinde circuit yeni ClaimsPrincipal'ı alıyor ve AuthorizeView yeniden render ediliyor. 11'de açık sekmenin eski rolle kaldığını söylemiştik; .NET 11 bu boşluğu kapatıyor, bu yazıda ayrıca denemedim. Bunlara ek olarak WebAssembly'de IHostedService ve IConfiguration'da ortam değişkenleri, artık her öğenin aynı yükseklikte olduğunu varsaymayan bir Virtualize ve ScrollToItemAsync, DisplayName, BasePath ve EnvironmentView component'leri ve deneysel AI component'leri var.
Paneli .NET 11'e Yükseltmek
İlk denemeyi 12'nin koduyla yaptım: global.json'da SDK 11.0.100, net11.0 ve bütün Microsoft.AspNetCore.* ve Microsoft.EntityFrameworkCore.* paketlerinde 11.0.0. bUnit 2.15.2 net11.0'ı zaten destekliyor. İlk build:
$ dotnet build Bookstore.slnx
src/Bookstore.Admin/Components/Catalog/NewAuthorForm.cs(7,2): error CS0246: The type or namespace name 'ValidatableType' could not be found (are you missing a using directive or an assembly reference?)
Birinci sorunun cevabı: hayır. 07'de .NET 10 SDK'sının web projelerinde ValidatableTypeAttribute'un deneysel olmayan bir kopyasını ürettiğini ve global using'le eklediğini görmüştük; .NET 11'de attribute artık deneysel değil, kopya da üretilmiyor. Çözüm 07'de derleme hatasına yol açan satırın kendisi: using Microsoft.Extensions.Validation;. Bu satır iki sürümde birden çalışmadığı için yükseltmenin bir parçası olarak ekleniyor. İkinci build:
$ dotnet build Bookstore.slnx
src/Bookstore.Admin/Components/Books/CopyIsbnButton.razor(17,26): error BL0016: JS interop call 'InvokeAsync' is not guarded with a try/catch block.
src/Bookstore.Admin/Components/Reports/SalesChart.razor(28,24): error BL0016: JS interop call 'InvokeAsync' is not guarded with a try/catch block.
src/Bookstore.Admin/Components/Reports/SalesChart.razor(29,30): error BL0016: JS interop call 'GetValueAsync' is not guarded with a try/catch block.
...
src/Bookstore.Admin/Program.cs(55,5): error ASPDEPR011: 'WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging(IApplicationBuilder)' is obsolete: 'UseWebAssemblyDebugging is obsolete and should be removed along with any inspectUri configurations. Blazor WebAssembly debugging is now launched directly by Visual Studio and Visual Studio Code.' (https://aka.ms/aspnet/deprecate/011)
src/Bookstore.Admin/Services/BookstoreApiClient.cs(97,43): error BL0013: 'BookstoreApiClient' calls GetAuthenticationStateAsync on AuthenticationStateProvider without subscribing to the AuthenticationStateChanged event. This may result in using stale authentication state.
Bunlar hata değil uyarı; projede TreatWarningsAsErrors açık olduğu için build'i durdurdular. BL0016 yeni bir analyzer ve try/catch içinde olmayan JS interop çağrılarını işaretliyor; dokuz çağrının hepsi bizim kodumuzdaydı. BL0013, AuthenticationStateProvider'dan kullanıcıyı alıp AuthenticationStateChanged'e abone olmayan service'leri işaretliyor; .NET 11'de kullanıcı circuit içinde değişebildiği için saklanan bir kullanıcı bayatlayabilir. ASPDEPR011, UseWebAssemblyDebugging'in artık gereksiz olduğunu söylüyor: Visual Studio ve Visual Studio Code WebAssembly debugging'ini kendileri başlatıyor, launchSettings.json'daki inspectUri satırları da silinmeli. Bu iki analyzer'ın Microsoft Learn'de 2026-11-15 itibarıyla kendi sayfası yok; yukarıdaki açıklamalar derleyicinin mesajlarından.
Yükseltmeden Önce .NET 10'da Yapılabilecekler
Bu yazının commit'i panelin .NET 10'da kalan ama .NET 11'e hazır hâli: iki sürümde de doğru olan değişiklikler önce yapıldı. BL0016'nın işaret ettiği sorun gerçek. Chart.js'in yüklenemediği bir durumu (eksik dosya, dosyayı sunan sunucunun bir kesintisi ya da bir CSP engeli) 12'nin koduyla denedim:
error banner visible: true
chart area says: "Chart.js"
after Refresh: error banner visible: true
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit '…'.
Microsoft.JSInterop.JSException: Failed to fetch dynamically imported module: http://localhost:5037/Components/Reports/SalesChart.san3whzjle.razor.js
TypeError: Failed to fetch dynamically imported module: http://localhost:5037/Components/Reports/SalesChart.san3whzjle.razor.js
OnAfterRenderAsync'te yakalanmayan JSException circuit'i bitirdi; kullanıcı hata şeridini görüyor ve sayfadaki hiçbir düğme çalışmıyor. Grafik component'i artık hatayı kendi içinde tutuyor:
try
{
// Prerendering sırasında JS yok; en erken nokta ilk interaktif render.
module = await JS.InvokeAsync<IJSObjectReference>("import", "./Components/Reports/SalesChart.razor.js");
chartVersion = await JS.GetValueAsync<string>("Chart.version");
chart = await JS.InvokeConstructorAsync("Chart", canvas, new
{
type = "bar",
data = new
{
labels = Sales.Select(s => s.Category),
datasets = new[] { new { label = "Copies sold", data = Sales.Select(s => s.Copies) } }
}
});
selfReference = DotNetObjectReference.Create(this);
await module.InvokeVoidAsync("onBarClick", chart, selfReference);
}
catch (JSException ex)
{
// Başarısız bir import ya da Chart.js hatası circuit'i bitirmek yerine bu component'in içinde kalır.
Logger.LogWarning(ex, "Sales chart could not be created");
loadFailed = true;
}
StateHasChanged();
error banner visible: false
chart area says: "The chart couldn't be loaded. Copies sold per category:"
after Refresh: error banner visible: false
warn: Bookstore.Admin.Components.Reports.SalesChart[0]
Sales chart could not be created
Microsoft.JSInterop.JSException: Failed to fetch dynamically imported module: http://localhost:5037/Components/Reports/SalesChart.san3whzjle.razor.js
Kopyalama düğmesi de aynı şekilde: modül yüklenmezse "Copy failed" yazıyor. Bunu bUnit'le test etmek için copyText çağrısının JSException fırlatmasını kurdum; grafik için aynı testi yazamadım, çünkü bUnit 2.15.2 .NET 10'un GetValueAsync'ini henüz taklit etmiyor:
System.NotImplementedException : The method or operation is not implemented.
API client'ındaki BL0013 bu kodda yanlış alarm: kullanıcı her çağrıda yeniden okunuyor, hiçbir yerde saklanmıyor. Kural kapatıldı ama sebebi yanına yazıldı:
// Her çağrıda yeniden okunur, hiç saklanmaz; yenilenen kullanıcı hemen görülür, BL0013 (.NET 11) burada geçerli değil.
#pragma warning disable BL0013
AuthenticationState state = await authState.GetAuthenticationStateAsync();
#pragma warning restore BL0013
Üçüncü değişiklik .NET 11 template'inden: NavMenu'deki inline onclick collocated bir JS module'üne taşındı.
<script type="module" src="@Assets["Components/Layout/NavMenu.razor.js"]"></script>
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Bookstore Admin</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div id="nav-scrollable" class="nav-scrollable">
// .NET 11 template'indeki gibi mobil menüyü bir tıklamadan sonra kapatır (inline onclick handler yok).
const navScrollable = document.getElementById("nav-scrollable");
const navToggler = document.querySelector(".navbar-toggler");
if (navScrollable && navToggler) {
navScrollable.addEventListener("click", () => navToggler.click());
}
menu open after the toggler: true
menu open after a link click: false
Bunun bir yan faydası 12'deki CSP'den 'unsafe-hashes' ve hash'in kalkması; policy'de artık inline script'e tek izin import map'in nonce'u. Bu üç değişiklikten sonra .NET 11'e geçiş beklenen iki satıra iniyor:
$ dotnet build Bookstore.slnx
src/Bookstore.Admin/Program.cs(55,5): error ASPDEPR011: 'WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging(IApplicationBuilder)' is obsolete: 'UseWebAssemblyDebugging is obsolete and should be removed along with any inspectUri configurations. Blazor WebAssembly debugging is now launched directly by Visual Studio and Visual Studio Code.' (https://aka.ms/aspnet/deprecate/011)
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,3 +2,3 @@
<PropertyGroup>
- <TargetFramework>net10.0</TargetFramework>
+ <TargetFramework>net11.0</TargetFramework>
<Nullable>enable</Nullable>
--- a/src/Bookstore.Admin/Components/Catalog/NewAuthorForm.cs
+++ b/src/Bookstore.Admin/Components/Catalog/NewAuthorForm.cs
@@ -2,2 +2,3 @@ using System.ComponentModel.DataAnnotations;
using Bookstore.Admin.Validation;
+using Microsoft.Extensions.Validation;
--- a/src/Bookstore.Admin/Program.cs
+++ b/src/Bookstore.Admin/Program.cs
@@ -52,7 +52,3 @@ if (app.Environment.IsDevelopment() && app.Configuration.GetValue<bool>("Identit
-if (app.Environment.IsDevelopment())
-{
- app.UseWebAssemblyDebugging();
-}
-else
+if (!app.Environment.IsDevelopment())
{
UseWebAssemblyDebugging ve inspectUri silinince build temiz geçti ve testler .NET 11'de çalıştı:
$ dotnet --version
11.0.100
$ dotnet test tests/Bookstore.Admin.Tests
Test run summary: Passed!
total: 27
failed: 0
succeeded: 27
$ dotnet test tests/Bookstore.Api.Tests -- --filter-class Bookstore.Api.Tests.AuthorizationTests
Test run summary: Passed!
total: 8
failed: 0
succeeded: 8
Yükseltme Kontrol Listesi
Panelde gördüklerimiz ve resmî migration rehberiyle breaking change listesindeki Blazor maddeleri:
- SDK ve TFM:
global.json'da 11.0.100,net11.0,Microsoft.AspNetCore.*veMicrosoft.EntityFrameworkCore.*paketlerinde 11.0.0. [ValidatableType]kullanan dosyalarausing Microsoft.Extensions.Validation;;ASP0029bastırmaları varsa silinebilir.UseWebAssemblyDebuggingveinspectUrisilinir.- Yeni analyzer'lar (
BL0010,BL0013,BL0016)TreatWarningsAsErrorsaçıksa build'i durdurur; uyarıları tek tek değerlendir. NavMenu'deki inline handler module'e taşınır, CSP'deki hash silinir.- QuickGrid link'lere ve URL state'ine geçti; düğmeye bakan CSS ve testler, aynı sayfadaki birden fazla grid kontrol edilir.
- Static SSR endpoint'leri antiforgery kararını middleware'den alıyor;
UseAntiforgery()çağıran uygulamada değişen bir şey yok. - Enhanced navigation ile gelinen sayfalarda preload hint'leri artık üretilmiyor.
- Passkey ile giriş de onaylı hesap ve lockout kurallarına tabi; Identity kullanan uygulamada giriş testleri yeniden çalıştırılır.
- WebAssembly'de ortam değişkenleri
IConfiguration'a da giriyor; elle eklenenAddEnvironmentVariables()artık gereksiz.
Üretimde Dikkat
Üçüncü sorunun cevabı .NET'in yayın sayfasındaki verilerde: .NET 10 LTS ve .NET 11 STS'nin desteği aynı gün, 14 Kasım 2028'de bitiyor. Microsoft STS sürümlerinin desteğini 24 aya çıkardığı için .NET 11'e geçmek bugün destek süresinden bir şey kaybettirmiyor; kaybettirdiği şey, .NET 12'nin beklendiği Kasım 2027'den sonra bir yükseltmeyi daha yapmak zorunda kalmak. Panel gibi her yıl dokunulan bir iç uygulamada .NET 11'in QuickGrid, auto-pause ve authentication refresh yenilikleri bu bedele değebilir; yılda bir kez bakılan bir uygulama için .NET 10'da kalıp .NET 12'yi beklemek daha az iş. Hangi yolu seçersen seç, yükseltmeyi ayrı bir branch'te, testler ve TreatWarningsAsErrors açıkken yap; bu yazıdaki uyarıların hepsi sessiz bir build'de gözden kaçardı.
.NET 11 dokümanlarının bazılarında hâlâ preview notları var. GA (General Availability; genel kullanıma sunulan sürüm) sonrasında release notes'un tarihine ve breaking change sayfalarındaki "Version introduced" satırına bakmak, bir RC (Release Candidate; yayın adayı) davranışını GA sanmamak için yeterli.
Sık Düşülen Tuzaklar
Yükseltmeyi yalnız TFM değişikliği sanmak. Paket sürümleri, SDK'nın ürettiği kodlar ve analyzer'lar da değişiyor.
Yeni analyzer uyarılarını toplu bastırmak. BL0016 bu panelde circuit'i bitiren gerçek bir hatayı gösterdi; BL0013 ise yanlış alarmdı. İkisini ayırmak için tek tek bakmak gerekiyor.
QuickGrid'in URL'ye geçmesini unutmak. Aynı sayfada iki grid aynı page parametresini paylaşır.
Auto-pause'u açıp state'i düşünmemek. Duraklama, binding'i olmayan state'i (canvas, WebRTC) kendiliğinden korumuyor; doküman bunun için bir onCircuitPausing handler'ı gösteriyor.
Küçük Bir Deneme
★☆☆ Paneli yerelde .NET 11'e yükselt ve yazarlar sayfasında ikinci sayfanın adresini başka bir sekmede aç. Aynı sayfa mı geliyor?
★★☆ Auto-pause'u HiddenDelay 30 saniyeyle aç ve 12'deki dotnet-counters ölçümünü tekrarla: 20 sekme gizlendiğinde circuit.connected ve circuit.active nasıl değişiyor?
★★★ .NET 11'deki authentication refresh'i dene: Grace'in rolünü değiştir, security stamp'ini yenile ve açık bir circuit'te AuthorizeView'ın yeniden render edildiğini bir Playwright testiyle göster.
Blazor serisi burada bitiyor. On üç yazıda Bookstore'un yönetim panelini Static SSR sayfalarından InteractiveServer formlarına, Auto ile çalışan bir teklif ekranından JS interop'lu bir grafiğe, kimlik doğrulamadan production ayarlarına kadar kurduk. Mobil taraf ilerideki Flutter ile Mobil serisinde.