本文重點介紹 .NET 11 中 ASP.NET Core 中最重要的變更,並附有相關文件連結。
隨著提供新的預覽版本,本文將會更新。
Blazor
本章節會說明 Blazor 的新功能。
新的DisplayName元件及對[Display]和[DisplayName]屬性的支援
該 DisplayName 元件可用來顯示來自元資料屬性的屬性名稱:
[Required, DisplayName("Production Date")]
public DateTime ProductionDate { get; set; }
支援模型類別屬性的[Display]屬性:
[Required, Display(Name = "Production Date")]
public DateTime ProductionDate { get; set; }
在這兩種方法中,推薦屬性 [Display] ,這會使額外的屬性可用。 該 [Display] 屬性同時也允許指派資源類型以進行在地化。 當這兩個屬性同時存在時,[Display]會優先於[DisplayName]。 若兩個屬性皆不存在,元件會退回到屬性名稱。
在標籤或表格標頭中使用元件 DisplayName :
<label>
<DisplayName For="@(() => Model!.ProductionDate)" />
<InputDate @bind-Value="Model!.ProductionDate" />
</label>
Blazor 網頁腳本啟動選項格式現已支援 Blazor Server 和 Blazor WebAssembly 腳本
傳遞給 Blazor Web App 的 blazor.web.js 腳本(Blazor.start())選項物件自 .NET 8 發布以來採用以下格式:
Blazor.start({
ssr: { ... },
circuit: { ... },
webAssembly: { ... },
});
現在, Blazor Server (blazor.server.js)和 Blazor WebAssembly (blazor.webassembly.js)腳本可以使用相同的選項格式。
以下範例展示了先前期權格式,該格式仍被支援:
Blazor.start({
loadBootResource: function (...) {
...
},
});
前述範例新支援的選項格式:
Blazor.start({
webAssembly: {
loadBootResource: function (...) {
...
},
},
});
欲了解更多資訊,請參閱 ASP.NET Core Blazor startup。
新的 BasePath 元件
Blazor Web Apps 可以利用新元件 BasePath (<BasePath />)自動渲染應用程式的應用程式基底路徑(<base href>) HTML 標籤。 更多資訊請參見 ASP.NET Core Blazor 應用程式基礎路徑。
從元件中移除JS內嵌NavMenu事件處理器
用於切換導覽連結顯示的內嵌 JS 事件處理器已不存在於專案範本的 NavMenu 元件 Blazor Web App 中。 從專案範本產生的應用程式現在採用 共置 JS 模組 的方式,在渲染頁面上顯示或隱藏導航列。 新方法改善了內容安全政策(CSP)的合規性,因為它不要求CSP包含不安全的內嵌JS雜湊值。
若要將現有應用程式遷移至 .NET 11,包括在導航列切換器中採用新的 JS 模組方法,請參見 從 .NET 10 的 ASP.NET Core 遷移至 .NET 11 的 ASP.NET Core。
NavigateTo 和 NavLink 對於相對導航的支援
新的RelativeToCurrentUri參數(預設:false)適用於NavigationManager.NavigateTo和NavLink元件,允許你導航到相對於目前頁面路徑的 URI,而不是應用程式的基礎 URI。
考慮以下巢狀端點:
/docs/getting-started/installation/configuration
當瀏覽器的 URI 是/docs/getting-started/installation,且你想讓使用者導航到/docs/getting-started/configuration時,會在應用程式根目錄中由NavigateTo("/configuration")重新導向至/configuration,而非相對路徑/docs/getting-started/configuration。 設定 RelativeToCurrentUri 與 NavigateTo 或 NavLink 元件 以實現所需的導航:
Navigation.NavigateTo("/configuration", new NavigationOptions
{
RelativeToCurrentUri = true
});
<NavLink href="configuration" RelativeToCurrentUri="true">Configuration</NavLink>
在靜態伺服器端渲染(靜態 SSR)期間,持續存在 HTTP 請求之間的暫存資料
為了在進行靜態伺服器端渲染(Static SSR)時,在 HTTP 請求間保存暫存資料,Blazor 支援 TempData。 TempData 非常適合在表單提交後閃現訊息、重定向時傳遞資料(POST-Redirect-GET 模式)及一次性通知等情境。
TempData 當在應用程式的
[CascadingParameter]
public ITempData? TempData { get; set; }
當提供給參數以便簡單讀寫單一值時,請使用屬性 [SupplyParameterFromTempData] :
[SupplyParameterFromTempData]
public string? Message { get; set; }
更多資訊請參見 ASP.NET Core Blazor 伺服器端狀態管理。
新的 Blazor 網頁工作者範本(blazorwebworker)
.NET Web Worker 專案範本,包含一個用於將長期執行工作卸載至背景執行緒的 Web Worker 用戶端,現已更名為 Blazor Web Worker專案範本(blazorwebworker)。 名稱變更後,更清楚地表明此範本是 Blazor 堆疊的一部分,供 Blazor WebAssembly 和 Blazor Web 應用程式使用(用戶端轉譯,CSR)。
產生的 WebWorkerClient 已新增兩項經常被要求的功能:
-
InvokeVoidAsync用於不回傳值的 fire-and-forget worker 呼叫,其形式與IJSRuntime上的定義相對應。 - 在工作者建立和工作者呼叫時提供取消與超時支援,讓來電者能乾淨利落地傳遞
CancellationToken並拆除卡住的工人。
使用舊範本建立的現有專案仍可正常運作。 重新命名只影響 dotnet new list 以及 Visual Studio 的 Create a new project 範本清單中顯示的模板名稱。
如需詳細資訊,請參閱下列資源:
ASP.NET Core 與 .NET 在 Web Workers 上合作 -
.NET Web Worker 範本更新為 Blazor Web Worker 範本(
dotnet/aspnetcore#66070)(請勿對已關閉的議題和 PR 發表評論。)
虛擬化增強功能
元件 Virtualize<TItem> 不再假設每個物品的高度相同。 先前,該元件會停用瀏覽器的原生捲動錨定(以避免無限渲染迴圈),這意味著視窗上方的任何高度變動——如項目擴展、資料更新、懶散載入內容——都會導致可見項目跳躍到螢幕上。 元件
Virtualize現在會在執行時自動調整以符合測量的項目大小,減少了在項目高度變化時的錯誤間距與捲動。這些更新採用混合式方法:在支援原生 CSS 捲動錨定的瀏覽器上,針對非
<table>版面配置使用原生 CSS 捲動錨定;而對於ResizeObserver版面配置和 Safari,則使用以<table>為基礎的手動捲動補償作為備援方案,因為原生錨定會在<tr>元素上錯誤計算位置。使用該
Virtualize元件的應用程式會自動獲得這些更新的好處。 開發者不需要更改 API。這些更新包括將Virtualize<TItem>.OverscanCount的預設值更新,該值在 .NET 10 或更早版本為
3,而在 .NET 11 或更新版本中則更改為15。 預設值的變更提升了平均項目高度計算的精確度。如需詳細資訊,請參閱下列資源:
使用新
AnchorMode參數來控制視窗在動態新增項目時,列表邊緣的行為:-
None:沒有邊緣釘。 視窗會保持在當前滾動位置,不管物品有沒有變動。 -
Start(預設):將檢視區固定在清單的開頭。 例如,這種釘選功能對新聞動態的使用者體驗很有幫助。 -
End: 將視窗釘在清單末端。 例如,這種釘選行為對於聊天或日誌使用者體驗非常有用。
在以下範例中,虛擬化內容被釘選在列表的開頭:
<Virtualize AnchorMode="Start" ...> ... </Virtualize>如需詳細資訊,請參閱下列資源:
-
內容安全政策(CSP)合規
元件
Virtualize會在間隔器與佔位符元素(例如style)上呈現動態內嵌style="height: 478896px; flex-shrink: 0;"屬性,因為間隔器高度是在執行時根據捲動位置、物品數量及平均物品大小計算,這些大小會隨著每次捲動互動而改變。 這些style-src 'self'政策會在設定內容安全政策(Content Security Policy, CSP)時被阻擋,對於採用嚴格 CSP 政策的應用程式來說,虛擬化會完全中斷。現在,因為
Virtualize元件,所以可避免 CSP 違規:- 將計算出的間隔器與佔位高度以屬性中的
data-blazor-virtualize-reserved-height數值呈現。 - 必要時,將後方間隔器的垂直偏移渲染成屬性中的
data-blazor-virtualize-loop-breaker-transform數值,以隱藏間隔器。
- 將計算出的間隔器與佔位高度以屬性中的
適用於 Blazor WebAssembly 應用程式的新服務預設程式庫專案範本
blazor-wasm-servicedefaults 專案範本會建立供 Blazor WebAssembly 應用程式使用且已整合 Aspire 的服務預設值程式庫。 如需詳細資訊,請參閱 ASP.NET Core Blazor 工具。
適用於 Blazor WebAssembly 應用程式的全新開發伺服器
Microsoft.AspNetCore.Components.Gateway 是一款輕量級的 ASP.NET Core 主機,取代了 Microsoft.AspNetCore.Components.WebAssembly.DevServer,在開發與生產期間負責獨立的 Blazor WebAssembly 應用程式。
若要在現有的獨立 Blazor WebAssembly 應用程式中採用閘道器,請參考該應用程式專案檔案中的套件 Microsoft.AspNetCore.Components.Gateway 。
應用程式不需要自訂路由程式碼和中介軟體。 備援端點來自 SDK 在應用程式的專案檔中設定 StaticWebAssetSpaFallbackEnabled 屬性時所產生的靜態 Web 資產資訊清單,而從專案範本建立的獨立 Blazor WebAssembly 應用程式預設會包含此屬性:
<StaticWebAssetSpaFallbackEnabled>true</StaticWebAssetSpaFallbackEnabled>
在 .NET 11 發布之前,inspectUri 檔案的 Properties/launchSettings.json 屬性:
- 可讓 IDE 偵測應用程式是否為 Blazor 應用程式。
- 指示指令碼偵錯基礎結構透過 Blazor 的偵錯代理伺服器連接到瀏覽器。
使用新開發伺服器時不再需要此屬性。
開啟啟動專案的 Properties/launchSettings.json 檔案。 在每個啟動設定檔中移除該 inspectUri 檔案 profiles 節點的屬性:
- "inspectUri": "..."
如需更多資訊,請參閱 [Blazor] 將獨立 WASM 應用程式的 DevServer 取代為 BlazorGateway (dotnet/aspnetcore #65982)(請勿對已關閉的問題和 PR 發表評論)。
伺服器觸發的迴路暫停
此功能適用於伺服器端 Blazor 應用程式。
Blazor 已經支援透過 Blazor.pauseCircuit() 和 Blazor.resumeCircuit() 平順地暫停與恢復電路。 .NET 11 引入了對稱的伺服器端暫停與恢復功能,伺服器可請求連接的用戶端開始優雅的電路暫停流程。
Circuit.RequestCircuitPauseAsync(CancellationToken) 用於請求已連線的用戶端開始平順的電路暫停程序。
CancellationToken 會在框架接受該請求之前取消該請求。 如果請求被接受且客戶端被要求開始暫停,方法會回傳 true 。
這項功能在下列案例中很有用:
- 計畫中的停工與部署。
- 實例消耗。
- 應用程式維護視窗。
欲了解更多資訊及伺服器重啟實作範例,請參見 ASP.NET Core Blazor 伺服器端狀態管理。
較小 Blazor WebAssembly 的出版產量
兩項裁剪變更可縮減未使用 Blazor WebAssembly 或 熱重新載入 的已發佈 應用程式大小:
-
ComponentsMetrics和ComponentsActivitySource類型現在受[FeatureSwitchDefinition]屬性控制,因此當Renderer為System.Diagnostics.Metrics.Meter.IsSupported時(這是已修剪應用程式的預設值),修剪器即可從false及相關項目中移除計量和追蹤的呼叫路徑。[browser][wasm] 為 OTEL 實作 IL 修剪(dotnet/aspnetcore#65901)(請勿對已關閉的問題和 PR 發表評論)。 現在會公開一個由功能切換控制、且繫結至 的 屬性,因此裁剪器可在發行時於整個轉譯器中移除熱重新載入快取和中繼資料更新處理常式註冊 [ blazor][wasm] 修正熱重新載入 IL 裁剪 ( (請勿對已關閉的議題和 PR 發表評論)。#65903)
使用 OTEL 或 熱重新載入 的應用程式不會受到前述更新的影響。
QuickGrid 改進
QuickGrid 元件 在 .NET 11 中獲得多項新功能。
欲了解更多以下功能,請參見 ASP.NET Core Blazor 「QuickGrid」元件。
分頁模式
在 .NET 11 發布之前,分頁與排序狀態在記憶體中管理於 QuickGrid 元件中,且不更改 URL,稱為 inner-state navigation。 需要互動式渲染模式。
隨著 .NET 11 的發行,QuickGrid 支援URL 導覽。
分頁與排序狀態會持續存在於 URL 查詢字串中。 當使用者分頁或排序時,網址會更新(例如: ?page=2&sort=Name&direction=asc)。 這可支援連結分享、瀏覽器上一頁/下一頁,以及不含互動功能的靜態 SSR。
可排序的欄標頭和分頁器控制項會轉譯為 <a> 元素,並具有 href 屬性。
StaticHtmlRenderer 會渲染這些錨點。 每次請求時,伺服器都會讀取查詢字串以判斷當前頁面與排序狀態——無需 JavaScript 執行時。
查詢字串參數:
-
page:從 1 開始的頁碼。 第一頁省略了乾淨網址的參數。 -
sort: 欄位標題用於排序網格。 -
direction:上升(asc)或下降(desc)。
欄位 sort 由欄位的 Title 屬性來識別。 沒有 Title 的欄位會顯示為不可點擊的 <div> 標頭。
QuickGrid 初始化時會讀取 URL,並訂閱 NavigationManager.LocationChanged,因此瀏覽器的返回/前進操作以及直接輸入 URL 都能正常運作。 當排序參數從 URL 中移除時,它會回復到預設的排序欄位/方向。
已停用的分頁器連結會使用 aria-disabled="true" 和 pointer-events: none,而非 HTML 的 disabled 屬性,因為 disabled 屬性並不存在於 元素上。
查詢參數名稱
QueryParameterNameOptions元件的新QuickGrid參數控制查詢字串參數的名稱,這些參數會持續維持 URL 的網格狀態。 這個 QueryParameterNameOptions 類別有三個可設定的屬性:
-
Sort: 包含排序欄位的查詢字串參數名稱。 預設值為sort。 -
Direction: 查詢字串參數名稱,該參數包含排序方向。 預設值為direction。 -
Page:包含頁碼的查詢字串參數名稱。 預設值為page。
建構子接受一個可選的前綴參數,這個參數會加在三個預設名稱之前。 前綴必須包含你希望出現在前綴與名稱之間的分隔字元。 在以下範例中,查詢字串參數分別為 products_sort、 products_direction、 products_page和 :
@using Microsoft.AspNetCore.Components.QuickGrid
<QuickGrid ...
QueryParameterNameOptions="@(new QueryParameterNameOptions("products_"))">
...
</QuickGrid>
若要單獨控制名稱,請設定類別的屬性。 屬性設定明確優先於傳給建構子的前綴,因此兩種方法可結合:
@using Microsoft.AspNetCore.Components.QuickGrid
<QuickGrid ... QueryParameterNameOptions="@queryParameterNames">
...
</QuickGrid>
@code {
private QueryParameterNameOptions queryParameterNames = new()
{
Sort = "orderBy",
Direction = "orderDir",
Page = "p"
};
}
同一頁面上的多個網格
同一頁面上的多個 QuickGrid 元件需要唯一的查詢參數名稱,以避免查詢字串衝突。 為除了其中一個網格以外的所有格子指派一個 QueryParameterNameOptions 參數。
每個 QuickGrid 實例必須有自己的 PaginationState 實例。 若多個網格使用不同的查詢參數名稱,則不能共享 a PaginationState ——最後渲染的網格會覆蓋共享狀態上的查詢參數名稱,導致 Paginator 讀取錯誤的參數。
在 .NET 11 之前的版本中,以下 QuickGrid 元件是隱含運作的:
<QuickGrid ... Pagination="@pagination1">
...
</QuickGrid>
<QuickGrid ... Pagination="@pagination2">
...
</QuickGrid>
隨著 .NET 11 的發布,以下QuickGrid元件需要唯一的查詢參數名稱。 第一個 QuickGrid 使用預設名稱,第二個則使用 cities_ 前綴:
<QuickGrid ... Pagination="@pagination1">
...
</QuickGrid>
<QuickGrid ... Pagination="@pagination2"
QueryParameterNameOptions="@(new QueryParameterNameOptions("cities_"))">
...
</QuickGrid>
前述 QuickGrid 元件的查詢字串範例:
?page=2&sort=Name&direction=asc&cities_page=3&cities_sort=Population&cities_direction=desc
依欄位排序
將 Sortable="true" 加入至 PropertyColumn。 透過基於 URL 的導航,選擇標頭即可導向帶有更新 sort 與 direction 參數的 URL。 在內部狀態導覽中,選取標頭時會觸發 @onclick,而後者會呼叫 SortByColumnAsync。 在這兩種情況下,都是 SortByColumnAsync 透過 NavigationManager.NavigateTo(GetSortQueryStringUrl(...))來導航,因此 URL 總是反映排序狀態。
標題式排序識別
URL 中的排序狀態會使用欄位的 Title 屬性作為識別碼。
sort查詢參數設column.Title為(欄位標題Name為例:?sort=Name&direction=asc)。 當 URL 發生變更時,QuickGrid 會執行 sort,將 _columns.FirstOrDefault(c => c.Title == sort.ColumnTitle) 值對應回欄位。 若欄位標題不符,排序會被忽略,網格會退回到預設排序。
改名欄位 Title 會破壞網址。 任何包含舊標題 sort 的書籤或共享 URL 都不再匹配,網格也會默默回歸預設排序,而不是依預期欄位排序。 在 PropertyColumn 中,Title 預設為屬性名稱(例如:Property="@(p => p.FirstName)" 會產生 Title="First Name"),因此重新命名屬性或明確變更 Title 參數,都會破壞現有的 URL。
分頁器
Paginator 注入 NavigationManager、 訂閱 LocationChanged,並在每次位置變更時從查詢字串讀取頁面索引。
GoToPageAsync 會導向目標 URL,而不是直接變更 PaginationState。 狀態會透過 LocationChanged 回調流程更新。
GetPageUrl 會回傳包含從 1 開始之頁碼的 URL。 頁面索引 0(第 1 頁)完全省略了查詢參數。
CSS 破壞性變更
當啟用以 URL 為基礎的導覽時,以 button.col-title 為目標的選擇器也必須以 a.col-title 為目標,而 nav button/nav button:disabled 需要 nav a/nav a[aria-disabled="true"]。 內建 QuickGrid 樣式表預設提供兩者。
如何停用基於網址的導航
要停用基於 URL 的導覽,請將該功能的開關設定 AppContext 為 false:
AppContext.SetSwitch(
"Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting",
false);
這會還原帶有 <button> 處理常式的 @onclick 元素。 需要互動式渲染模式。
開關只控制所渲染的 HTML 元素(顯示為 <a> 或 <button>)。 即使停用,內部 QuickGrid 仍會讀寫 URL 查詢字串的狀態。
SortByColumnAsync 和 Paginator.GoToPageAsync 不論旗標為何,都透過 NavigationManager.NavigateTo 導覽。
列點擊事件(OnRowClick)
元件 QuickGrid 現在支援透過新 OnRowClick 參數進行列點擊事件。 設定完成後,格子會自動套用適當的樣式(游標指標),並對點擊的項目呼叫回調:
@using Microsoft.AspNetCore.Components.QuickGrid
@inject NavigationManager NavigationManager
<QuickGrid Items="@people.AsQueryable()"
OnRowClick="@((Person args) => HandleRowClick(args))">
<PropertyColumn Property="@(p => p.Name)" />
<PropertyColumn Property="@(p => p.Email)" />
</QuickGrid>
@code {
private List<Person> people = new()
{
new(1, "Alice Smith", "alice@example.com", "Engineering"),
new(2, "Bob Johnson", "bob@example.com", "Marketing"),
new(3, "Carol Williams", "carol@example.com", "Engineering"),
};
private void HandleRowClick(Person person)
{
NavigationManager.NavigateTo($"/person/{person.Id}");
}
private record Person(int Id, string Name, string Email, string Department);
}
此功能包含內建的 CSS 樣式,透過 row-clickable CSS 類別為可點擊的資料列套用指標游標,為使用者提供清楚的視覺回饋。
在 Blazor Web App 中進行用戶端預先轉譯會保留伺服器的文化特性
依預設,在伺服器上進行的用戶端預先轉譯(.Client 中的 Blazor Web App 專案)會將伺服器的 CurrentCulture 和 CurrentUICulture 保留到元件狀態中,並在衛星組件載入前於用戶端上套用它們。
需要用戶端獨立於伺服器自行選擇文化的應用程式,可以在 WebAssemblyComponentsOptions.UseCultureFromServer 的 Blazor Web App 檔案中使用 Program 選擇退出:
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents(options =>
{
options.UseCultureFromServer = false;
});
在靜態伺服器端渲染(靜態 SSR)期間,持續保存 HTTP 請求之間的會話資料
工作階段資料持久化會在靜態伺服器端轉譯(static SSR)期間讀取及寫入以 cookie 為基礎的 HTTP 工作階段值,這對購物車 ID 或多步驟表單進度等情境非常實用。 與 暫時資料持久化(ITempData)不同,會話值在讀取後不會被清除。 值會在多次請求中持續存在,以維持會話生命週期。
會話儲存配置需要透過呼叫 AddSession 並請求管線配置 UseSession來新增服務:
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
builder.Services.AddRazorComponents();
var app = builder.Build();
app.UseSession();
當提供給參數時,請使用不含鍵或含有鍵(字串)的 [SupplyParameterFromSession] 屬性:
[SupplyParameterFromSession]
public string? Message { get; set; }
[SupplyParameterFromSession(Name = "flash_message")]
public string? FlashMessage { get; set; }
更多資訊請參見 ASP.NET Core Blazor 伺服器端狀態管理。
GetUriWithFragment 擴充方法
一種新的 GetUriWithFragment 擴充方法允許 NavigationManager 輕鬆構建帶有雜湊片段的 URI。 此輔助方法提供一種高效且零分配的方式,將雜湊片段附加到目前的 URI。 以下範例展示了兩種使用情境:
- 內嵌呼叫會跳至轉譯後頁面的第 1 節(
id="section-1")。 - 方法呼叫接收區段 Id (
sectionId) 並導向該頁面區段。
@inject NavigationManager Navigation
<a href="@Navigation.GetUriWithFragment("section-1")">
Jump to Section 1
</a>
@code {
private void NavigateToSection(string sectionId)
{
var uri = Navigation.GetUriWithFragment(sectionId);
Navigation.NavigateTo(uri);
}
}
此方法使用 string.Create 以獲得最佳效能,且可在非根基底 URI 的情況下正確運作(例如使用 <base href="/app/"> 時)。
EnvironmentView 元件
Blazor 現在內建一個 EnvironmentView 基於主機環境的條件渲染元件。 此元件提供一種一致的方式,能根據當前環境在伺服器端與用戶端主機模型中呈現內容。
元件 EnvironmentView 接受 Include 參數 Exclude 來指定環境名稱。 該元件執行大小寫不區分的匹配,並遵循與 MVC EnvironmentTagHelper相同的語意。
@using Microsoft.AspNetCore.Components.Web
<EnvironmentView Include="Development">
<div class="alert alert-warning">
Debug mode enabled
</div>
</EnvironmentView>
<EnvironmentView Include="Development,Staging">
<p>Pre-production environment</p>
</EnvironmentView>
<EnvironmentView Exclude="Production">
<p>@DateTime.Now</p>
</EnvironmentView>
MathML 命名空間支援
Blazor 現已支援互動式渲染中的 MathML 元素。 MathML 元素(例如 <math>、<mrow>、<mi> 和 <mn>)會使用 http://www.w3.org/1998/Math/MathML 以正確的命名空間(document.createElementNS())建立,類似於 SVG 元素的處理方式:
<math>
<mrow>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mo>−</mo>
<mi>b</mi>
<mo>±</mo>
<msqrt>
<mrow>
<msup><mi>b</mi><mn>2</mn></msup>
<mo>−</mo>
<mn>4</mn>
<mi>a</mi>
<mi>c</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mi>a</mi>
</mrow>
</mfrac>
</mrow>
</math>
此修正確保 MathML 內容在透過 的渲染器動態 Blazor新增時,能在瀏覽器中正確呈現,解決了過去 MathML 元素僅以一般 HTML 元素而產生、未附上正確命名空間的問題。
InvokeVoidAsync() 分析儀
新增了一個分析 Blazor 器(BL0010),建議 InvokeVoidAsync 在呼叫不回傳值的 JavaScript 函式時,使用 代替 InvokeAsync<object> 。 此分析器協助開發者撰寫更有效率的 JSInterop 程式碼。
有問題的程式碼:
// ⚠️ BL0010: Use InvokeVoidAsync for JavaScript functions that don't return a value
await JSRuntime.InvokeAsync<object>("console.log", "Hello");
建議代碼:
// ✅ Correct: Use InvokeVoidAsync
await JSRuntime.InvokeVoidAsync("console.log", "Hello");
分析器有助於發現不必要的效能問題,避免 InvokeAsync 不必要地與 object 回報值一起使用或忽略,引導開發者採用更合適的 InvokeVoidAsync 方法。
IComponentPropertyActivator
Blazor 現在提供 IComponentPropertyActivator,可用來自訂元件上的 [Inject] 屬性要如何填入。 這使得進階情境得以實現,例如:
- 提供更多房產解決的背景資訊。
- 支援需要攔截屬性注入的自訂 DI 容器。
- 需要屬性注入自訂的進階情境。
public interface IComponentPropertyActivator
{
Action<IServiceProvider, IComponent> GetActivator(
[DynamicallyAccessedMembers(Component)] Type componentType);
}
預設實作會依元件類型快取啟動器,支援透過 [Inject(Key = "...")] 提供具索引鍵的服務,並與 熱重新載入 整合以進行快取失效,同時包含適當的裁剪註解,以支援 AOT 相容性。
SignalR
ConfigureConnection 用於互動伺服器元件
Blazor 現在可讓您透過 SignalR 上新的 ConfigureConnection 屬性,在使用互動式伺服器元件時設定底層 ServerComponentsEndpointOptions 連線選項。 這使得原本只能透過變通方法存取的 HttpConnectionDispatcherOptions 屬性能夠進行設定。
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(options =>
{
options.ConfigureConnection = dispatcherOptions =>
{
dispatcherOptions.CloseOnAuthenticationExpiration = true;
dispatcherOptions.AllowStatefulReconnects = true;
dispatcherOptions.ApplicationMaxBufferSize = 1024 * 1024;
};
});
這提供了一個乾淨且型別安全的 API,方便設定連線 SignalR 設定,無需檢查端點元資料。
IHostedService 中的 Blazor WebAssembly 支援
Blazor WebAssembly 現在支援 IHostedService 在瀏覽器中執行背景服務。 這使其與 Blazor Server 達到功能對等,並支援定期資料重新整理、即時更新和背景處理等情境。
public class DataRefreshService : IHostedService
{
private Timer? _timer;
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
return Task.CompletedTask;
}
private void RefreshData(object? state)
{
// Refresh data periodically
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Dispose();
return Task.CompletedTask;
}
}
// Registration
builder.Services.AddHostedService<DataRefreshService>();
託管服務會在應用程式啟動時啟動,並在應用程式關閉時停止,為 Blazor WebAssembly 應用程式中的背景作業提供明確的生命週期管理。
從伺服器端設定 Blazor 客戶端行為
Blazor 應用程式現在可以在對應 Razor 元件時,於伺服器端以 C# 設定客戶端啟動行為,而不必手寫 Blazor.start JavaScript。
WithBrowserOptions 會設定一些選項,伺服器會將這些選項序列化至已呈現的頁面中,而 Blazor 指令碼則會在瀏覽器中套用這些選項;這適用於 Server、WebAssembly 和 Auto 轉譯模式。 選項涵蓋用戶端日誌層級、互動式伺服器重新連線、增強導覽是否保留 DOM,以及 WebAssembly 執行環境名稱、文化與環境變數:
app.MapRazorComponents<App>()
.WithBrowserOptions(options =>
{
options.InteractiveServer.ReconnectionDialogId = "reconnect-dialog";
options.InteractiveServer.ReconnectionMaxRetries = 10;
options.InteractiveServer.ReconnectionRetryInterval = TimeSpan.FromSeconds(1.5);
options.InteractiveWebAssembly.EnvironmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] =
"https://localhost:4318";
options.StaticServer.CircuitInactivityTimeout = TimeSpan.FromSeconds(1.5);
options.StaticServer.PreserveDom = true;
options.InteractiveWebAssembly.ApplicationCulture = "en-ca";
options.InteractiveWebAssembly.EnvironmentName = "Staging";
options.LogLevel = LogLevel.Warning;
});
你也可以使用 Razor 元件,在 ConfigureBrowser 元件中設定這些選項:
<ConfigureBrowser Options="RequestBrowserOptions" />
...
@code {
private BrowserOptions RequestBrowserOptions => new()
{
LogLevel = LogLevel.Trace,
StaticServer = { PreserveDom = true }
};
}
使用 HttpContext 從 GetBrowserOptions() 讀取已解析的選項:
@BrowserOptions.GetBrowserOptions(HttpContext).LogLevel
如需詳細資訊,請參閱下列資源:
-
API 提案:用於伺服器到用戶端設定的 BrowserOptions(
dotnet/aspnetcore#66393) -
依據審查意見重構 BrowserConfiguration API(BrowserOptions),同時保留 JS 線上格式(
dotnet/aspnetcore#67337) -
根據審查意見重塑 BrowserOptions 的伺服器到用戶端設定 API(
dotnet/aspnetcore#67918)
請不要對已關閉議題和公關發表評論。 請開啟新議題以提供對此 API 的回饋。
Blazor WebAssembly 組態中的環境變數
Blazor WebAssembly應用程式現在可以透過 來存取環境變數。IConfiguration 這讓執行時能在不重建應用程式的情況下進行配置,讓同一個建置更容易部署到不同環境。
在以下範例中, API_ENDPOINT 與 ENABLE_FEATURE_X 環境變數會自動包含在設定中:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var apiEndpoint = builder.Configuration["API_ENDPOINT"];
var featureFlag = builder.Configuration["ENABLE_FEATURE_X"];
環境變數與其他設定來源(如應用程式設定 (appsettings.json)一同載入設定系統,提供一種統一的方式,無論來源為何都能存取設定值。
Blazor WebAssembly 元件指標與追蹤
Blazor WebAssembly 應用程式現在在執行時啟用了指標支援時,會提供元件專屬的指標與追蹤功能。
在範本中啟用容器支援Blazor Web App
Blazor Web App專案範本現在支援Visual Studio中的啟用容器支援選項。 這使得容器化 Blazor Web App 並部署到容器編排平台(如 Kubernetes 或 Azure 容器應用程式)變得更容易。
靜態 SSR 支援用戶端驗證
Blazor 靜態伺服器端渲染(static SSR)表單現在可立即在瀏覽器中收到驗證回饋,無需與伺服器來回通訊,體驗與互動式 Blazor 應用程式以及採用非侵入式驗證的 MVC 應用程式一致。 .NET 模型仍是驗證規則的唯一真實來源。 伺服器會呈現驗證規則的元資料,這些規則會由客戶端的程式碼強制執行 BlazorJS 。
此功能預設為所有包含該 DataAnnotationsValidator 元件的靜態 SSR 表單啟用。 支援強化型與非強化型態。
完整的功能覆蓋可在靜態 SSR 中的 ASP.NET Core Blazor 用戶端表單驗證中取得。
如需詳細資訊,請參閱下列資源:
-
在 Blazor SSR 中為用戶端驗證新增 .NET 支援(
dotnet/aspnetcore#66441) -
新增 JS 用於 SSR 用戶端驗證 Blazor 的函式庫(
dotnet/aspnetcore#66420)
請不要對已關閉議題和公關發表評論。 如果你對此功能有任何意見回饋,請在 dotnet/aspnetcore GitHub 存放庫中開啟新的議題。
非同步表單驗證支援
Blazor 表單支援非同步驗證規則,例如資料庫查詢或遠端 API 呼叫。 在任何渲染模式下,EditForm 提交驗證都會全程等待非同步驗證程序完成。
內建 DataAnnotationsValidator 元件執行非同步 DataAnnotations API(AsyncValidationAttribute 與 IAsyncValidatableObject),因此模型上宣告的非同步規則無需額外設定即可運作。
驗證器元件會使用 ValidationRequestedEventArgs.AddAsyncValidator 為整個表單註冊非同步工作,並使用 EditContext.RegisterAsyncFieldValidator 為單一欄位註冊非同步工作。 此框架擁有取消權杖來源,會取消已被取代的驗證,並透過 IsValidationPending(field) 和 IsValidationFaulted(field) 提供進度資訊。
<EditForm EditContext="editContext" OnValidSubmit="HandleSubmit">
<InputText @bind-Value="model.Username" />
@if (editContext.IsValidationPending(() => model.Username))
{
<span>Checking availability...</span>
}
<ValidationMessage For="() => model.Username" />
<button type="submit">Register</button>
</EditForm>
@code {
[Inject] public UserService Users { get; set; } = default!;
private readonly RegistrationModel model = new();
private EditContext editContext = default!;
private ValidationMessageStore messages = default!;
protected override void OnInitialized()
{
editContext = new EditContext(model);
messages = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += (_, e) =>
{
if (e.FieldIdentifier.FieldName == nameof(model.Username))
{
editContext.RegisterAsyncFieldValidator(e.FieldIdentifier,
token => CheckAsync(e.FieldIdentifier, model.Username, token));
}
};
}
private async Task CheckAsync(FieldIdentifier field, string value, CancellationToken ct)
{
messages.Clear(field);
if (await Users.IsUsernameTakenAsync(value, ct))
{
messages.Add(field, "Username is taken.");
}
editContext.NotifyValidationStateChanged();
}
private Task HandleSubmit() => RegisterAsync();
}
完整的功能涵蓋可參考 ASP.NET Core Blazor 進階表單驗證。
欲了解更多資訊,請參閱 在 Blazor 中新增對非同步表單驗證的內建支援(dotnet/aspnetcore #66526)。
請不要對已關閉議題和公關發表評論。 如果你對此功能有任何意見回饋,請在 dotnet/aspnetcore GitHub 存放庫中開啟新的議題。
TempData 與 [SupplyParameterFromSession] 串流 SSR 持久化的修正
當頁面使用會話支援功能,且元件有[SupplyParameterFromSession]參數(可建立訂閱)或會話儲存的 TempData 提供者處於啟用狀態時,即使最終未寫入任何值,也會在串流開始前發布該會話cookie.AspNetCore.Session()。 不使用會話支援功能的頁面則不受影響。
欲了解更多資訊,請參閱 修正串流 SSR 案例中 TempData 與 SupplyParameterFromSession 的持續性問題(dotnet/aspnetcore #66832)。 (請勿對已封閉議題及公關發表評論。)
防偽造中介軟體(app.UseAntiforgery())在 Blazor Web App 中為選用
CSRF 保護預設會透過自動注入的 CSRF 保護中介軟體啟用,因此通常不需要在 app.UseAntiforgery() 中明確呼叫Blazor Web App,且只應在特定使用案例中加入。 該通話不再出現在從 Blazor Web App 專案範本建立的應用程式中。
如需詳細資訊,請參閱下列資源:
ASP.NET Core 中新的自動 CSRF 保護機制概述:
Blazor Virtualize 可捲動至某個項目
Virtualize<TItem> 元件現在可在開啟時顯示特定項目,並可視需要捲動至任何項目。 兩個新的公開 API 使此成為可能:
-
InitialItemIndex在第一個互動式渲染中,將清單定位於指定項目,使列表在該項目開啟時不會閃現第一個項目。 -
ScrollToItemAsync(int itemIndex, CancellationToken cancellationToken = default)會在首次渲染後的任何時間捲動至某個項目,並回傳一個Task,在目標對齊至檢視區頂端時完成。
<Virtualize TItem="Product" Items="products" InitialItemIndex="500" @ref="list">
<div class="product">@context.Name</div>
</Virtualize>
<button @onclick="GoToTop">Back to top</button>
@code {
private Virtualize<Product> list = default!;
private List<Product> products = ProductCatalog.All;
private async Task GoToTop() => await list.ScrollToItemAsync(0);
}
超出範圍的索引會被限制在合法範圍內。 若第二 ScrollToItemAsync 通電話在一通仍在飛行時開始,最後一通通電話勝出。 在第一次互動式轉譯之前呼叫 ScrollToItemAsync 會引發 InvalidOperationException;請改用 InitialItemIndex 來設定起始位置。
如需詳細資訊,請參閱下列資源:
- ASP.NET Core Razor 元件虛擬化
-
新增
dotnet/aspnetcore(sic†)參數與InitialIndex(sic†)API 至ScrollToIndexAsync(Virtualize<TItem>#66753)。 (請勿對已關閉議題及PRs發表評論。 原文如此†:API命名更改時未更新PR標題。)
分頁非作用中時自動暫停電路
自動暫停可在瀏覽器分頁變成隱藏狀態時暫停電路,釋放伺服器記憶體和由非作用中使用者占用的 SignalR 連線。 這是由 Microsoft.AspNetCore.Components.Server.AutoPause 套件提供的自願啟用功能。 新增套件參考後,在對應用程式的根元件進行映射時,呼叫 AddAutoPause 以啟用此功能:
app.MapRazorComponents<App>()
.WithBrowserOptions(options => options.AddAutoPause(p => p.HiddenDelay = TimeSpan.FromSeconds(30)));
當分頁被隱藏到可設定的延遲期(預設:2 分鐘)後,電路會暫停。 如果使用者在延遲結束前返回,暫停就不會發生。
更多資訊請參見 ASP.NET Core Blazor 伺服器端狀態管理。
提供對 AuthorizationPolicy 和 IAuthorizationRequirementData 更廣泛的支援
從 .NET 11 開始,你可以對集線器和集線器方法、MVC 控制器和動作,以及 Blazor和AuthorizeViewAuthorizeRouteView元件套用IAuthorizationRequirementDataSignalR屬性,而不只是端點。 對於以早於 .NET 11 的版本為目標的應用程式,這些屬性僅會在 Minimal API 和路由端點上強制套用。
如需詳細資訊,請參閱 使用 'IAuthorizationRequirementData' 的自定義授權原則。
QuickGrid Virtualize 的 API 被公開
當網格虛擬化時,會暴露以下新的 QuickGrid 元件 API(Virtualize 設定為 true):
-
InitialItemIndex: 在第一個互動式渲染時,將網格捲動到給定的零基礎列索引。 該數值只會套用一次,並限制在有效範圍內。 這會轉發給內部的Virtualize元件。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件虛擬化。 -
ScrollToItemAsync程式化地將網格捲動至給定的零為基礎的列索引,並將其對齊於頂端。 以最後一次呼叫為準;當虛擬化已停用,或方格尚未完成轉譯時,此方法會擲回 InvalidOperationException。 這會轉發給內部的Virtualize元件。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件虛擬化。 -
AnchorMode:控制在動態新增項目時,視區在清單邊緣的行為(預設值:Start)。 這是一個實驗性的 API,需要先選擇加入ASP0030診斷,並會轉發到內部Virtualize元件。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件虛擬化。 -
ItemComparer:用於偵測項目是否在兩次資料載入之間被前插或附加的比較子,這對於由 ItemsProvider 提供的類別型別項目很有幫助。 這是一個實驗性的 API,需要先選擇加入ASP0030診斷,並會轉發到內部Virtualize元件。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件虛擬化。
如需詳細資訊,請參閱 ASP.NET 核心 Blazor 『QuickGrid' 元件。
在靜態 SSR 期間,將元件子樹的已渲染輸出快取
新的 CacheView 元件會在靜態伺服器端渲染(static SSR)期間,快取 Razor 元件子樹的渲染結果。 發生快取命中時,系統會直接重新使用已快取的標記,而不會實例化或執行包含在已快取輸出中的子元件其生命週期。
CacheView 對於昂貴且大多靜態且不需要整個回應快取的頁面區塊非常有用:
<CacheView VaryByQuery="category" ExpiresAfter="TimeSpan.FromMinutes(5)">
<ProductList Category="@Category" />
</CacheView>
欲了解更多資訊,請參閱 ASP.NET Core Blazor CacheView 元件。
Blazor Server 線路會在驗證重新整理後更新
互動式伺服器元件現在可以在不重新連接電路的情況下接收更新後 ClaimsPrincipal 的資訊。
Blazor元件樞紐與用戶端會自動啟用認證刷新,因此不需要額外的設定。
連線刷新認證後,更新 Blazor 認證狀態並提升 AuthenticationStateChanged。 使用 AuthenticationStateProvider 的元件(包括 AuthorizeView)會使用更新後的識別資訊與宣告重新轉譯。 當使用者角色或權限在活躍電路中改變,或元件在聲明更新後需重新載入使用者專屬內容時,這種行為非常有用。 使用者介面可以反映新的認證狀態,而不必強制使用者重新連接或重新載入頁面。
如需詳細資訊,請參閱下列資源:
-
[Blazor] 將 SignalR 認證更新傳遞至伺服器迴路(
dotnet/aspnetcore#68221) -
[release/11.0-rc1] 強化 SignalR 驗證更新 (
dotnet/aspnetcore#68593)
請不要對已關閉議題和公關發表評論。 如果你對此功能有任何意見回饋,請在 dotnet/aspnetcore GitHub 存放庫中開啟新的議題。
用於代理型使用者介面的實驗性 Blazor AI 元件
現代 AI 應用程式日益提供與客服的豐富互動。 完整的代理型使用者介面可能需要串流持續工作、視覺化代理的推理與進度、工具啟動前請求批准、接受多模態輸入,以及同步應用程式與代理之間的狀態。 Blazor AI 元件設計用來提供使用Blazor元件模型創造這些體驗的基礎。
新的 Microsoft.AspNetCore.Components.AI NuGet 套件 包含一組 Blazor 初步的 AI 元件,用於串流聊天、富文本與工具渲染、人工審核流程,以及輸入、分享和預測 UI 狀態。
開始
這很重要
該Microsoft.AspNetCore.Components.AI套件在 .NET 11 中是預發布的實驗性套件。
將套件加入 Blazor 應用程式:
dotnet add package Microsoft.AspNetCore.Components.AI --prerelease
基本聊天和 Components.AI 區塊模型適用於任何 IChatClient。 若要透過代理使用者互動協定(AG-UI)將應用程式連接到Blazor遠端代理程式,請安裝以下AGUI.Client套件:
dotnet add package AGUI.Client
AGUI.Client 包含對 AGUI.Abstractions 的傳遞相依參考,而後者提供了後續範例中使用的 AG-UI 事件類型。 將 AGUIChatClient 註冊為應用程式的 IChatClient:
using AGUI.Client;
using Microsoft.Extensions.AI;
builder.Services.AddHttpClient<IChatClient>(httpClient =>
new AGUIChatClient(new(httpClient, "https://api.example.com/agent")));
AGUIChatClient 會將 AG-UI 事件以 ChatResponseUpdate 值的形式串流傳送。
Blazor AI 元件會渲染這些更新帶來的對話內容,而應用程式則能利用額外的 AG-UI 事件資訊,建立更豐富的代理互動。 雖然任何 IChatClient都支援基本聊天功能,但當遠端伺服器與 Blazor 用戶端必須交換前端工具宣告、後端工具事件、批准中斷、共享狀態事件或 AG-UI 對話識別碼時,AG-UI 是必要的。
Microsoft Agent Framework(MAF)可以透過 ASP.NET Core AG-UI 端點公開一個 AIAgent。 關於伺服器端的設定,請參見 AG-UI 與 Agent Framework 的整合及其.NET入門指南。
建立一個基本的串流對話
代理型使用者介面的第一步通常是一段基本對話,透過串流回應並保留跨回合的訊息歷史。
初期的聊天支援是服務提供者和協議中立的。 應用程式提供來自 Microsoft.Extensions.AI 的 IChatClient,而 UIAgent 會將其串流回應轉換為可觀測的內容區塊。
ChatPage 是一個完整的聊天殼,結合了三個較低階的組件:
-
AgentBoundary創造並連鎖化對話狀態。 -
MessageList會在串流期間渲染每個回合,並提供預設的正在輸入、錯誤和重試介面。 -
MessageInput從文字區發送訊息,並在回應串流時關閉輸入。
以下元件會在應用程式提供的 UIAgent 之上建立 IChatClient,並使用 ChatPage 呈現對話:
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Extensions.AI
@implements IDisposable
@inject IChatClient ChatClient
<ChatPage Agent="agent" Placeholder="Type a message...">
<WelcomeContent>
<p>Ask the agent a question.</p>
</WelcomeContent>
</ChatPage>
@code {
private UIAgent agent = default!;
protected override void OnInitialized()
{
agent = new UIAgent(ChatClient);
}
public void Dispose() => agent.Dispose();
}
Placeholder 設定在空白訊息輸入框中顯示的提示文字。
WelcomeContent 在發送第一則訊息前提供內容。
將元件樣式包含在 App 元件(Components/App.razor)中:
<link rel="stylesheet" href="@Assets["_content/Microsoft.AspNetCore.Components.AI/ai-chat.css"]" />
渲染內容區塊
一個 IChatClient 會以 ChatResponseUpdate 值串流傳送面向模型的內容,例如 TextContent、RichTextContent 和 FunctionCallContent。
UIAgent 將這些回應內容映射到面向 ContentBlock UI 的物件中,物件保留渲染狀態,並在回應串流時即時更新。 例如,明文片段與結構化富文本快照皆映射到 RichContentBlock。
內建的區塊類型包括:
-
RichContentBlock用於串流文字及結構化豐富內容。 -
FunctionInvocationContentBlock用於伺服器函式呼叫以及其最終結果。 -
UIActionBlock適用於在 Blazor 應用程式中執行的函式。 -
FunctionApprovalBlock用於等待使用者批准的函式呼叫。 -
ActivityContentBlock用於應用程式定義的進度,並即時更新。
ChatPage 和 MessageList 包含 RichContentBlock 和 FunctionApprovalBlock 的預設轉譯。 將 BlockRenderer<TBlock> 新增至 ChatPage.MessageListContent,以取代此預設的渲染方式,或渲染另一種區塊類型。 其子內容會以 context 的形式接收對應的區塊,其中包含其目前的屬性,且這些屬性會在串流期間發生變化。
以下範例取代了對話內容的預設渲染方式:
<ChatPage Agent="agent">
<MessageListContent>
<BlockRenderer TBlock="RichContentBlock" Context="block">
<div class="agent-response">@block.RawText</div>
</BlockRenderer>
</MessageListContent>
</ChatPage>
使用渲染器的 When 謂詞只處理特定類型的區塊。 如果多個渲染器匹配,則最近註冊的渲染器優先。 應用程式也可以定義自訂 ContentBlock 類型,並使用 ContentBlockHandler<TState> 將回應內容對應至這些類型。
渲染結構化富文本
富文本支援允許代理回傳結構化呈現模型,而非純文字。
RichTextContent 是一種回應內容,包含純文字以及用於標題、段落、強調、連結、清單、程式碼區塊、表格及其他呈現元素的 RichTextNode 值。
UIAgent 會對應到用於純文字 TextContent 的相同 RichContentBlock,但會使用提供的節點樹,而不是建立簡單的段落。
應用程式可以直接產生 RichTextContent,或使用 IChatClient 中介軟體來轉換串流的 TextContent,例如將 Markdown 剖析為 RichTextNode 值。 此套件不需要也不包含任何特定的 Markdown 實作。 每個 RichTextContent 都是一個完整的快照,因此隨著串流進行,它會原子性地替換先前的內容,以傳遞相同的訊息。
ChatPage 和 MessageList 接著會在無需自訂 BlockRenderer 的情況下呈現結構化內容。
顯示伺服器端工具呼叫
代理可以呼叫在其伺服器上執行的工具,而 Blazor 應用程式則透過應用程式特定的介面渲染操作。 例如,客服可以呼叫天氣工具,應用程式可以立即顯示所請求的位置,伺服器回傳結果後再顯示天氣卡。
伺服器工具呼叫會成為 FunctionInvocationContentBlock 執行個體,將 FunctionCallContent 與其最終的 FunctionResultContent 配對,並提供工具名稱、引數及完成狀態。
套件的來源產生器會根據標註了 ToolBlock、ToolParameter 和 ToolResult 的類別,建立強型別的區塊處理常式([Blazor] 新增 Components.AI 伺服器工具轉譯(dotnet/aspnetcore #68327)):
[ToolBlock("get_weather")]
public partial class WeatherToolBlock : FunctionInvocationContentBlock
{
[ToolParameter(Name = "location")]
public string? Location { get; set; }
[ToolResult]
public WeatherInfo? Weather { get; set; }
}
在建構 UIAgent 時註冊產生的處理常式:
var agent = new UIAgent(
chatClient,
options => options.AddGeneratedToolBlocks());
在 MessageListContent 中轉譯產生的區塊:
<ChatPage Agent="agent">
<MessageListContent>
<BlockRenderer TBlock="WeatherToolBlock">
@if (context.HasResult)
{
<p>@context.Location: @context.Weather?.Temperature°C</p>
}
else
{
<p>Checking the weather for @context.Location...</p>
}
</BlockRenderer>
</MessageListContent>
</ChatPage>
隨著呼叫引數陸續串流傳入,生成的處理常式會更新 Location,而 HasResult 則維持為 false。 當結果傳回時,會將結果填入 Weather,將 HasResult 設為 true,並將同一個區塊重新渲染為已完成的天氣卡。
當 MAF 託管遠端代理時,後端工具會使用其正常的工具管線,AG-UI 將通話與結果傳送至用戶端。 請參考 AG-UI 的後端工具渲染。
執行前端工具
前端工具是在客戶端應用程式中執行,而非在代理伺服器上。 例如,應用程式 Blazor 可以公開一個工具,改變使用者介面狀態、讀取本地偏好設定,或向使用者徵求意見。 使用 AIFunctionFactory從 Microsoft.Extensions.AI建立該工具,然後向 UIAgentOptions.RegisterUIAction註冊([Blazor] 新增 Components.AI 用戶端工具轉譯(dotnet/aspnetcore #68325))。
RegisterUIAction向代理程式通告AIFunction。 當代理提出要求時,UIAgent 會建立一個 UIActionBlock,而不是立即執行該函式:
var setAccentColor = AIFunctionFactory.Create(
async (string color) =>
{
await InvokeAsync(() =>
{
accentColor = color;
StateHasChanged();
});
return $"Changed the accent color to {color}.";
},
name: "set_accent_color");
var agent = new UIAgent(
chatClient,
options => options.RegisterUIAction(setAccentColor))
將一個 BlockRenderer<UIActionBlock> 放在 MessageListContent 中,以處理代理程式要求的函式呼叫。 例如,渲染器可以使用一個元件自動呼叫該函式並顯示其進度:
<ChatPage Agent="agent">
<MessageListContent>
<BlockRenderer TBlock="UIActionBlock"
When='@(action => action.ToolName == "set_accent_color")'
Context="action">
<AutoInvokeAction Action="action" />
</BlockRenderer>
</MessageListContent>
</ChatPage>
AutoInvokeAction元件在收到區塊時呼叫InvokeAsync:
@if (Action.IsComplete)
{
<span>Accent color updated</span>
}
else
{
<span>Updating accent color...</span>
}
@code {
[Parameter, EditorRequired]
public UIActionBlock Action { get; set; } = default!;
protected override async Task OnInitializedAsync()
{
if (!Action.IsComplete)
{
await Action.InvokeAsync();
}
}
}
呼叫 InvokeAsync 時會執行已註冊的函式,並使用代理提供的參數。 函式會在 Blazor UI 執行的地方執行:對於 Blazor Server,是在伺服器端電路中;對於 WebAssembly,則是在瀏覽器中。 當函式完成時, UIAgent 會將其結果回傳給代理並繼續對話。 使用渲染器的 When 謂詞來為每個 action.ToolName提供不同的處理。 渲染器可以自動呼叫動作,如圖所示,或呈現先收集輸入或確認的介面。
工具運行前必須取得批准
應用程式可以要求使用者在客服人員繼續前,先批准一項重要的工具呼叫,例如安排會議。 工具核准請求會變成 FunctionApprovalBlock 實例。 對話會暫停,直到 UI 呼叫 Approve 或 Reject([Blazor] 新增 Components.AI 人工審核流程(dotnet/aspnetcore #68329))為止:
<ChatPage Agent="agent">
<MessageListContent>
<BlockRenderer TBlock="FunctionApprovalBlock" Context="approval">
<p>Allow <code>@approval.ToolName</code> to run?</p>
<button @onclick="approval.Approve">Approve</button>
<button @onclick="() => approval.Reject()">Reject</button>
</BlockRenderer>
</MessageListContent>
</ChatPage>
對於 MAF 代理,伺服器決定哪些功能需要核准,AG-UI 則傳送請求與決策。 請參閱 AG-UI 的人類參與迴圈。
展示活動
活動是應用程式定義的進度項目,當代理執行較長時間的工作時,會即時更新。 例如,研究代理可以顯示他們正在搜尋來源、比較結果並完成研究,而不必為每次更新新增獨立訊息。
ActivityHandler<TBlock> 是一個協定中立的擴充點,可將提供者或應用程式特定的進度更新對應為可變的 ActivityContentBlock([Blazor] 新增代理式生成式 UI 狀態渲染(dotnet/aspnetcore #68333))。
TryCreateBlock 會初始化並發出第一個符合條件之更新的區塊。
TryUpdateBlock 會在後續更新到達時更新同一個區塊,並指出該活動何時完成。 使用 UIAgentOptions.AddBlockHandler 註冊處理常式,然後為應用程式專屬區塊提供 BlockRenderer。 活動沒有預設的視覺表示。
AG-UI ACTIVITY_SNAPSHOT 和 ACTIVITY_DELTA 事件是這些更新的可能來源之一。
AGUIChatClient 透過 ChatResponseUpdate.RawRepresentation 公開原始事件。 例如,應用程式可以替換包含應用程式定義 complete 屬性的快照:
using System.Text.Json;
using Microsoft.AspNetCore.Components.AI;
using AGUI.Abstractions;
public sealed class ResearchActivityBlock : ActivityContentBlock
{
public string ActivityMessageId { get; set; } = "";
}
public sealed class ResearchActivityHandler
: ActivityHandler<ResearchActivityBlock>
{
protected override bool TryCreateBlock(
BlockMappingContext context,
ResearchActivityBlock state)
=> TryApplySnapshot(context, state, out _);
protected override bool TryUpdateBlock(
BlockMappingContext context,
ResearchActivityBlock state,
out bool isCompleted)
=> TryApplySnapshot(context, state, out isCompleted);
private static bool TryApplySnapshot(
BlockMappingContext context,
ResearchActivityBlock state,
out bool isCompleted)
{
isCompleted = false;
if (context.Update.RawRepresentation is not ActivitySnapshotEvent snapshot ||
(state.ActivityMessageId.Length > 0 &&
(state.ActivityMessageId != snapshot.MessageId ||
snapshot.Replace == false)))
{
return false;
}
state.ActivityMessageId = snapshot.MessageId;
state.ActivityType = snapshot.ActivityType;
state.Content = snapshot.Content;
isCompleted =
snapshot.Content.ValueKind == JsonValueKind.Object &&
snapshot.Content.TryGetProperty("complete", out var complete) &&
complete.ValueKind == JsonValueKind.True;
context.MarkUpdateHandled();
return true;
}
}
處理器會將 AG-UI 訊息 ID 儲存在區塊上,以便與後續的快照建立關聯。 改為使用 ActivityDeltaEvent 的應用程式,會先將其 RFC 6902 JSON Patch 作業套用至目前的 Content,再從 TryUpdateBlock 傳回。 應用程式會定義活動承載資料與完成語義;Components.AI 不包含 AG-UI 專屬的活動處理器或 JSON Patch 實作。
共用狀態
代理式使用者介面通常會與對話一同顯示共享工作空間,例如代理可以更新的食譜、文件、表單或計畫。
UIAgent<TState> 將這些資料以類型化、可觀察的 UI 狀態形式公開,與對話內容分開([Blazor] 新增代理生成 UI 狀態渲染(dotnet/aspnetcore #68333))。
應用程式會為其 ChatResponseUpdate 產生的 IChatClient 值設定狀態對應器。 在 AG-UI 整合中,代理伺服器會明確將所選工具的結果對應至 STATE_SNAPSHOT 或 STATE_DELTA 事件。
AGUIChatClient接著透過 ChatResponseUpdate.RawRepresentation 公開這些事件,讓 Blazor 應用程式可將其反序列化並呼叫 SetState:
using System.Text.Json;
using AGUI.Abstractions;
using Microsoft.AspNetCore.Components.AI;
var agent = new UIAgent<RecipeState>(chatClient, options =>
{
options.StateMapper = context =>
{
if (context.Update.RawRepresentation is StateSnapshotEvent snapshot &&
snapshot.Snapshot.Deserialize<RecipeState>(
JsonSerializerOptions.Web) is { } state)
{
context.SetState(state);
}
};
});
讀取當前值 agent.State.Value ,並訂閱 agent.State.OnChanged 當周圍元件需要重新渲染時。 狀態映射器也能處理其他IChatClient實作的應用程式專屬AIContent。
如需瞭解對應的 MAF 伺服器設定,包括將工具結果對應到狀態快照與增量,請參見 使用 AG-UI 進行狀態管理。
顯示預測性 UI 狀態
預測狀態允許應用程式在模型仍在產生狀態時,渲染代理人所提議的狀態變化,而不必替換已提交的狀態。 例如,當代理在工具參數中產生已編輯文件的完整內容時,介面可以逐步顯示該提案文件與差異。 當生成完成後,使用者可以接受或拒絕完成的提案,並還原已提交的文件。
AG-UI 伺服器整合可將狀態寫入工具的串流參數映射到臨時狀態事件。 已完成的工具呼叫引數即為具權威性的提案。 建立 UIAgent<TState> 時,將其狀態映射器設定為可將這些事件還原序列化,並呼叫 SetPredictiveState。
AgentState<TState> 然後保留先前已提交的值以供回滾([Blazor] 加入預測式狀態更新(dotnet/aspnetcore #68335)):
using System.Text.Json;
using AGUI.Abstractions;
using Microsoft.AspNetCore.Components.AI;
var agent = new UIAgent<DocumentState>(chatClient, options =>
{
options.StateMapper = context =>
{
if (context.Update.RawRepresentation is StateSnapshotEvent snapshot &&
snapshot.Snapshot.Deserialize<DocumentState>(
JsonSerializerOptions.Web) is { } predictedState)
{
context.SetPredictiveState(predictedState);
}
};
options.RegisterUIAction(AIFunctionFactory.Create(
ConfirmChanges,
name: "confirm_changes",
description: "Confirm the proposed document changes."));
});
範例也記錄了一個 confirm_changes 前端動作。 當動作出現時,自訂區塊渲染器會顯示接受與拒絕控制項。 渲染器會將使用者的選擇加入參數 accepted 並呼叫 UIActionBlock.InvokeAsync,執行註冊回調:
private string ConfirmChanges(bool accepted)
{
if (accepted)
{
agent.State.AcceptPredictiveState();
}
else
{
agent.State.RejectPredictiveState();
}
return accepted
? "The user accepted the changes."
: "The user rejected the changes.";
}
臨時值可立即從 agent.State.Value 取得,而 HasPendingPredictiveState 表示其尚未認可。 回呼會提交已完成的提案或還原基準狀態,而其回傳值則會在後續執行中將該決策回報給代理程式。 如果生成失敗、遭到取消,或在未作出決定的情況下結束,暫定值會自動還原。 對串流工具引數進行的伺服器端擷取與映射必須明確設定;請參閱 使用 AG-UI 進行狀態管理。
堅持並恢復對話
IConversationThread 會儲存已完成的回合,讓使用者介面能在元件或應用程式重新啟動後重新建立對話。 執行緒也可以保留協定中繼資料,例如用來延續由伺服器端擁有的 AG-UI 對話的 threadId 和先前的 runId。
建構 UIAgent 時傳遞執行緒,然後呼叫 UIAgent.RestoreAsync 或 AgentContext.RestoreAsync,以明確地將已儲存的更新重新套用至內容區塊和具型別的狀態([Blazor] 新增共享代理程式與使用者介面狀態(dotnet/aspnetcore #68334)):
var agent = new UIAgent(
chatClient,
options => options.Thread = conversationThread);
var restoredBlocks = await agent.RestoreAsync();
將執行緒傳遞給 UIAgent 可讓新完成的對話輪次持久保存,但不會自動還原先前的對話輪次。 關於MAF託管的 AG-UI 代理,請參見 AG-UI 對話連續性。
Blazor Hybrid
本章節會說明 Blazor Hybrid 的新功能。
隨著預覽功能開放,發布說明會在此區塊中呈現。
SignalR
本章節會說明 SignalR 的新功能。
SignalR 認證刷新
SignalR 連線可以重新整理認證,且不會在存取權杖到期時中斷連線。 伺服器會同時暴露一個 /refresh 端點 /negotiate ,並在協商回應中報告令牌壽命。 客戶端會在代幣到期前重新認證,因此先前因持有人代幣過期而關閉的樞紐連線仍可保持開啟。
在伺服器上每個集線器啟用這個功能。 伺服器可以透過回傳以下 OnAuthenticationRefresh值來檢查或拒絕刷新後的身份:
using System.Security.Claims;
app.MapHub<ChatHub>("/chat", options =>
{
options.EnableAuthenticationRefresh = true;
options.CloseOnAuthenticationExpiration = true;
// Optional: inspect the refreshed identity and decide whether to accept it.
options.OnAuthenticationRefresh = context =>
{
var previousSubject = context.PreviousUser.FindFirstValue("sub")
?? context.PreviousUser.FindFirstValue(ClaimTypes.NameIdentifier);
var newSubject = context.NewUser.FindFirstValue("sub")
?? context.NewUser.FindFirstValue(ClaimTypes.NameIdentifier);
return Task.FromResult(
previousSubject is not null &&
string.Equals(previousSubject, newSubject, StringComparison.Ordinal));
};
});
樞紐可透過覆寫 OnAuthenticationRefreshedAsync來回應更新的身份:
public class ChatHub : Hub
{
public override Task OnAuthenticationRefreshedAsync()
{
// The connection's User has been updated with the refreshed token.
return Task.CompletedTask;
}
}
.NET 用戶端預設開啟自動刷新功能,且可設定為 WithAuthenticationRefresh。 重新整理通知是 HubConnection 上的事件,而 RefreshAuthenticationAsync 會在應用程式取得新的宣告後立即要求重新整理:
await using var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chat")
.WithAuthenticationRefresh(options =>
{
// EnableAutoRefresh is true by default.
options.RefreshBeforeExpiration = TimeSpan.FromMinutes(1);
})
.Build();
connection.AuthenticationRefreshed += context => Task.CompletedTask;
connection.AuthenticationRefreshFailed += context => Task.CompletedTask;
await connection.StartAsync();
// Refresh immediately after acquiring a token with updated claims.
await connection.RefreshAuthenticationAsync();
從用戶端取消 hub 呼叫
SignalR用戶端可以取消一般非串流集線器方法的呼叫。 過去只有串流呼叫可以從用戶端取消。 現在,當您將 CancellationToken 傳遞給 InvokeAsync 並將其取消時,用戶端會傳送取消訊息,而伺服器上的中樞方法 CancellationToken 參數會被觸發。
// Client — canceling the token cancels the server-side invocation.
using var cts = new CancellationTokenSource();
var work = connection.InvokeAsync("LongRunningWork", cts.Token);
// ...
cts.Cancel();
// Hub — accept a CancellationToken to observe client cancellation.
public class WorkHub : Hub
{
public async Task LongRunningWork(CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
}
}
SignalR.NET 用戶端支援重定向後的認證刷新
SignalR .NET 用戶端擴充了SignalR驗證重新整理功能,使其在交涉重新導向到另一部伺服器時也能正常運作,此功能由 @MoChilia 貢獻。 此用戶端變更可支援重新導向伺服器,例如尚未啟用此功能的 Azure SignalR Service。
用戶端會在重新導向過程中保留 app-token 提供者,採用回應中的已重新整理傳輸權杖,並保留 tokenLifetimeSeconds,讓自動重新整理在原始權杖過期後仍維持排程。
感謝 @MoChilia 的貢獻!
SignalR TypeScript 用戶端支援認證更新
SignalR TypeScript 用戶端支援在不重新建立連線的情況下重新整理存取權杖。 它可以根據伺服器回報的令牌壽命排程刷新,或在應用程式取得更新申報後立即重新整理。
使用 withAuthenticationRefresh 設定自動重新整理。 在建置連線上登錄成功與失敗處理程序,並呼叫 refreshAuthentication 手動刷新:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/clock", { accessTokenFactory: getAccessToken })
.withAuthenticationRefresh({
enableAutoRefresh: true,
refreshBeforeExpirationInMilliseconds: 120_000,
})
.build();
connection.onAuthenticationRefreshed((context) => {
console.log(`New token lifetime: ${context.newTokenLifetimeInSeconds}`);
});
connection.onAuthenticationRefreshFailed((context) => {
console.error(context.error);
});
await connection.start();
// Refresh immediately after acquiring a token with updated claims.
await connection.refreshAuthentication();
最小化 API
本節介紹 Minimal API 的新功能。
端點過濾器會偵測到參數繫結失敗
當最小 API 端點已設定任何過濾器或過濾器工廠時,過濾器管線即使參數綁定失敗也會持續執行。 過濾器可以讀取 HttpContext.Response.StatusCode == 400 並替換自己的回應體。
在 Development 環境中,將 RouteHandlerOptions.ThrowOnBadRequest = false 設定為讓框架傳回 400,讓篩選器可觀察到,而不是將 BadHttpRequestException 擲回開發人員例外狀況頁面。 這在非Development 環境下已經是預設了。
感謝 @marcominerva 的貢獻!
C# 聯集類型
ASP.NET Core 支援 C# 聯集型別(C# 語言參考),這是 .NET 11 新增的,適用於任何System.Text.Json可用的地方:Minimal API 和 MVC 中的 JSON 請求與回應主體、SignalRJavaScript JsonHubProtocolBlazor 互操作、持久元件狀態,以及預先渲染的元件參數。
public union UnionIntString(int, string);
app.MapGet("/value", () => new UnionIntString(42));
Union 類型不支援非正體綁定來源,例如路由值、查詢字串、標頭和表單欄位。
對於 OpenAPI,回傳 union 的端點會以 anyOf 列出每種案例類型的結構來描述。 與多型型不同,聯集格不帶有 $type 判別子,因此每個格都會重複使用其獨立組件(例如 #/components/schemas/Dog),而非重複且前綴的組件。 ApiExplorer 會透過 JsonTypeInfoKind.Union 偵測 union 類型,因此該結構描述也會傳遞到 Swashbuckle 和 NSwag。 當多個案例序列化成相同的 JSON 形狀時,請用 [JsonUnion] 分類器將它們消歧。
SignalR 聯合體要求使用 JSON 集線協定;訊息包和 Newtonsoft.Json 協議不支援工會。
如需適用於 Blazor 應用程式的範例及其他資訊,請參閱〈元件概觀文章中的元件參數一節〉,以及〈動態轉譯的 ASP.NET Core Razor 元件文章中的傳遞參數一節〉。
最小 API 的非同步驗證
Minimal API 驗證現在支援非同步驗證器的端對端支援(dotnet/aspnetcore #66487、dotnet/aspnetcore #67183)。 預覽版 5 提供了非同步表單驗證的基礎模組。Blazor 預覽版 6 在基礎函式庫DataAnnotations(和AsyncValidationAttribute)中新增了非同步 IAsyncValidatableObject API,現在Microsoft.Extensions.Validation當端點驗證請求時會執行這些 API。
新增非同步規則最簡單的方法是自訂驗證屬性。 從 AsyncValidationAttribute 衍生並實作 IsValidAsync 查詢資料庫或呼叫遠端 API,且不會阻塞執行緒。 同步 IsValid 式也是抽象式的;當該屬性僅非同步驗證時,從中拋出:
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
public sealed class UniqueEmailAttribute : AsyncValidationAttribute
{
// Synchronous IsValid. This attribute validates asynchronously only.
protected override ValidationResult? IsValid(object? value, ValidationContext context) =>
throw new InvalidOperationException("Validate this attribute with IsValidAsync.");
protected override async Task<ValidationResult?> IsValidAsync(
object? value, ValidationContext context, CancellationToken cancellationToken)
{
var users = context.GetRequiredService<IUserService>();
if (value is string email && await users.EmailExistsAsync(email, cancellationToken))
{
return new ValidationResult("That email is already registered.");
}
return ValidationResult.Success;
}
}
像套用任何內建驗證屬性一樣套用 [UniqueEmail] 屬性。
若驗證跨越多個屬性或整個物件,則實作 IAsyncValidatableObject 並以 . 形式回傳結果 IAsyncEnumerable<ValidationResult>。 因為 IAsyncValidatableObject 擴展了 IValidatableObject,也實作了同步 Validate 方法。 當一個型別僅以非同步方式驗證時,請從 丟出 Validate ,這樣同步 API 不會悄悄跳過其驗證:
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
public class ReservationRequest : IAsyncValidatableObject
{
[Required]
public string Email { get; set; } = "";
public DateOnly Date { get; set; }
// Synchronous IValidatableObject. This type validates asynchronously only.
public IEnumerable<ValidationResult> Validate(ValidationContext context) =>
throw new InvalidOperationException("Validate this type with ValidateAsync.");
public async IAsyncEnumerable<ValidationResult> ValidateAsync(
ValidationContext context,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var rooms = context.GetRequiredService<IRoomService>();
if (!await rooms.HasAvailabilityAsync(Date, cancellationToken))
{
yield return new ValidationResult(
"No rooms are available on that date.", [nameof(Date)]);
}
}
}
暫存器驗證,框架會在端點執行前驗證請求:
builder.Services.AddValidation();
app.MapPost("/reservations", (ReservationRequest request) =>
Results.Ok(request));
驗證器盡可能同時運行:同一成員的非同步屬性會一起開始,集合項目會平行驗證,框架則保留成員、類型與 IValidatableObject 驗證之間的既有排序。
具有屬性的短路端點
新的 [ShortCircuit] 屬性會將端點標示為在路由之後立即執行,並略過其餘的中介軟體管線。 這是現有 ShortCircuit() 端點慣例的屬性形式,因此可以直接套用到 MVC 控制器和動作。
短路對於不需要驗證、CORS 或其他中介軟體的端點非常有用——例如健康檢查或 robots.txt 回應,且能避免執行這些中介軟體的成本。 端點仍然會執行並產生回應。 傳送一個可選的狀態碼,例如 [ShortCircuit(404)],以設定回應狀態碼。
[ApiController]
[Route("robots.txt")]
[ShortCircuit]
public class RobotsController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Content("User-agent: *\nDisallow:", "text/plain");
}
同一屬性適用於最小 API 端點,現有 ShortCircuit() 慣例也未曾改變:
app.MapGet("/health", [ShortCircuit] () => "Healthy");
感謝 @Porozhniakov 貢獻這個專題!
內建驗證在地化功能
Microsoft.Extensions.Validation 在地化驗證訊息與顯示名稱,無需獨立套件即可。 呼叫 AddLocalization 以註冊 IStringLocalizerFactory,接著呼叫 AddValidation,即可自動啟用本地化。 驗證來源產生器會將本地化查詢直接傳送到你的組裝語言中。
builder.Services.AddLocalization();
builder.Services.AddValidation();
[ValidatableType]
public class CustomerModel
{
[Display(Name = "CustomerName")] // resource key for the display name
[Required(ErrorMessage = "NameRequired")] // resource key for the message
public string? Name { get; set; }
}
預設情況下,金鑰會根據宣告該已驗證成員之類型的資源進行解析。 明確的 ErrorMessage 值,如 NameRequired 前述範例所示,是本地化嘗試的第一個資源鍵。 當屬性未指定 ErrorMessage時,本地化會嘗試從最細到最不具體的內建資源名稱慣例:
{DeclaringType}_{MemberName}_{AttributeType}_Error{DeclaringType}_{AttributeType}_Error{AttributeType}_Error
例如,在 CustomerModel.Name 上的 [Required] 屬性會對照 CustomerModel_Name_RequiredAttribute_Error、CustomerModel_RequiredAttribute_Error 或共用的 RequiredAttribute_Error 資源進行解析。 這允許將屬性的預設訊息在整個應用程式中翻譯一次。 若無資源解析,驗證會回退至屬性內建訊息。 請改用 ValidationOptions.LocalizerProvider 來解析共享資源檔案中的金鑰:
builder.Services.AddValidation(options =>
{
options.LocalizerProvider = (_, factory) => factory.Create(typeof(ValidationMessages));
});
本地化字串不一定要來自資源檔案。 註冊自訂 IStringLocalizerFactory 會將驗證訊息切換到該工廠的備份儲存庫,例如資料庫或 JSON 檔案。 使用者註冊的工廠優先於預設的資源檔案實作:
builder.Services.AddSingleton<IStringLocalizerFactory, DbStringLocalizerFactory>();
builder.Services.AddValidation();
已自行完成本地化的屬性(ErrorMessageResourceType、[Display(ResourceType = ...)])會完全略過整個管線。 需要將自己的值代入訊息範本的自訂屬性可以實作 IValidationMessageFormatter:
public sealed class DivisibleByAttribute : ValidationAttribute, IValidationMessageFormatter
{
public int Divisor { get; init; }
public string FormatMessage(CultureInfo culture, string template, string displayName)
=> string.Format(culture, template, displayName, Divisor); // {0} = name, {1} = divisor
}
同樣的在地化規則適用於最小 API 和 Blazor的驗證,因此訊息在模型使用處的任何地方都會以相同的方式本地化。
完整的專題報導可參考以下文章:
欲了解更多資訊,請參閱 將本地化支援新增至 Microsoft.Extensions.Validation(dotnet/aspnetcore #66646)。
驗證屬性不再是實驗性質
來自 SkipValidationAttribute 的 ValidatableTypeAttribute 和 Microsoft.Extensions.Validation 屬性不再標示為實驗性。 如果你為了使用任一屬性而隱藏 ASP0029,請移除該隱藏。
如需詳細資訊,請參閱下列資源:
OpenAPI
本節說明 OpenAPI 的新功能。
描述二進位檔案回應
ASP.NET Core 11 引入了對回傳二進位檔案回應操作產生 OpenAPI 描述的支援。 此支援將 FileContentResult 結果型別映射到帶有 type: string 和 format: binary 的 OpenAPI 架構。
使用 Produces<T> 帶有 T 的 FileContentResult 擴充方法來指定回應類型與內容類型:
app.MapPost("/filecontentresult", () =>
{
var content = "This endpoint returns a FileContentResult!"u8.ToArray();
return TypedResults.File(content);
})
.Produces<FileContentResult>(contentType: MediaTypeNames.Application.Octet);
產生的 OpenAPI 文件描述端點回應如下:
responses:
'200':
description: OK
content:
application/octet-stream:
schema:
$ref: '#/components/schemas/FileContentResult'
在 FileContentResult 中 components/schemas 定義為:
components:
schemas:
FileContentResult:
type: string
format: binary
OpenAPI 3.2.0 支援(破壞性變更)
Microsoft.AspNetCore.OpenApi 現在透過更新對 Microsoft.OpenApi 3.3.1 的相依性,支援 OpenAPI 3.2.0。 這次更新包含了底層函式庫的重大變更。 欲了解更多資訊,請參閱Microsoft。OpenApi 升級指南。
要產生 OpenAPI 3.2.0 文件,請在呼叫 AddOpenApi時指定版本:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_2;
});
後續更新則利用 3.2.0 規範中的新功能,例如對串流事件的項目結構支援。
感謝 @baywet 的貢獻!
產生的 OpenAPI 文件中的 HTTP QUERY
OpenAPI 文件產生現在能將 HTTP QUERY 視為已知的操作類型。 QUERY 是一種提議中的安全且冪等的請求方法,允許客戶端在描述搜尋條件時傳送請求主體;當查詢內容過大或結構過於複雜而無法放入 URL 時,這種方法就非常有用。 路由已可透過 MapMethods 接受任意動詞字串,而 OpenAPI 3.2 也在路徑項目物件中新增了 query 欄位,讓這項能力可在 OpenAPI 文件中加以描述。
請注意,query 只在 OpenAPI 3.2 文件中有效,因此請在 OpenApiVersion 中設定 OpenApiOptions。 在較早的 OpenAPI 版本中,query 作業會在 Path Item 物件中的 x-oai-additionalOperations 規格擴充內產生。
using Microsoft.OpenApi;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_2;
});
var app = builder.Build();
app.MapOpenApi();
app.MapMethods("/search", ["QUERY"], (SearchRequest request) =>
SearchService.Run(request));
app.Run();
在 OpenAPI 3.2 文件中,QUERY 作業會以內嵌方式描述,作為 get、post 及其他標準作業的同層項目:
"paths": {
"/search": {
"query": {
"requestBody": { ... },
"responses": { "200": { ... } }
}
}
}
在 OpenAPI 3.0 與 3.1 文件中,相同的作業會表示在 Path Item 上的 x-oai-additionalOperations 擴充功能下:
"paths": {
"/search": {
"x-oai-additionalOperations": {
"QUERY": {
"requestBody": { ... },
"responses": { "200": { ... } }
}
}
}
}
感謝 @kilifu 的貢獻!
檔案串流的結果類型出現在 OpenAPI 文件中
FileStreamResult, FileContentHttpResult, , FileStreamHttpResult 現在在生成的 OpenAPI 文件中以二進位字串結構描述,讓用戶端能看到串流檔案端點的精確回應形狀。 使用 .Produces<FileContentHttpResult>(contentType: "application/pdf")(或對應的 FileStreamHttpResult/FileStreamResult 型別)為端點加上註解,讓 OpenAPI 能辨識結果型別並產生二進位結構描述。
感謝 @marcominerva 的貢獻!
OpenAPI 架構更符合 ASP.NET Core 的行為
OpenAPI 產生功能現在能更準確地處理多種結構描述情況。 非本文列舉參數即使 HTTP JSON 選項設定了 JsonStringEnumConverter 命名原則,仍會保留原始的 C# 列舉成員名稱,因為查詢、路由、標頭和表單繫結使用的是 Enum.TryParse,而不是 JSON 序列化。 陣列結構參考 ID 現在使用有效的元件名稱,如 stringArray 和 TodoArray ,取代帶有陣列語法的名稱。
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(
new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower));
});
app.MapGet("/orders", (OrderStatus status) => Results.Ok(status));
在此配置下,身體結構仍可描述 OrderStatus.PendingReview 為 pending-review,而查詢參數結構則將被接受的值描述為 PendingReview。
最小 API 端點可針對相同的狀態碼支援多次呼叫 Produces 擴充方法,例如,指定 200 回應可能以 application/json 或 text/plain 的形式傳回,且具有不同的結構描述。 同樣的支援也適用於 MVC 控制器,透過多種 [ProducesResponseType] 屬性。
在先前版本中,框架將每個狀態碼壓縮為單一回應類型,並悄然刪除其餘部分,導致無法描述服務多種內容類型的端點。
Microsoft.AspNetCore.Mvc.ApiExplorer 現在會以確定性順序保留每個宣告的回應類型,而產生的 OpenAPI 文件會依媒體類型分別發出內容條目——當多個類型共享相同內容類型時,則會發出 anyOf 架構。
感謝 @marcominerva 提供陣列結構參考資料!
預設為 OpenAPI 3.2
產生的 OpenAPI 文件現在預設針對 OpenAPI 3.2。 文件會持續照常產生。 如果你需要針對尚未支援 OpenAPI 3.2 的工具使用較早版本,請明確設定文件版本。
若要鎖定較早版本,請在呼叫 AddOpenApi時指定:
builder.Services.AddOpenApi(options =>
{
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});
OpenAPI 3.2 對伺服器傳送事件的支援
回傳 SseItem<T> 的端點,會在產生的 OpenAPI 文件中,以 OpenAPI 3.2 的 itemSchema 結構描述 text/event-stream 回應。
itemSchema 會描述串流各事件的資料酬載結構,而不是退回使用一般的 string 結構描述。
app.MapGet("/todos/stream", (CancellationToken ct) =>
TypedResults.ServerSentEvents(GetTodosAsync(ct)))
.WithName("StreamTodos");
static async IAsyncEnumerable<SseItem<Todo>> GetTodosAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
foreach (var todo in Todos.All)
{
yield return new SseItem<Todo>(todo) { EventId = todo.Id.ToString() };
await Task.Delay(1000, ct);
}
}
將串流回傳至 TypedResults.ServerSentEvents。 直接回傳 IAsyncEnumerable<SseItem<T>> 的處理器會以 JSON 序列化,而非 SSE。 使用專用的 SseItem<T> 多載版本,不要使用 eventType。 若要對整個串流使用單一事件名稱,請傳入純 IAsyncEnumerable<T>,並搭配 eventType。
產生的 3.2 文件說明了事件酬載,其中 itemSchema 參照 #/components/schemas/Todo,並包含標準 SSE 的 event 和 id 字串欄位:
responses:
'200':
description: OK
content:
text/event-stream:
itemSchema:
type: object
required: [data]
properties:
data:
$ref: '#/components/schemas/Todo'
event: { type: string }
id: { type: string }
若事件承載資料是辨別聯集(C# 14 的預覽功能),OpenAPI 也會將該聯集的案例名稱作為 enum 產生在 event 欄位上。
選擇一個用於建置時 OpenAPI 文件產生的環境
建置時 OpenAPI 文件產生支援使用 OpenApiGenerationEnvironment MSBuild 屬性選擇應用程式環境。 屬性設定主機生成過程的環境,等同於設定 ASPNETCORE_ENVIRONMENT 環境 DOTNET_ENVIRONMENT 變數。 因此,環境特定的配置與文件轉換可以影響產生的 OpenAPI 文件,而無需在執行 dotnet build前設定環境變數。
在專案檔案中設定屬性:
<PropertyGroup>
<OpenApiGenerationEnvironment>Development</OpenApiGenerationEnvironment>
</PropertyGroup>
欲了解更多資訊,請參閱 產生 OpenAPI 文件。
感謝 @ldsenow 的貢獻!
OpenAPI 反映過時的 API
ASP.NET Core OpenAPI 產生功能會自動將作業、結構描述類型和結構描述屬性的 [Obsolete] 對應至 deprecated: true。 因此,API 用戶端與文件工具可在不使用自訂 OpenAPI 轉換器的情況下,呈現與 .NET 呼叫器相同的棄用資訊。
app.MapGet("/catalog/{id}", GetCatalogItem);
#pragma warning disable CS0618 // This example intentionally declares and maps obsolete APIs.
app.MapGet("/catalog/legacy/{id}", GetLegacyCatalogItem);
[Obsolete("Use /catalog/{id}.")]
static LegacyCatalogItem GetLegacyCatalogItem(int id) =>
new(id, $"Product {id}", $"SKU-{id:D4}");
static CatalogItem GetCatalogItem(int id) =>
new(id, $"Product {id}", $"SKU-{id:D4}");
public sealed record CatalogItem(
int Id,
string Name,
string StockKeepingUnit);
[Obsolete("Use CatalogItem.")]
public sealed record LegacyCatalogItem(
int Id,
string Name,
[property: Obsolete("Use StockKeepingUnit.")] string Sku);
#pragma warning restore CS0618
產生的文件標示遺留操作、回應結構及 Sku 已棄用屬性:
{
"paths": {
"/catalog/legacy/{id}": {
"get": {
"deprecated": true
}
}
},
"components": {
"schemas": {
"LegacyCatalogItem": {
"deprecated": true,
"properties": {
"sku": {
"deprecated": true
}
}
}
}
}
}
IOpenApiOperationTransformer 或 IOpenApiSchemaTransformer 可以覆寫特定 API 的已產生值。
感謝 @fickleEfrit 的貢獻!
身份驗證與授權
本章節會說明驗證和授權的新功能。
TimeProvider 支援 ASP.NET Core Identity
ASP.NET Core Identity 現在使用 TimeProvider 取代 DateTime 和 DateTimeOffset 來處理所有時間相關操作。 此變更使 Identity 元件更易測試,並在測試及專業情境中提供更佳的時間控制。
以下範例展示了如何使用假 TimeProvider 來測試 Identity 功能:
// In tests
var fakeTimeProvider = new FakeTimeProvider(
new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero));
services.AddSingleton<TimeProvider>(fakeTimeProvider);
services.AddIdentity<IdentityUser, IdentityRole>();
// Identity will now use the fake time provider
透過使用 TimeProvider,您可以更輕鬆地撰寫時間敏感 Identity 特性的確定性測試,如令牌到期、鎖定時間及安全印章驗證。
從認證器推斷通行金鑰顯示名稱
ASP.NET Core Identity 現在會根據通行金鑰的 AAGUID(認證器證明 GUID)自動推斷出友善的顯示名稱。 內建映射功能,適用於最常用的通行金鑰驗證器,包括 Google 密碼管理器、iCloud 鑰匙圈、Windows Hello、1Password 及 Bitwarden。
對於已知的驗證器,名稱會自動分配,且不會提示使用者。 對於未知的驗證器,使用者會被導向一個更名頁面。 透過在專案中的 PasskeyAuthenticators 字典中新增條目來擴充映射。
dotnet user-jwts 支援基於檔案的應用程式
這個 dotnet user-jwts 工具會建立簽署開發 JWT,讓你可以在不需要設定真實身份提供者的情況下呼叫應用程式的認證端點。 指令會 create 產生一個憑證,將其簽署金鑰儲存在應用程式的使用者秘密中,並列印該憑證作為持有憑證使用。 現在也可透過新的 app.cs 選項搭配以檔案為基礎的應用程式使用(僅有單一 --file 且沒有專案檔案):
dotnet user-jwts create --file app.cs
整個堆疊間一致的授權元資料
授權元資料可以表示為 IAuthorizeData、 、 AuthorizationPolicy或屬性 IAuthorizationRequirementData 。 MVC 篩選條件、SignalR Hub 方法,以及 Blazor 的 AuthorizeView 和 AuthorizeRouteView,都會一致地套用這三種形式。
新的 AuthorizationPolicy.CombineAsync 超載是共享實作:
public class AuthorizationPolicy
{
public static Task<AuthorizationPolicy?> CombineAsync(
IAuthorizationPolicyProvider policyProvider,
IEnumerable<object> metadata);
}
MVC、SignalR 和 Blazor 會在內部使用此多載。 同時實作 IAuthorizeData 和 IAuthorizationRequirementData 的自訂屬性,只會對決策貢獻一次。 帶有 EnableEndpointRouting = false 的舊版 MVC 路徑維持不變。
協商認證使用 TLS 通道綁定
在 Kestrel 上,Negotiate 驗證會針對 HTTPS 連線使用 TLS 端點通道繫結權杖。 認證處理程序將令牌提供給底層的 Kerberos 或 NTLM 交換,並在多輪驗證中保留該憑證。
不需要任何設定變更。 非 HTTPS 連線以及沒有通道綁定令牌的 HTTPS 連線,則會繼續使用現有的行為。
實驗性裝置綁定會話憑證支援
這很重要
該Microsoft.AspNetCore.Authentication.DeviceBoundSessions套件仍屬實驗性質,並在 .NET 11 及規範穩定前持續提供預發布狀態。
裝置綁定會話憑證(DBSC)規範定義了一種協定,將會話刷新綁定到瀏覽器持有的私鑰。 應用程式會發出一個短暫的會話 cookie,瀏覽器必須提供簽署的持有證明才能重新更新。 複製的會話 cookie 可能會持續使用直到到期,但沒有裝置金鑰的攻擊者無法利用它來延長會話。
ASP.NET Core 在套件Microsoft.AspNetCore.Authentication.DeviceBoundSessions中新增了實驗性的伺服器端 DBSC 實作。 認證元件會疊加在現有 cookie 的認證方案之上,管理註冊與刷新端點、路徑範圍的刷新 cookie,以及短暫的會話 cookie。
新增 Microsoft.AspNetCore.Authentication.DeviceBoundSessions 套件後,將 DBSC 配置於現有 cookie 的認證方案之上:
builder.Services
.AddAuthentication("Application")
.AddCookie("Application")
.AddDeviceBoundSession("Application", options =>
{
options.ShortLivedCookieExpiration = TimeSpan.FromMinutes(10);
});
目前瀏覽器支援需要實驗性的 DBSC 實作。 欲了解更多資訊,請參閱 Chrome 的 DBSC 文件。
其他
本節描述 .NET 11 中的各種新功能。
IOutputCachePolicyProvider 介面
.NET 11 中的 ASP.NET Core 提供了 IOutputCachePolicyProvider 介面,用於實作自訂輸出快取政策選擇邏輯。 透過此介面,應用程式可以判斷預設的基底快取政策,檢查是否有命名的政策,並支援需要動態解析政策的進階情境。 例如從外部設定來源、資料庫載入政策,或套用租戶專屬的快取規則。
以下為顯示IOutputCachePolicyProvider介面的程式碼:
public interface IOutputCachePolicyProvider
{
IReadOnlyList<IOutputCachePolicy> GetBasePolicies();
ValueTask<IOutputCachePolicy?> GetPolicyAsync(string policyName);
}
感謝 @lqlive 的貢獻!
WSL 中的自動信任開發憑證
開發憑證設定現在會自動信任 WSL(Windows 子系統 Linux 版)環境中的憑證。 當你在 WSL 中執行 dotnet dev-certs https --trust 時,憑證會自動安裝並在 WSL 環境及 Windows 中被信任,避免手動設置信任。
# Automatically trusts certificates in both WSL and Windows
dotnet dev-certs https --trust
此改進簡化了使用 WSL 的開發體驗,消除了在 Windows 上 Linux 環境開發者常見的摩擦點。
感謝 @StickFun 的貢獻!
ASP.NET Core 的原生 OpenTelemetry 追蹤
ASP.NET Core 現在原生地將 OpenTelemetry 語意慣例屬性加入 HTTP 伺服器活動,與 OpenTelemetry HTTP 伺服器 span 規範相符。 所有必要的屬性預設包含,與先前僅能透過 OpenTelemetry.Instrumentation.AspNetCore 函式庫取得的元資料相符。
要收集內建的追蹤資料,請訂閱 OpenTelemetry 設定中的 Microsoft.AspNetCore 活動來源:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("Microsoft.AspNetCore")
.AddConsoleExporter());
不需要額外的儀器函式庫(如 OpenTelemetry.Instrumentation.AspNetCore)。 框架現在直接在請求活動上填充語意慣例屬性,例如 http.request.method、 url.path、 http.response.status_codeserver.address和 。
如果你不想讓 OpenTelemetry 屬性加入活動,可以把 Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData AppContext 切換設為 true 來關閉。
效能改善
Kestrel的 HTTP/1.1 請求解析器現在使用非拋出的程式碼路徑來處理錯誤的請求。 解析器不會丟 BadHttpRequestException 出每次解析失敗,而是回傳一個結果結構體,表示成功、不完整或錯誤狀態。 在許多錯誤形式的請求情境中——如埠掃描、惡意流量或設定錯誤的用戶端——這可消除昂貴的例外處理開銷,並提升吞吐量多達 20-40%。 這不會影響有效的請求處理。
HTTP 日誌中介軟體現在會將 ResponseBufferingStream 實例池化,當啟用回應體日誌或攔截器時,減少每個請求的分配。
Zstandard 回應壓縮與請求解壓縮
ASP.NET Core 現在支援 Zstandard (zstd),用於回應壓縮與請求解壓縮。 此系統為現有的回應壓縮與請求解壓縮中介軟體新增 zstd 支援,並預設啟用 zstd。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression();
builder.Services.AddRequestDecompression();
builder.Services.Configure<ZstandardCompressionProviderOptions>(options =>
{
options.CompressionOptions = new ZstandardCompressionOptions
{
Quality = 6 // 1-22, higher = better compression, slower
};
});
感謝 @manandre 的貢獻!
HTTP/3 會更早開始處理請求
Kestrel 現在可以直接處理 HTTP/3 請求,無需先等待控制串流和 SETTINGS 幀,從而降低新連線的首次請求延遲。
MCP 伺服器範本隨 .NET SDK 一同附帶
模型情境協定(MCP)是一項開放標準,AI 應用程式與代理程式(如 Visual Studio、Visual Studio Code 及 GitHub Copilot)用以透過一致的介面發現並呼叫外部工具、資料與服務。 MCP 伺服器會提供你自己的功能,例如自訂工具或資料來源的存取權,讓 AI 主機能代表使用者叫用這些功能。
當你想建立一個整合程式碼或服務與 AI 驅動工具的 C# MCP 伺服器時,可以使用這個 mcpserver 範本。 生成的專案使用官方的 C# SDK for MCP ,並包含一個可運作的範例工具,讓你有個可執行的起點,可以用自己的工具擴充。
mcpserver 專案範本,過去僅能透過安裝 Microsoft.McpServer.ProjectTemplates 取得,現在則以 .NET SDK 的捆綁範本形式提供:
dotnet new mcpserver -o MyMcpServer
將範本移至 ASP.NET Core 後,便可透過 dotnet new list 找到它,無須另外安裝,且其維護也能與其餘 Web 堆疊保持一致。
欲了解更多資訊,請參閱 C# 中的「建構模型情境協定(MCP)伺服器」。
Kestrel 中的 TLS 握手可觀測性
有兩個相關變更讓 TLS 連線的診斷與自訂變得更容易。Kestrel
ITlsHandshakeFeature 現在會公開一個 Exception 屬性,其中包含 TLS 交握失敗期間所擲出的例外,讓中介軟體和記錄機制可以記錄連線失敗的原因,而不是只在堆疊較上層看到單獨的 IOException。 握手失敗後,該功能仍可繼續運作——Kestrel 會在底層 SslStream 被處置之前,擷取相關欄位的快照。
TlsClientHelloBytesCallback上的HttpsConnectionAdapterOptions選項已改為連線中介軟體。 先前的回呼函式形式現已淘汰;請改為透過新的 ListenOptions.UseTlsClientHelloListener 延伸設定 ClientHello 檢查。 以下範例同時使用這兩個功能——連線中介軟體在握手後讀取 ITlsHandshakeFeature.Exception ,並在 UseTlsClientHelloListener TLS 前檢查 ClientHello:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.Use(next => async context =>
{
await next(context);
var tlsHandshakeFeature = context.Features.Get<ITlsHandshakeFeature>();
if (tlsHandshakeFeature?.Exception is { } ex)
{
Console.WriteLine($"[TLS Handshake Failed] ConnectionId={context.ConnectionId}, Exception={ex.GetType().Name}: {ex.Message}");
}
});
// UseTlsClientHelloListener must be called before UseHttps()
listenOptions.UseTlsClientHelloListener((connection, clientHelloBytes) =>
{
Console.WriteLine($"TLS Client Hello received on {connection.ConnectionId}, {clientHelloBytes.Length} bytes");
});
listenOptions.UseHttps();
});
});
回應壓縮一律輸出 Vary: Accept-Encoding
現在,當啟用壓縮時,回應壓縮中介軟體會在每個回應中加入 Vary: Accept-Encoding,即使回應本身未經壓縮也是如此。 這可防止共享快取和 CDN 將經壓縮的內容傳送給未要求壓縮內容的用戶端(反之亦然)。
感謝 @pedrobsaila 的貢獻!
啟用執行時非同步以支援共享框架函式庫
ASP.NET Core 中僅屬於共用架構的程式庫現在已在 runtime-async 上使用 net11.0+ 功能進行編譯。 Runtime-async 會讓執行階段而非 C# 編譯器為 async/await 產生狀態機,這可減少每個 await 的配置,並改進診斷。 這是內部程式碼生成變更,對公開 API 沒有影響——針對 net11.0 的應用程式在呼叫受影響的 ASP.NET Core 函式庫時會自動受益。
同時作為共享框架成員及獨立 NuGet 套件發佈的函式庫會被排除,因為 runtime-async 與 WebAssembly 不相容,否則會導致這些套件的 Wasm 取用端無法運作。
因為 runtime-async 改變了 ASP.NET Core 堆疊中很大一部分的 async/await 產生方式,請以此預覽版測試你的應用程式;如果遇到非預期行為,尤其是在例外狀況堆疊、ExecutionContext/ 流程,或任何看起來像是 .NET 10 回歸問題的情況下,請AsyncLocal。
速率限制中介軟體會回傳準確的 Retry-After 標頭
FixedWindowRateLimiter 現在會回報一個 RetryAfter 中繼資料值,能準確反映下一個視窗邊界。 在其 Retry-After 回呼中將這些中繼資料傳播至 OnRejected 回應標頭的應用程式,現在無需修改程式碼,即可自動產生正確的重試間隔。
System.Threading.RateLimiting 中的其他修正解決了 TokenBucketRateLimiter 在取得零許可時不當處理部分代幣補充的問題,並改進了由 CreateChained 傳回的鏈式速率限制器,使其能正確轉送其內部限制器的閒置持續時間與補充行為。
如需速率限制中介軟體的概觀,請參閱 ASP.NET Core 中的速率限制中介軟體。
感謝 @asbjornvad 和 @apoorvdarshan 的貢獻!
Kestrel 套用拖車標頭逾時
Kestrel 現在會將 RequestHeadersTimeout 套用於未完成傳送標頭區塊的分段 HTTP/2 和 HTTP/3 尾端標頭。 保護初始請求標頭的相同逾時設定,現在也會防止連線在 Kestrel 等待尾端標頭 HEADERS 訊框完成期間無限期持續開啟。
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
});
TLS 通道綁定令牌存取來自 ITlsConnectionFeature
使用 TLS 的應用程式可以讀取連線的通道綁定令牌,以防禦中繼攻擊:
using System.Security.Authentication.ExtendedProtection;
app.Use(async (context, next) =>
{
var tls = context.Features.Get<ITlsConnectionFeature>();
if (tls is not null && tls.TryGetChannelBindingBytes(
ChannelBindingKind.Endpoint,
out ReadOnlyMemory<byte> cbt))
{
// Compare cbt against the token the client presented during authentication.
}
await next(context);
});
Kestrel 會傳回來自 SslStream.TransportContext.GetChannelBinding 的繫結。 IIS 和 HTTP.sys 會從要求中傳回它。 在 HTTP.sys 上,HttpSysOptions.HttpAuthenticationHardeningLevel 會控制延伸保護與通道繫結權杖的公開:
-
Legacy關閉通道綁定驗證,且不會暴露該代幣。 -
Medium預設值會暴露該代幣並在提供時驗證,但容忍其缺失。 -
Strict對於認證請求需要該令牌,且未使用令牌則拒絕請求。 如果作業系統無法套用該設定,它在啟動時也會失敗,而Legacy和Medium則會記錄設定失敗並繼續執行。
重大突破性變更
請使用Breaking changes in .NET中的文章,找出可能影響升級至新版 .NET 的應用程式的破壞性變更。