共用方式為


.NET 10 中 ASP.NET Core 的新功能

本文強調 .NET 10 中 ASP.NET Core 中最重要的變更,其中包含相關文件的連結。

如需重大變更,請參閱 .NET 中的重大變更

Blazor

本節說明 Blazor的新功能。

全新及更新的Blazor Web App安全性範例

我們已新增並更新 Blazor Web App 下列文章中連結的安全性範例:

我們的所有 OIDC 和 Entra 範例解決方案現在都包含個別的 Web API 專案 (MinimalApiJwt),以示範如何安全地設定和呼叫外部 Web API。 呼叫 web API 的示範包括使用令牌處理程式及具名 HTTP 用戶端以整合 OIDC 身分識別提供者,或使用 Microsoft Entra ID 的 Microsoft Identity Web 套件/API。

範例解決方案會在其 Program 檔案中的 C# 程式代碼中設定。 若要從應用程式配置檔設定解決方案(例如,appsettings.json),請參閱 OIDC 或 Entra 文章中的 新的JSON 組態提供者(應用程式設定)一節。

我們的 Entra 文章和範例應用程式也包含下列方法的新指引:

QuickGrid RowClass 參數

使用新的 RowClass 參數,根據行項目,將樣式表類別套用到網格的行。 在下列範例中,每一列都會呼叫 GetRowCssClass 方法,根據列中的項目條件來套用樣式類別:

<QuickGrid ... RowClass="GetRowCssClass">
    ...
</QuickGrid>

@code {
    private string GetRowCssClass(MyGridItem item) =>
        item.IsArchived ? "row-archived" : null;
}

如需詳細資訊,請參閱 ASP.NET 核心 Blazor 『QuickGrid' 元件

將 Blazor 腳本作為靜態網頁資產

在舊版 .NET 中,Blazor 腳本會從 ASP.NET Core 共用架構中的內嵌資源提供。 在 .NET 10 或更新版本中,Blazor 腳本作為靜態 Web 資產,自動進行壓縮和指紋處理。

Blazor框架會包含腳本(blazor.web.jsblazor.server.js),如果專案至少包含一個 Razor 元件檔案(.razor)。 如果您的應用程式需要 Blazor 腳本,但未至少包含一個元件,請在應用程式的專案檔中加入以下 MSBuild 屬性,以強制無條件地包含該腳本:

<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>

如需詳細資訊,請參閱下列資源:

路由範本重點

屬性[Route]現在支援路由語法醒目提示,以協助可視化路由範本的結構:

計數器值的路由屬性路由範本模式會顯示語法醒目提示

先前,NavigationManager.NavigateTo 滾動至頁面的頂端以進行同頁面導航。 .NET 10 中已變更此行為,因此流覽至相同頁面時,瀏覽器不再捲動到頁面頂端。 這意味著當更新目前頁面的位址(例如變更查詢字串或片段)時,視窗不再被重置。

重新連線 UI 元件已新增至 Blazor Web App 項目範本

Blazor Web App 專案範本現在包含 ReconnectModal 元件,內含樣式表和 JavaScript 檔案,以便在用戶端失去與伺服器的 WebSocket 連線時,提供開發者更多對重新連線 UI 的控制權。 元件不會使用程式化的方式插入樣式,這樣做可以確保符合 style-src 原則下更嚴格的內容安全政策(CSP)設定。 在舊版中,預設的重新連線 UI 是由架構所建立,因此可能會導致 CSP 違規。 請注意,當應用程式未定義重新連線 UI 時,預設重新連線 UI 仍會作為後援使用,例如使用專案範本的 ReconnectModal 元件或類似的自定義元件。

新的重新連線 UI 功能:

  • 除了在重新連線 UI 元素上設定特定的 CSS 類別來指出重新連線狀態之外,新的 components-reconnect-state-changed 事件會分派以進行重新連線狀態變更。
  • 程式代碼能更好地區分重新連線過程的各個階段,以及指出由 CSS 類別和新事件表示的新重新連線狀態「retrying」。

如需詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指引

使用 NavLinkMatch.All 時,請忽略查詢字串和片段

使用 NavLink 參數的 NavLinkMatch.All 值時,Match 元件現在會忽略查詢字串和片段。 這表示如果 URL 路徑符合,但查詢字串或片段變更,則連結會保留 active 類別。 若要恢復到原始行為,請使用設定為 Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext

您也可以在 ShouldMatch 上覆寫 NavLink 方法,以自訂匹配行為:

public class CustomNavLink : NavLink
{
    protected override bool ShouldMatch(string currentUriAbsolute)
    {
        // Custom matching logic
    }
}

欲了解更多資訊,請參閱 ASP.NET 核心 Blazor 導航

關閉 QuickGrid 欄位設定

您現在可以使用新的 QuickGrid 方法來關閉 HideColumnOptionsAsync 欄位選項用戶介面。

下列範例會使用 HideColumnOptionsAsync 方法,在套用標題篩選器後立即關閉欄位選項 UI:

<QuickGrid @ref="movieGrid" Items="movies">
    <PropertyColumn Property="@(m => m.Title)" Title="Title">
        <ColumnOptions>
            <input type="search" @bind="titleFilter" placeholder="Filter by title" 
                @bind:after="@(() => movieGrid.HideColumnOptionsAsync())" />
        </ColumnOptions>
    </PropertyColumn>
    <PropertyColumn Property="@(m => m.Genre)" Title="Genre" />
    <PropertyColumn Property="@(m => m.ReleaseYear)" Title="Release Year" />
</QuickGrid>

@code {
    private QuickGrid<Movie>? movieGrid;
    private string titleFilter = string.Empty;
    private IQueryable<Movie> movies = new List<Movie> { ... }.AsQueryable();
    private IQueryable<Movie> filteredMovies => 
        movies.Where(m => m.Title!.Contains(titleFilter));
}

預設啟用 HttpClient 回應串流

在先前 Blazor 版本中,HttpClient 要求的回應串流是選擇性啟用的。 現在,預設會啟用回應串流。

這是重大變更,因為呼叫 HttpContent.ReadAsStreamAsync 用於 HttpResponseMessage.Contentresponse.Content.ReadAsStreamAsync()) 時,會返回 BrowserHttpReadStream ,而不再返回 MemoryStreamBrowserHttpReadStream 不支援同步作業,例如 Stream.Read(Span<Byte>)。 如果您的程式碼使用同步作業,您可以選擇不使用回應串流,或自行將 Stream 複製到 MemoryStream

若要關閉全域回應串流,請使用下列其中一種方法:

  • 屬性 <WasmEnableStreamingResponse> 新增至項目檔, 值為 false

    <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
    
  • DOTNET_WASM_ENABLE_STREAMING_RESPONSE 環境變數設定為 false0

若要取消個別請求的回應串流,請於SetBrowserResponseStreamingEnabled上設定falseHttpRequestMessagerequestMessage在下列範例中)。

requestMessage.SetBrowserResponseStreamingEnabled(false);

如需詳細資訊,請參閱 HttpClientHttpRequestMessage,以及 Fetch API 要求選項(呼叫 Web API 一文)

客戶端指紋識別

.NET 9 的版本引入了伺服器端靜態資產指紋識別,並引入了映射靜態資產路由端點慣例()、Blazor Web App 元件和 MapStaticAssets 屬性(),以解析指紋化的 JavaScript 模組。 針對 .NET 10,您可以選擇參與獨立 Blazor WebAssembly 應用程式的 JavaScript 模組用戶端指紋識別。

在建置/發佈期間的獨立 Blazor WebAssembly 應用程式中,架構會覆寫 index.html 中的佔位符,並在建置期間以計算出的值來為靜態資產建立指紋。 指紋會被嵌入 blazor.webassembly.js 腳本檔案名稱中。

下列標記必須存在於檔案中 wwwroot/index.html ,才能採用指紋功能:

<head>
    ...
+   <script type="importmap"></script>
</head>

<body>
    ...
-   <script src="_framework/blazor.webassembly.js"></script>
+   <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

</html>

在項目檔(.csproj)中,將<OverrideHtmlAssetPlaceholders>屬性設定為true:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
+   <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
  </PropertyGroup>
</Project>

任何在index.html中具有指紋標記的腳本都會由框架進行指紋識別。 例如,在應用程式的scripts.js資料夾中名為wwwroot/js的腳本檔案,會在副檔名前加上#[.{fingerprint}] 以進行指紋處理(.js):

<script src="js/scripts#[.{fingerprint}].js"></script>

若要在獨立JS應用程式中指紋其他Blazor WebAssembly模組,請使用<StaticWebAssetFingerprintPattern>應用程式項目檔 (.csproj) 中的屬性。

在下列範例中,會針對應用程式中所有開發人員提供的 .mjs 檔案新增指紋:

<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs" 
  Expression="#[.{fingerprint}]!" />

檔案會自動放入匯入地圖。

  • 自動為 Blazor Web App CSR 進行操作。
  • 根據上述指示,在獨立 Blazor WebAssembly 應用程式中啟用模組指紋時。

解析 JavaScript Interop 的匯入時,瀏覽器會使用導入地圖來解析具指紋的文件。

在獨立 Blazor WebAssembly 應用程式中設定環境

檔案 Properties/launchSettings.json 不再用來控制獨立 Blazor WebAssembly 應用程式中的環境。

從 .NET 10 開始,在應用程式項目檔(<WasmApplicationEnvironmentName>)中使用 .csproj 屬性來設定環境。

下列範例會將應用程式的環境設定為 Staging

<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

預設環境為:

  • Development 用於建構。
  • Production 用於發佈。

欲了解更多資訊,請參閱 ASP.NET 核心 Blazor 環境

內嵌開機組態檔

Blazor的開機組態,在 .NET 10 版本之前存在於名為 blazor.boot.json的檔案中,已內嵌到腳本中 dotnet.js 。 這隻會影響直接與檔案互動的 blazor.boot.json 開發人員,例如開發人員:

目前,上述方法沒有記載的取代策略。 如果您需要上述任一策略,請使用任一文章底部的 [開啟文件問題 ] 鏈接,開啟描述案例的新文件問題。

宣告式模型用於在元件及服務中持續保存狀態

您現在可以使用 [PersistentState] 屬性,以宣告方式指定要從元件和服務保留的狀態。 具有此屬性的屬性會在預先呈現期間使用 PersistentComponentState 服務自動保存。 當元件以互動方式轉譯或服務具現化時,就會擷取狀態。

在舊 Blazor 版中,使用 PersistentComponentState 服務預先呈現期間保存元件狀態涉及大量程序代碼,如下列範例所示:

@page "/movies"
@implements IDisposable
@inject IMovieService MovieService
@inject PersistentComponentState ApplicationState

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    public List<Movie>? MoviesList { get; set; }
    private PersistingComponentStateSubscription? persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<List<Movie>>(nameof(MoviesList), 
            out var movies))
        {
            MoviesList = await MovieService.GetMoviesAsync();
        }
        else
        {
            MoviesList = movies;
        }

        persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
        {
            ApplicationState.PersistAsJson(nameof(MoviesList), MoviesList);
            return Task.CompletedTask;
        });
    }

    public void Dispose() => persistingSubscription?.Dispose();
}

現在可以使用新的宣告式模型來簡化此程式碼:

@page "/movies"
@inject IMovieService MovieService

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    [PersistentState]
    public List<Movie>? MoviesList { get; set; }

    protected override async Task OnInitializedAsync()
    {
        MoviesList ??= await MovieService.GetMoviesAsync();
    }
}

狀態可以針對相同類型的多個元件進行序列化,您可以在服務中建立宣告式狀態,並透過在元件產生器RegisterPersistentService 上使用Razor,具備自定義服務類型和呈現模式,以便於整個應用程式中使用AddRazorComponents。 如需詳細資訊,請參閱 ASP.NET Core Blazor 預先呈現的狀態持續性

新的 JavaScript Interop 功能

Blazor 新增下列 JS 互操作功能的支援:

下列異步方法可用於IJSRuntimeIJSObjectReference,其範圍行為與現有的IJSRuntime.InvokeAsync方法相同:

  • InvokeConstructorAsync(string identifier, object?[]? args):以異步方式叫用指定的 JS 建構函式。 函式是透過new運算子來呼叫。 在下列範例中, jsInterop.TestClass 是具有建構函式的類別,而 classRefIJSObjectReference

    var classRef = await JSRuntime.InvokeConstructorAsync("jsInterop.TestClass", "Blazor!");
    var text = await classRef.GetValueAsync<string>("text");
    var textLength = await classRef.InvokeAsync<int>("getTextLength");
    
  • GetValueAsync<TValue>(string identifier):以異步方式讀取指定 JS 之屬性的值。 屬性不能是 set僅限屬性。 假如屬性不存在,則會擲回JSException。 下列範例會從資料屬性傳回值:

    var valueFromDataPropertyAsync = await JSRuntime.GetValueAsync<int>(
      "jsInterop.testObject.num");
    
  • SetValueAsync<TValue>(string identifier, TValue value):以異步方式更新指定 JS 屬性的值。 屬性不能是 get僅限屬性。 如果未在目標對象上定義 屬性,則會建立 屬性。 如果屬性存在但不可寫入,或無法將新屬性增加到物件中,則會拋出JSException。 在下列範例中,如果值不存在, num 則會在 上 testObject 建立 ,值為 30:

    await JSRuntime.SetValueAsync("jsInterop.testObject.num", 30);
    

對於採用 CancellationToken 自變數或 TimeSpan 逾時自變數的每個上述方法,都可以使用多載。

下列同步方法可在IJSInProcessRuntimeIJSInProcessObjectReference上使用,其作用域行為與現有IJSInProcessObjectReference.Invoke方法相同。

  • InvokeConstructor(string identifier, object?[]? args):同步叫用指定的 JS 建構函式。 函式是透過new運算子來呼叫。 在下列範例中, jsInterop.TestClass 是具有建構函式的類別,而 classRefIJSInProcessObjectReference

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var classRef = inProcRuntime.InvokeConstructor("jsInterop.TestClass", "Blazor!");
    var text = classRef.GetValue<string>("text");
    var textLength = classRef.Invoke<int>("getTextLength");
    
  • GetValue<TValue>(string identifier):同步讀取指定 JS 屬性的值。 屬性不能是 set僅限屬性。 假如屬性不存在,則會擲回JSException。 下列範例會從資料屬性傳回值:

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var valueFromDataProperty = inProcRuntime.GetValue<int>(
      "jsInterop.testObject.num");
    
  • SetValue<TValue>(string identifier, TValue value):同步更新指定 JS 屬性的值。 屬性不能是 get僅限屬性。 如果未在目標對象上定義 屬性,則會建立 屬性。 如果屬性存在但不可寫入,或無法將新屬性增加到物件中,則會拋出JSException。 在下列範例中,如果值不存在, num 則會在 上 testObject 建立 ,值為 20:

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    inProcRuntime.SetValue("jsInterop.testObject.num", 20);
    

如需詳細資訊,請參閱文章 從 .NET 方法呼叫 JavaScript 函式 的下列各節:

Blazor WebAssembly 效能分析與診斷計數器

Blazor WebAssembly 應用程式有新的效能分析和診斷計數器可供使用。 如需詳細資訊,請參閱下列文章:

預先載入的 Blazor 架構靜態資產

在 Blazor Web App中,架構靜態資產會自動使用 Link 標頭預先載入,這可讓瀏覽器在擷取和轉譯初始頁面之前預先載入資源。 在獨立 Blazor WebAssembly 應用程式中,框架資產會在瀏覽器 index.html 頁面處理初期被排程為高優先級的下載和快取。

如需詳細資訊,請參閱 ASP.NET Core Blazor 靜態檔案

選擇加入以避免在靜態伺服器端轉譯期間使用NavigationExceptionNavigationManager.NavigateTo

在靜態伺服器端轉譯 (靜態 SSR) 期間呼叫會 NavigationManager.NavigateTo 擲回 NavigationException,在轉換為重新導向回應之前,會中斷執行。 這可能會在偵錯過程中造成混亂,並且與互動式渲染行為不一致,其中程式碼 after NavigateTo 繼續正常執行。

在 .NET 10 中,您可以將 MSBuild 屬性設定 <BlazorDisableThrowNavigationException>true 應用程式的專案檔中,以避免在靜態 SSR 期間擲回例外狀況:

<PropertyGroup>
  <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>

設定 MSBuild 屬性後,在靜態 SSR 期間呼叫 NavigationManager.NavigateTo 不會再擲回 NavigationException. 相反地,它會執行導覽,而不擲回例外狀況,以一致的方式來符合互動式渲染。 之後 NavigationManager.NavigateTo 的程式碼會在重新導向發生之前執行。

.NET 10 Blazor Web App 專案範本預設會將 MSBuild 屬性設定為 true 。 建議更新至 .NET 10 的應用程式使用新的 MSBuild 屬性,並避免先前的行為。

如果使用 MSBuild 屬性,則應該更新依賴擲回的程式碼 NavigationException 。 在 .NET 10 發行之前的Blazor專案範本預設 UI 中IdentityBlazor Web App,會IdentityRedirectManager擲回 InvalidOperationException after 呼叫RedirectTo,以確保在互動式轉譯期間未叫用方法。 現在應該在使用 MSBuild 屬性時移除此例外狀況和 [DoesNotReturn] 屬性 。 如需詳細資訊,請參閱 從 .NET 9 中的 ASP.NET Core 移轉至 .NET 10 中的 ASP.NET Core

Blazor 路由器具有 NotFoundPage 參數

Blazor 現在提供改善的方式,可在流覽至不存在的頁面時顯示「找不到」頁面。 您可以使用NavigationManager.NotFound參數將頁面類型傳遞至Router元件,以指定在NotFoundPage被叫用時要轉譯的頁面,該操作會在下一節中描述。 此功能支援路由,跨程式碼狀態碼頁重新執行中介軟體運作,甚至與非Blazor情境相容。

在 .NET 10 或更新版本中,NotFound 轉譯片段 (<NotFound>...</NotFound>) 不受支援。

<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>This content is ignored because NotFoundPage is defined.</NotFound>
</Router>

專案 Blazor 範本現在預設會包含 NotFound.razor 頁面。 每當在您的應用程式中呼叫 NavigationManager.NotFound 時,此頁面就會自動呈現,使您能更輕鬆地處理遺漏路由,並提供一致性的使用者體驗。

欲了解更多資訊,請參閱 ASP.NET 核心 Blazor 導航

用於靜態 SSR 和全域互動渲染的未找到回應NavigationManager

現在 NavigationManager 包含一個 NotFound 方法來處理在靜態伺服器端轉譯(靜態 SSR) 或全域互動式轉譯期間找不到要求資源的案例:

  • 靜態伺服器端轉譯 (靜態 SSR):呼叫 NotFound 會將 HTTP 狀態代碼設定為 404。

  • 互動式渲染:向路由器(Blazor)發出Router訊號,以渲染「找不到內容」。

  • 串流轉譯:如果 強化導覽 作用中,串流轉譯 會處理找不到的內容,而不需要重載頁面。 當強化導航遭到封鎖時,架構會通過頁面重新整理將畫面導向至找不到內容的頁面。

串流渲染只能渲染具有路由的元件,例如 NotFoundPage 指派 (NotFoundPage="...") 或 狀態代碼頁重新執行中間件頁面指派 (UseStatusCodePagesWithReExecute)。 DefaultNotFound 404 內容 (“Not found純文本” ) 沒有路由,因此無法在流式渲染期間使用。

NavigationManager.NotFound 內容渲染會依序使用下列項目,無論回應是否已啟動:

  • 如果設定了 NotFoundEventArgs.Path,則呈現指派頁面的內容。
  • 如果設定了 Router.NotFoundPage,則渲染被指定的頁面。
  • 如果已設定,狀態代碼頁會重新執行中間件頁面。
  • 如果未採用上述任何方法,則不採取任何行動。

狀態代碼頁重新執行中間件 優先處理 UseStatusCodePagesWithReExecute 瀏覽器中與地址路由相關的問題,例如在瀏覽器的地址欄中輸入錯誤的URL,或選擇在應用程式中沒有正確端點的連結。

您可以在NavigationManager.OnNotFound叫用時使用NotFound事件來進行通知。

欲了解更多資訊與範例,請參閱 ASP.NET 核心 Blazor 導航

支援沒有路由器的 Blazor應用程式中的「找不到」回應

實作自訂路由器的應用程式可以使用 NavigationManager.NotFound。 有兩種方式可以在呼叫 NavigationManager.NotFound 時告知轉譯器應該轉譯哪個頁面:

無論回應狀態為何,建議的方法是呼叫 UseStatusCodePagesWithReExecute。 當呼叫 NavigationManager.NotFound 時,中間件會渲染傳遞給方法的路徑:

app.UseStatusCodePagesWithReExecute(
    "/not-found", createScopeForStatusCodePages: true);

如果您不想使用 UseStatusCodePagesWithReExecute,該應用程序仍然可以支持 NavigationManager.NotFound 已經開始的響應。 在路由器中訂閱 OnNotFoundEvent,並將「找不到」頁面的路徑指派給 NotFoundEventArgs.Path,以通知渲染器在呼叫 NavigationManager.NotFound 時要渲染哪些內容。

CustomRouter.razor

@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Http
@implements IDisposable
@inject NavigationManager NavigationManager

@code {
    protected override void OnInitialized() =>
        NavigationManager.OnNotFound += OnNotFoundEvent;

    [CascadingParameter]
    public HttpContext? HttpContext { get; set; }

    private void OnNotFoundEvent(object sender, NotFoundEventArgs e)
    {
        // Only execute the logic if HTTP response has started
        // because setting NotFoundEventArgs.Path blocks re-execution
        if (HttpContext?.Response.HasStarted == false)
        {
            return;
        }

        e.Path = GetNotFoundRoutePath();
    }

    // Return the path of the Not Found page that you want to display
    private string GetNotFoundRoutePath()
    {
        ...
    }

    public void Dispose() => NavigationManager.OnNotFound -= OnNotFoundEvent;
}

如果您在應用程式中使用這兩種方法,則處理常式中 OnNotFoundEvent 指定的「找不到」路徑會優先於重新執行中介軟體中設定的路徑。

計量和追蹤

此版本引進應用程式 Blazor 的完整計量和追蹤功能,提供元件生命週期、導覽、事件處理和線路管理的詳細可觀察性。

如需詳細資訊,請參閱 ASP.NET Core Blazor 效能最佳做法

JavaScript 配套程序支援

Blazor的組建輸出與 JavaScript 套件組合器不相容,例如 GulpWebpackRollup。 Blazor 現在可以透過將 MSBuild 屬性設為 WasmBundlerFriendlyBootConfig 的方式設定 true 來在發佈期間產生適合打包工具的輸出。

如需詳細資訊,請參閱裝載和部署 ASP.NET Core Blazor

Blazor WebAssembly 在 Blazor Web App 中預載靜態資源

我們已將 <link> 標頭替換成一個用於在 ResourcePreloader 中預載 WebAssembly 資產的 <ResourcePreloader /> 元件(Blazor Web App)。 這可讓應用程式基底路徑組態 (<base href="..." />) 正確識別應用程式的根目錄。

如果應用程式使用 loadBootResource 回呼 來修改 URL,則移除元件會停用此功能。

預設情況下,範本Blazor Web App 會採用 .NET 10 的功能,而升級至 .NET 10 的應用程式可以透過在ResourcePreloader 元件的前端內容(<base>)中將App 元件放置於基底 URL 標籤App.razor 之後來實作該功能。

<head>
    ...
    <base href="/" />
+   <ResourcePreloader />
    ...
</head>

如需詳細資訊,請參閱裝載和部署 ASP.NET Core 伺服器端 Blazor 應用程式

改善表單驗證

Blazor 現在已改進表單驗證功能,包含支援驗證巢狀物件及集合項目的屬性。

若要建立已驗證的表單,請在 DataAnnotationsValidator 元件內使用 EditForm 元件,就像之前一樣。

若要加入新的驗證功能:

  1. AddValidation檔案中呼叫Program擴充方法來註冊服務。
  2. 在 C# 類別檔案中宣告表單模型類型,而不是在 Razor 元件中(.razor)。
  3. 使用 [ValidatableType] 屬性標註根表單模型類型。

若未遵循上述步驟,驗證行為會與先前的 .NET 版本相同。

下列範例示範已改進表單驗證的客戶訂單(為簡潔起見,省略詳細說明):

Program.cs 中,於服務集合呼叫 AddValidation 以啟用新的驗證行為:

builder.Services.AddValidation();

在以下 Order 類別中,[ValidatableType] 屬性是最上層模型類型所需的。 系統會自動探索其他類型。 OrderItemShippingAddress 不會為了簡潔起見而顯示,但是巢狀和集合驗證在顯示這些類型時的運作方式相同。

Order.cs

[ValidatableType]
public class Order
{
    public Customer Customer { get; set; } = new();
    public List<OrderItem> OrderItems { get; set; } = [];
}

public class Customer
{
    [Required(ErrorMessage = "Name is required.")]
    public string? FullName { get; set; }

    [Required(ErrorMessage = "Email is required.")]
    public string? Email { get; set; }

    public ShippingAddress ShippingAddress { get; set; } = new();
}

在下列 OrderPage 元件中, DataAnnotationsValidator 元件存在於元件中 EditForm

OrderPage.razor

<EditForm Model="Model">
    <DataAnnotationsValidator />

    <h3>Customer Details</h3>
    <div class="mb-3">
        <label>
            Full Name
            <InputText @bind-Value="Model!.Customer.FullName" />
        </label>
        <ValidationMessage For="@(() => Model!.Customer.FullName)" />
    </div>

    @* ... form continues ... *@
</EditForm>

@code {
    public Order? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    // ... code continues ...
}

在 Razor 元件(.razor 檔案)之外宣告模型類型的需求,是因為新的驗證功能和 Razor 編譯器本身都使用了來源產生器。 目前,一個來源產生器的輸出無法做為另一個來源產生器的輸入。

驗證支援現在包括:

  • 現在支援驗證巢狀複雜物件和集合。
    • 這包括屬性屬性、類別屬性和 IValidatableObject 實作所定義的驗證規則。
    • 屬性 [SkipValidation] 可以從驗證中排除屬性或類型。
  • 驗證現在使用以來源產生器為基礎的實作,而不是以反射為基礎的實作,以改善效能以及與提前 (AOT) 編譯的相容性。

DataAnnotationsValidator 元件現在具有與 相同的 System.ComponentModel.DataAnnotations.Validator驗證順序和短路行為。 驗證型別 T的執行個體時,會套用下列規則:

  1. T 成員屬性會經過驗證,包括遞迴驗證巢狀物件。
  2. T 類型層級屬性已驗證。
  3. IValidatableObject.Validate如果實作方法,則T會執行該方法。

如果上述其中一個步驟產生驗證錯誤,則會略過其餘步驟。

使用不同程序集的驗證模型

您可以驗證定義在不同元件 (例如程式庫或.Client的Blazor Web App專案) 中的模型的表單,方法是透過在程式庫或.Client專案中建立一個方法,該方法會接收IServiceCollection實例作為參數並在其上呼叫AddValidation

  • 在應用程式中,同時呼叫 method 和 AddValidation.

如需詳細資訊和範例,請參閱 ASP.NET 核心 Blazor 表單驗證

已移除自定義 Blazor 快取和 BlazorCacheBootResources MSBuild 屬性

現在所有 Blazor 客戶端檔案都已由瀏覽器生成指紋標記並快取,Blazor 已從架構中移除自定義快取機制和 BlazorCacheBootResources MSBuild 屬性。 如果客戶端專案的項目檔包含 MSBuild 屬性,請移除 屬性,因為它不再有任何作用:

- <BlazorCacheBootResources>...</BlazorCacheBootResources>

如需詳細資訊,請參閱 ASP.NET 核心 Blazor WebAssembly 快取和完整性檢查失敗

ASP.NET Core 支援 Web 驗證 API(通行密鑰)Identity

Web 驗證 (WebAuthn) API 支援,廣泛稱為 通行密鑰,是一種抗網路釣魚的新式驗證方法,利用公鑰密碼學和裝置型驗證來改善安全性和用戶體驗。 ASP.NET Core Identity 現在支援以 WebAuthn 和 FIDO2 標準為基礎的複雜密鑰驗證。 這項功能可讓使用者使用安全、裝置型驗證方法,例如生物特徵辨識或安全性密鑰,而不需使用密碼登入。

Blazor Web App專案範本提供現成可用的通行金鑰管理和登入功能。

如需詳細資訊,請參閱下列文章:

線路狀態持續性

在伺服器端轉譯期間,Blazor Web App現在可以在長時間伺服器連線中斷或主動暫停連線時保存使用者的會話(線路)狀態,只要未觸發完整頁面重新整理。 這可讓使用者在下列案例中繼續其會話,而不會遺失未儲存的工作:

  • 瀏覽器索引標籤限制
  • 行動裝置使用者切換應用程式
  • 網路中斷
  • 主動式資源管理(暫停非作用中的線路)
  • 增強的導航

如需詳細資訊,請參閱 ASP.NET Core Blazor 伺服器端狀態管理

WebAssembly 和 .NET 的 Hot Reload 熱重新載入

SDK 已移轉至適用於 WebAssembly 案例的通用熱重新載入。 新的 MSBuild 屬性 WasmEnableHotReload 預設用於 true 組態 (Debug) 中,啟用熱重新載入 Configuration == "Debug"

對於具有自訂組態名稱的其他組態,請在應用程式的專案檔中將值設定為 , true 以啟用熱重新載入:

<PropertyGroup>
  <WasmEnableHotReload>true</WasmEnableHotReload>
</PropertyGroup>

若要停用 Debug 組態的熱重新載入,將值設定為 false

<PropertyGroup>
  <WasmEnableHotReload>false</WasmEnableHotReload>
</PropertyGroup>

已更新 PWA Service Worker 註冊,以防止快取問題

漸進式 Web 應用程式 (PWA) 專案範本中的Blazor服務工作者註冊現在包含此updateViaCache: 'none'選項,可防止在服務工作者更新期間發生快取問題。

- navigator.serviceWorker.register('service-worker.js');
+ navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });

此選項可確保:

  • 瀏覽器不會使用快取版本的 Service Worker 指令碼。
  • Service Worker 更新會可靠地套用,而不會被 HTTP 快取封鎖。
  • PWA 應用程式可以更有預測性地更新其服務工作者。

這解決了可能導致無法正確應用服務背景工作角色更新的快取問題,這對於依賴服務背景工作角色進行離線功能的 PWA 尤其重要。

建議您在所有 PWA 中使用設定為的 none 選項,包括以 .NET 9 或更早版本為目標的 PWA。

持續性元件狀態的序列化擴充性

實作一個自訂序列化器。PersistentComponentStateSerializer<T> 如果沒有已註冊的自訂序列化程式,序列化會回復至現有的 JSON 序列化。

自訂序列化程式會註冊在應用程式的 Program 檔案中。 在下列範例中,CustomUserSerializer 已被註冊為 TUser 類型。

builder.Services.AddSingleton<PersistentComponentStateSerializer<TUser>, 
    CustomUserSerializer>();

類型會自動保存並使用自訂序列化程式還原:

[PersistentState] 
public User? CurrentUser { get; set; } = new();

OwningComponentBase 現在實作 IAsyncDisposable

OwningComponentBase 現在包括對非同步處置的支持,改進了資源管理。 有新的 DisposeAsyncDisposeAsyncCore 方法,以及一個更新的 Dispose 方法,可以同時處理服務範圍的同步與非同步釋放。

InputHidden處理表單中隱藏輸入欄位的新元件

InputHidden 元件提供隱藏的輸入欄位來儲存字串值。

在下列範例中,會為表單的 Parameter 屬性建立隱藏的輸入欄位。 提交表單時,會顯示隱藏欄位的值:

<EditForm Model="Parameter" OnValidSubmit="Submit" FormName="InputHidden Example">
    <InputHidden id="hidden" @bind-Value="Parameter" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Parameter!</p>
}

@code {
    private bool submitted;

    [SupplyParameterFromForm] 
    public string Parameter { get; set; } = "stranger";

    private void Submit() => submitted = true;
}

增強型導覽的持續性元件狀態支援

Blazor 現在支援 在增強型導覽期間處理持續性元件狀態。 在增強型導覽期間保存的狀態可以由頁面上的互動式元件讀取。

根據預設,持續性元件狀態只會在互動式元件最初載入頁面時載入。 這可防止重要狀態 (例如已編輯的 Web 表單中的資料) 在載入元件後發生相同頁面的其他增強型導覽事件時被覆寫。

如果資料是唯讀且不經常變更,請透過設定AllowUpdates = true[PersistentState]屬性來選擇允許在增強型導覽期間進行更新。 這適用於顯示擷取成本高昂但不經常變更的快取資料等案例。 下列範例示範 AllowUpdates 如何用於天氣預報資料:

[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await ForecastService.GetForecastAsync();
}

若要在預先轉譯期間略過還原狀態,請設定 RestoreBehaviorSkipInitialValue

[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string NoPrerenderedData { get; set; }

若要在重新連線期間略過還原狀態,請設定 RestoreBehaviorSkipLastSnapshot。 這對於確保重新連線後的資料更新非常有用:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public int CounterNotRestoredOnReconnect { get; set; }

呼叫 PersistentComponentState.RegisterOnRestoring 以註冊回呼,以命令式控制狀態的還原方式,類似於提供狀態保存方式的完整控制方式 PersistentComponentState.RegisterOnPersisting

Blazor WebAssembly 遵循目前的 UI 文化特性設定

在 .NET 9 或更早版本中,獨立應用程式 Blazor WebAssembly 會根據 CultureInfo.DefaultThreadCurrentCulture載入 UI 全球化資源。 如果您想要額外載入由 所 CultureInfo.DefaultThreadCurrentUICulture定義的當地語系化文化特性的全球化資料,將 應用程式升級至 .NET 10 或更新版本

Blazor Hybrid

本節說明 Blazor Hybrid的新功能。

新的 .NET MAUIBlazor Hybrid,包括 Blazor Web App 和 ASP.NET Core Identity 文章及範例

已新增文章和範例應用程式,適用於 .NET MAUIBlazor Hybrid 和使用 ASP.NET Core 的 Web 應用程式。

如需詳細資訊,請參閱下列資源:

SignalR

本節說明 SignalR的新功能。

最小化 API

本節介紹 Minimal API 的新功能。

將表單提交中的空字串視為可空的數值型別 Null

在 Minimal API 中將該屬性與複雜物件一起使用 [FromForm] 時,表單貼文中的空字串值會被轉換成 null ,而不會造成解析失敗。 此行為與 Minimal APIs 中未關聯到複雜物件的表單貼文的處理邏輯相符。

using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/todo", ([FromForm] Todo todo) => TypedResults.Ok(todo));

app.Run();

public class Todo
{
  public int Id { get; set; }
  public DateOnly? DueDate { get; set; } // Empty strings map to `null`
  public string Title { get; set; }
  public bool IsCompleted { get; set; }
}

感謝 @nvmkpk 參與這項變更!

基本 API 中的驗證支援

現在已提供基本 API 中的驗證支援。 這項功能可讓您要求驗證傳送至 API 端點的數據。 啟用驗證可讓 ASP.NET Core 執行時間執行下列項目上定義的任何驗證:

  • Query
  • Header
  • 請求主體

驗證是使用 命名空間中的 DataAnnotations 屬性來定義。 開發人員會透過下列方式自定義驗證系統的行為:

如果驗證失敗,運行時間會傳回 400 不正確的要求回應,其中包含驗證錯誤的詳細數據。

啟用最小 API 的內建驗證支援

呼叫擴充方法,在應用程式的服務容器中註冊所需的服務,以 AddValidation 啟用最小 API 的內建驗證支援:

builder.Services.AddValidation();

實作會自動探索在最小 API 處理常式中定義的類型,或作為最小 API 處理常式中定義的類型的基本類型。 端點篩選會對這些類型執行驗證,並針對每個端點新增。

您可以使用 DisableValidation 擴充方法來禁用特定端點的驗證,如下列範例所示:

app.MapPost("/products",
    ([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
        => TypedResults.Ok(productId))
    .DisableValidation();

Note

已對適用於 .NET 10 的 ASP.NET Core 中引進的最小 API 驗證產生器進行數個小改進和修正。 為了支持未來的增強功能,基礎驗證解析程式 API 現在標示為實驗性。 最上層 AddValidation API 和內建驗證篩選條件保持穩定且非實驗性。

使用記錄類型的驗證

最小 API 也支援使用 C# 記錄類型的驗證。 您可以使用命名空間中的 System.ComponentModel.DataAnnotations 屬性來驗證記錄類型,類似於類別。 例如:

public record Product(
    [Required] string Name,
    [Range(1, 1000)] int Quantity);

使用記錄類型做為基本 API 端點中的參數時,驗證屬性會自動套用與類別類型相同的方式:

app.MapPost("/products", (Product product) =>
{
    // Endpoint logic here
    return TypedResults.Ok(product);
});

與 IProblemDetailsService 的最小 API 驗證整合

最小 API 的驗證邏輯中的錯誤回應現在可以透過應用程式服務集合(相依注入容器)中提供的 IProblemDetailsService 實作來進行自訂。 這使能夠更一致且針對使用者特定的錯誤反應。

伺服器傳送事件支援(SSE)

ASP.NET Core 現在支援使用 TypedResults.ServerSentEvents API 傳回 ServerSentEvents 結果。 基本 API 和控制器型應用程式都支援這項功能。

Server-Sent 事件是一種伺服器推送技術,可讓伺服器透過單一 HTTP 連線將事件訊息串流傳送至用戶端。 在 .NET 中,事件訊息會表示為 SseItem<T> 物件,其中可能包含事件類型、標識碼,以及類型的 T數據承載。

TypedResults 類別有一個名為 ServerSentEvents 的新靜態方法,可用來傳回ServerSentEvents結果。 這個方法的第一個 IAsyncEnumerable<SseItem<T>> 參數是 ,表示要傳送至用戶端的事件訊息數據流。

下列範例說明如何使用 TypedResults.ServerSentEvents API,將心率事件的數據流當做 JSON 物件傳回給用戶端:

app.MapGet("/json-item", (CancellationToken cancellationToken) =>
{
    async IAsyncEnumerable<HeartRateRecord> GetHeartRate(
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var heartRate = Random.Shared.Next(60, 100);
            yield return HeartRateRecord.Create(heartRate);
            await Task.Delay(2000, cancellationToken);
        }
    }

    return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
                                                  eventType: "heartRate");
});

如需詳細資訊,請參閱:

  • MDN 上的Server-Sent 事件
  • 使用 API 將心率事件的數據流當做字串、TypedResults.ServerSentEvents和 JSON 物件傳回至用戶端的ServerSentEvents
  • 控制器 API 範例應用程式 ,使用 TypedResults.ServerSentEvents API 將心率事件的數據流當做字串、 ServerSentEvents和 JSON 物件傳回給用戶端。

驗證 API 已移至 Microsoft.Extensions.Validation

驗證 API 已移至 Microsoft.Extensions.Validation 命名空間和 NuGet 套件。 這項變更可讓 API 在 ASP.NET Core HTTP 案例之外使用。 公用 API 和行為保持不變,只有套件和命名空間不同。 現有的專案不需要變更程式碼,因為舊參考會重新導向至新的實作。

加強類別和記錄的驗證

驗證屬性現在可以套用至類別和記錄,並具有一致的程式碼產生和驗證行為。 此增強功能提高了在 ASP.NET Core 應用程式中使用記錄設計模型時的靈活性。

社區貢獻:感謝 @marcominerva

OpenAPI

本節說明 OpenAPI 的新功能。

OpenAPI 3.1 支援

ASP.NET Core 已新增在 .NET 10 中產生 OpenAPI 3.1 版 文件的支援。 儘管是次要版本更新,但 OpenAPI 3.1 是對 OpenAPI 規格的一次重大更新,特別是完整支援 JSON Schema 草案 2020-12

您在產生的 OpenAPI 檔中會看到的一些變更包括:

  • 可空的類型在模型中不再具有 nullable: true 屬性。
  • 它們沒有 nullable: true 屬性,而是具有 type 關鍵詞,其值為陣列,其中包含 null 作為其中一種類型。
  • 定義為 C# intlong 的屬性或參數現在會出現在產生的 OpenAPI 檔中,但沒有 type: integer 欄位,而 pattern 欄位會將值限制為數字。 當 NumberHandling 中的 JsonSerializerOptions 屬性設定為 AllowReadingFromString 時,就會發生此情況,這是 ASP.NET Core Web 應用程式的預設值。 若要開啟 C# int 並將 long OpenAPI 檔案中表示為 type: integer,請將 NumberHandling 屬性設定為 Strict

使用這項功能時,所產生檔案的預設 OpenAPI 版本會3.1。 您可以在 AddOpenApi 的委派參數中明確設定 configureOptionsOpenApiVersion 屬性來變更版本:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});

在建置階段產生 OpenAPI 檔時,可以在 MSBuild 項目中設定 --openapi-versionOpenApiGenerateDocumentsOptions 來選取 OpenAPI 版本:

<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
    <!-- Configure build-time OpenAPI generation to produce an OpenAPI 3.1 document. -->
    <OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>

OpenAPI 3.1 支援主要新增在下列 PR中。

OpenAPI 3.1 重大變更

OpenAPI 3.1 的支援需要更新基礎 OpenAPI.NET 程式庫到新的主要版本 2.0。 這個新版本與舊版本相比有一些重大變更。 重大變更可能會影響那些包含任何文件、操作或架構轉換器的應用程式。 此反覆專案中的重大變更包括下列各項:

  • OpenAPI 檔中的實體,例如作業和參數,被類型化為介面。 具體版本的實作存在於實體的內嵌和參考變體中。 例如,IOpenApiSchema 可以是內嵌的 OpenApiSchema,或是指向檔案中其他地方所定義的架構的 OpenApiSchemaReference
  • Nullable 屬性已從 OpenApiSchema 類型中移除。 若要判斷某類型是否可以為 Null,請評估 OpenApiSchema.Type 屬性是否已設定 JsonSchemaType.Null

其中一項最重要的變更是,OpenApiAny 類別已捨棄,而偏好直接使用 JsonNode。 需要更新使用 OpenApiAny 的轉換器,以改用 JsonNode。 下列差異顯示架構轉換器從 .NET 9 變更為 .NET 10:

options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
    if (context.JsonTypeInfo.Type == typeof(WeatherForecast))
    {
-       schema.Example = new OpenApiObject
+       schema.Example = new JsonObject
        {
-           ["date"] = new OpenApiString(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd")),
+           ["date"] = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd"),
-           ["temperatureC"] = new OpenApiInteger(0),
+           ["temperatureC"] = 0,
-           ["temperatureF"] = new OpenApiInteger(32),
+           ["temperatureF"] = 32,
-           ["summary"] = new OpenApiString("Bracing"),
+           ["summary"] = "Bracing",
        };
    }
    return Task.CompletedTask;
});

請注意,即使將 OpenAPI 版本設定為 3.0,這些變更也是必要的。

YAML 中的 OpenAPI

ASP.NET 現在支援以 YAML 格式提供產生的 OpenAPI 檔。 YAML 可以比 JSON 更簡潔,因為在能夠推斷大括弧和引號的情況下,可以省略這些符號。 YAML 也支援多行字串,這對長描述很有用。

若要設定應用程式以 YAML 格式提供產生的 OpenAPI 檔,請使用 “.yaml” 或 “.yml” 後綴指定 MapOpenApi 呼叫中的端點,如下列範例所示:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi("/openapi/{documentName}.yaml");
}

支援項目:

  • YAML 目前僅適用於 OpenAPI 端點所提供的 OpenAPI。
  • 在建置階段以 YAML 格式產生 OpenAPI 檔會在未來的預覽中新增。

請參閱 此 PR ,此 PR 新增了以 YAML 格式提供所產生 OpenAPI 文件的支援。

API 控制器的回應說明ProducesResponseType

ProducesAttributeProducesResponseTypeAttributeProducesDefaultResponseTypeAttribute 現在接受選擇性字串參數 Description,以設定回應的描述:

[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK,
    Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{

產生的 OpenAPI 資料:

"responses": {
  "200": {
    "description": "The weather forecast for the next 5 days.",
    "content": {

API 控制器最小 API 都支援此功能。 針對最小 API,在屬性的類型與推斷的傳回類型不完全相符的情況下,Description 仍能正確設定。

社區貢獻 (dotnet/aspnetcore #58193) 作者: Sander ten Brinke

將 XML 文件註釋填入 OpenAPI 文件

ASP.NET Core OpenAPI 檔案生成現在會將方法、類別和成員定義中的 XML 文件批注的元數據包含在 OpenAPI 檔案中。 您必須在項目檔中啟用 XML 檔批注,才能使用此功能。 您可以將下列屬性新增至項目檔,以執行此動作:

  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

在建置階段,OpenAPI 套件會利用來源產生器來探索目前應用程式元件中的 XML 批註,以及任何專案參考,併發出原始程式碼,以透過 OpenAPI 檔案轉換器將它們插入檔中。

請注意,C# 建置過程不會擷取放在 lambda expresions 上的 XML 文件註解,因此要使用 XML 文件註解將元資料加入 Minimal API 端點,必須將端點處理程序定義為方法,並將 XML 文件註解放在該方法上,然後從該 MapXXX 方法中引用該方法。 例如,使用 XML 文件註解將元資料加入原本定義為 lambda 表達式的最小 API 端點:

app.MapGet("/hello", (string name) =>$"Hello, {name}!");

MapGet 呼叫變更為參考某個方法:

app.MapGet("/hello", Hello);

使用 XML 文件注釋定義 Hello 方法:

static partial class Program
{
    /// <summary>
    /// Sends a greeting.
    /// </summary>
    /// <remarks>
    /// Greeting a person by their name.
    /// </remarks>
    /// <param name="name">The name of the person to greet.</param>
    /// <returns>A greeting.</returns>
    public static string Hello(string name)
    {
        return $"Hello, {name}!";
    }
}

在上一個範例中,Hello 方法會新增至 Program 類別,但您可以將它新增至專案中的任何類別。

上一個範例說明 xml 檔批注 <summary><remarks><param>。 如需 XML 檔批註的詳細資訊,包括所有支援的標籤,請參閱 C# 檔案

由於核心功能是透過來源產生器提供,因此您可以將下列 MSBuild 新增至專案檔來停用。

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.2.*" GeneratePathProperty="true" />
</ItemGroup>

<Target Name="DisableCompileTimeOpenApiXmlGenerator" BeforeTargets="CoreCompile">
  <ItemGroup>
    <Analyzer Remove="$(PkgMicrosoft_AspNetCore_OpenApi)/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll" />
  </ItemGroup>
</Target>

來源產生器會處理包含在 AdditionalFiles 屬性中的 XML 檔案。 若要新增 (或移除),來源會修改 屬性,如下所示:

<Target Name="AddXmlSources" BeforeTargets="CoreCompile">
  <ItemGroup>
    <AdditionalFiles Include="$(PkgSome_Package)/lib/net10.0/Some.Package.xml" />
  </ItemGroup>
</Target>

Microsoft.AspNetCore.OpenApi 已新增至 ASP.NET Core Web API (原生 AOT) 範本

ASP.NET Core Web API (原生AOT)專案範本(簡短名稱webapiaot)現在預設支援使用套件產生Microsoft.AspNetCore.OpenApiOpenAPI文件。 建立新專案時,會使用 --no-openapi 旗標停用此支援。

社區貢獻 (dotnet/aspnetcore #60337) 作者: Sander ten Brinke

支援 DI 容器中的 IOpenApiDocumentProvider。

.NET 10 中的 ASP.NET Core 支援相依性插入 (DI) 容器中的 IOpenApiDocumentProvider 。 開發人員可以插入 IOpenApiDocumentProvider 其應用程式,並使用它來存取 OpenAPI 檔。 此方法適用於在 HTTP 要求內容之外存取 OpenAPI 檔,例如在背景服務或自定義中間件中。

先前,可以使用HostFactoryResolver來搭配no-op IServer實作,以執行應用程式啟動邏輯,而不啟動 HTTP 伺服器。 新功能通過提供一個受Aspire的IDistributedApplicationPublisher啟發的簡化 API,來簡化此過程,該 API 是Aspire的分散式應用程式託管與發布框架的一部分。

如需詳細資訊,請參閱 dotnet/aspnetcore #61463

XML 批注產生器的改善

XML 批注的產生在 .NET 10 中比舊版 .NET 更能有效地處理複雜類型。

  • 它針對更廣泛的類型產生精確且完整的 XML 批注。
  • 它會處理更複雜的案例。
  • 它會巧妙地略過會在舊版中導致建置錯誤的複雜類型的處理。

這些改進會將特定案例的失敗模式從建置錯誤變更為遺漏元數據。

此外,現在可以將 XML 檔批注處理設定為存取其他元件中的 XML 批注。 這對於產生在目前元件外部定義的型別的文件非常有用,例如ProblemDetails命名空間中的Microsoft.AspNetCore.Http型別。

此組態是使用專案建置檔中的 指示詞來完成。 下列範例示範如何設定 XML 批注產生器,以存取元件中型別的 Microsoft.AspNetCore.Http XML 批注,其中包含 ProblemDetails 類別。

<Target Name="AddOpenApiDependencies" AfterTargets="ResolveReferences">
  <ItemGroup>
  <!-- Include XML documentation from Microsoft.AspNetCore.Http.Abstractions
    to get metadata for ProblemDetails -->
    <AdditionalFiles
          Include="@(ReferencePath->'
            %(RootDir)%(Directory)%(Filename).xml')"
          Condition="'%(ReferencePath.Filename)' ==
           'Microsoft.AspNetCore.Http.Abstractions'"
          KeepMetadata="Identity;HintPath" />
  </ItemGroup>
</Target>

我們預期會在未來的預覽中包含共用架構中一組所選元件的 XML 註解,以避免在大部分情況下需要此設定。

OpenAPI XML註解產生器中文件ID的統一處理

即使參考元件的 XML 文件註解包含傳回類型尾碼,也會正確合併。 因此,所有有效的 XML 註解都會可靠地包含在產生的 OpenAPI 文件中,從而提高使用參考元件的 API 的文件準確性和完整性。

表單資料的列舉型別引數在 OpenAPI 中使用實際的列舉型別

MVC 控制器動作中的表單資料參數現在會使用實際的列舉類型來產生 OpenAPI 中繼資料,而不是預設為字串。

社區貢獻:感謝 @ascott18

支援在轉換器中產生OpenApiSchemas

開發人員現在可以使用與 ASP.NET Core OpenAPI 檔產生相同的邏輯來產生 C# 類型的架構,並將其新增至 OpenAPI 檔。 架構接著可以從 OpenAPI 檔中的其他地方參考。

傳遞給文件、操作和結構轉換器的上下文包含了一個新的 GetOrCreateSchemaAsync 方法,可以用來生成某一型別的結構。 這個方法也有選擇性 ApiParameterDescription 參數,可指定所產生架構的其他元數據。

為了支援將架構新增至 OpenAPI 檔, Document 屬性已新增至 Operation 和 Schema transformer 內容。 這可讓任何轉換器使用檔的 AddComponent 方法,將架構新增至 OpenAPI 檔。

Example

若要在文件、作業或架構轉換程式中使用這項功能,請使用內容中提供的方法建立架構 GetOrCreateSchemaAsync ,並使用檔的 AddComponent 方法將它新增至 OpenAPI 檔。

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer(async (operation, context, cancellationToken) =>
    {
        // Generate schema for error responses
        var errorSchema = await context.GetOrCreateSchemaAsync(typeof(ProblemDetails), null, cancellationToken);
        context.Document?.AddComponent("Error", errorSchema);

        operation.Responses ??= new OpenApiResponses();
        // Add a "4XX" response to the operation with the newly created schema
        operation.Responses["4XX"] = new OpenApiResponse
        {
            Description = "Bad Request",
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/problem+json"] = new OpenApiMediaType
                {
                    Schema = new OpenApiSchemaReference("Error", context.Document)
                }
            }
        };
    });
});

端點特定的 OpenAPI 作業轉換器

端點特定的操作轉換器可針對個別路由端點對 OpenAPI 文件進行精細自訂。 此功能允許開發人員根據每個操作或每個路由自訂 Swagger/OpenAPI 元資料和描述,從而增強進階 API 場景的可擴展性。

如需實作詳細資訊和程式碼範例,請參閱 自訂 OpenAPI 文件

將 Microsoft.OpenApi 升級至 2.0.0

Microsoft.OpenApi用於在 ASP.NET Core 中產生 OpenAPI 文件的程式庫已升級至 2.0.0 版 (GA)。

2.0.0 中的重大變更

預覽版中引進了下列重大變更,並保留在 GA 版本中。 這些主要影響實作文件、作業或結構描述轉換器的使用者:

隨著 GA 版本的更新,預計 OpenAPI 文件產生不會發生進一步的重大變更。

OpenAPI 結構描述產生增強功能

在 OpenAPI 結構描述中使用 oneOf 建立可為空類型的模型

使用oneOf模式替代複雜類型和集合的可 Null 屬性,改進了可為 Null 類型的 OpenAPI 結構描述生成。 實作:

  • 使用 oneOfnull 以及實際類型結構描述,以支援請求和回應結構描述中的可空複雜類型。
  • 使用反射和 NullabilityInfoContext 來偵測參數、屬性和返回類型的可空性。
  • 從元件化結構描述中移除 Null 類型,以避免重複。

模式參考解析的更新與優化

此版本透過正確解析根結構描述文件中的相對JSON結構描述參照($ref),改善了OpenAPI文件產生的JSON結構描述的處理。

在OpenAPI架構中,將屬性描述納入與$ref同一層級

在 .NET 10 之前,ASP.NET Core 捨棄了在產生的 OpenAPI 檔中定義的 $ref 屬性描述,因為 OpenAPI v3.0 不允許在結構描述定義中搭配同層級屬性 $ref 。 OpenAPI 3.1 現在可讓您在 旁邊包含描述 $ref。 RC1 新增了在生成的 OpenAPI 結構中,將屬性描述作為 $ref 的同層級內容的支援。

這是社區的貢獻。 謝謝 @desjoerd!

[AsParameters]類型的XML註解中繼資料添加至OpenAPI綱要。

OpenAPI 架構描述的產生器現在會處理參數類別 [AsParameters] 的屬性上的 XML 註解,以提取用於文件的中繼資料。

從 OpenAPI 中排除未知的 HTTP 方法

OpenAPI 結構描述產生現在會從產生的 OpenAPI 文件中排除未知的 HTTP 方法。 查詢方法(是標準 HTTP 方法,但 OpenAPI 無法辨識)現在會從產生的 OpenAPI 文件中正常排除。

這是社區的貢獻。 謝謝 @martincostello!

改善 JSON 補丁請求體的描述

JSON 修補操作的 OpenAPI 架構生成現在會正確地將媒體類型套用到使用 JSON 修補的請求主體。 這可確保產生的 OpenAPI 文件準確反映 JSON 修補程式作業的預期媒體類型。 此外,JSON 修補程式要求內文具有詳細的結構描述,可描述 JSON 修補程式文件的結構,包括可執行的作業。

這是社區的貢獻。 謝謝 @martincostello!

使用不變文化特性產生 OpenAPI 文件

OpenAPI 文件產生現在會使用不變文化特性來格式化所產生的 OpenAPI 文件中的數字和日期。 這可確保產生的文件一致,並且不會因伺服器的文化特性設定而變化。

這是社區的貢獻。 謝謝 @martincostello!

驗證和授權

驗證和授權計量

已在 ASP.NET Core 中新增特定驗證和授權事件的計量。 透過這項變更,您現在可以取得下列事件的計量:

  • Authentication:
    • 認證請求持續時間
    • 挑戰次數
    • 禁止使用計數功能
    • 登入次數
    • 註銷計數
  • Authorization:
    • 需要授權的請求計數

下圖顯示儀表板中 Aspire 已驗證請求持續時間計量的範例:

儀表板中已驗證需求的持續時間Aspire

如需詳細資訊,請參閱 ASP.NET 核心內建計量

ASP.NET 核心 Identity 指標

ASP.NET 核心 Identity .NET 10 中的可觀測性已改善計量。 度量是計數器、直方圖和量規,可提供系統或應用程式行為的時間序列測量。

舉例來說,使用新的 ASP.NET 核心 Identity 指標來觀察:

  • 使用者管理:新使用者建立、密碼變更和角色指派。
  • 登入/工作階段處理:使用雙因素驗證的登入嘗試、登入、登出和使用者。

新指標位於計量中 Microsoft.AspNetCore.Identity

  • aspnetcore.identity.user.create.duration
  • aspnetcore.identity.user.update.duration
  • aspnetcore.identity.user.delete.duration
  • aspnetcore.identity.user.check_password_attempts
  • aspnetcore.identity.user.generated_tokens
  • aspnetcore.identity.user.verify_token_attempts
  • aspnetcore.identity.sign_in.authenticate.duration
  • aspnetcore.identity.sign_in.check_password_attempts
  • aspnetcore.identity.sign_in.sign_ins
  • aspnetcore.identity.sign_in.sign_outs
  • aspnetcore.identity.sign_in.two_factor_clients_remembered
  • aspnetcore.identity.sign_in.two_factor_clients_forgotten

如需有關在 ASP.NET Core 中使用指標的詳細資訊,請參閱 ASP.NET Core 指標

依預設,對受驗證保護 cookie 的已知 API 端點發出的未經驗證和未經授權的請求現在會導致 401 和 403 回應,而不是重新導向至登入或存取拒絕的 URI。

此變更是 強烈要求的,因為將未經驗證的請求重新導向至登入頁面通常沒有意義,因為 API 端點通常依賴 401 和 403 狀態碼,而不是 HTML 重新導向來傳達驗證失敗。

已知的API 端點 會使用新 IApiEndpointMetadata 介面來識別,而實作新介面的中繼資料已自動新增至下列項目:

  • [ApiController] 端點
  • 能讀取 JSON 請求本文或寫入 JSON 回應的「最小 API」端點
  • 使用返回類型TypedResults的端點
  • SignalR 端點

存在 IApiEndpointMetadata 時,cookie 驗證處理常式現在會傳回適當的 HTTP 狀態碼(401 代表未經驗證的要求,403 代表禁止的要求),而不是進行重新導向。

如果您希望阻止此新行為,並在未經驗證或授權的請求時始終重新導向至登入或拒絕存取的 URI,無論目標端點是什麼,您可以按照如下所示覆寫 RedirectToLoginRedirectToAccessDenied 事件:

builder.Services.AddAuthentication()
    .AddCookie(options =>
    {
        options.Events.OnRedirectToLogin = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };

        options.Events.OnRedirectToAccessDenied = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
    });

如需此重大變更的詳細資訊,請參閱 ASP.NET 核心重大變更公告

Miscellaneous

本節說明 .NET 10 中其他新功能。

設定忽略例外處理常式的診斷

ASP.NET Core 異常處理程序中介軟體中新增了一個新的配置選項,以控制診斷輸出: ExceptionHandlerOptions.SuppressDiagnosticsCallback。 此回呼會傳遞要求和例外狀況的相關內容,可讓您新增邏輯,以判斷中介軟體是否應該寫入例外狀況記錄和其他遙測。

當您知道例外狀況是暫時性的,或已由例外狀況處理常式中介軟體處理,且您不希望錯誤記錄寫入可觀測性平台時,此設定非常有用。

中間件的預設行為也發生了變化:它不再為由 處理的 IExceptionHandler異常寫入異常診斷。 根據使用者的意見反應,在 IExceptionHandler.TryHandleAsync 傳回 true 的情況下,將處理後的異常狀況記錄為錯誤層級是不理想的。

您可以透過設定 SuppressDiagnosticsCallback下列方式還原到先前的行為:

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    SuppressDiagnosticsCallback = context => false;
});

如需此重大變更的詳細資訊,請參閱 https://github.com/aspnet/Announcements/issues/524

對 .localhost 頂級域名的支援

.localhost頂級網域 (TLD) 在 RFC2606RFC6761 中定義為保留用於測試目的,並可供使用者像使用任何其他網域名稱一樣在本機使用。 這表示,根據這些RFC,允許並預期使用可解析為IP環回地址的本地名稱 myapp.localhost。 此外,現代常青瀏覽器已經自動將任何*.localhost名稱解析為 IP 環回位址 (),127.0.0.1/::1有效地使它們成為本機電腦上已託管localhost的任何服務的別名,也就是說,任何回應的http://localhost:6789服務也將回應 http://anything-here.localhost:6789,假設該服務沒有執行進一步的特定主機名稱驗證或強制執行。

ASP.NET Core 已在 .NET 10 Preview 7 中更新,以更妥善地支援 .localhost TLD,因此現在可以在本機開發環境中建立和執行 ASP.NET Core 應用程式時輕鬆使用它。 讓不同的應用程式在本機執行,可以透過不同的名稱進行解析,可以更好地分離一些與網域名稱相關的網站資產,例如 Cookie,並更容易透過瀏覽器網址列中顯示的名稱來識別您正在瀏覽的應用程式。

ASP.NET Core 的內建 HTTP 伺服器 Kestrel,現在會正確地將透過*.localhost設定的任何名稱視為本機回送位址,從而綁定到它而不是所有外部位址 (即綁定到127.0.0.1/::1而不是 )。0.0.0.0/:: 這包括"applicationUrl"launchSettings.json 檔案中設定的啟動設定檔屬性,以及ASPNETCORE_URLS環境變數。 當配置為接聽地址.localhost時,Kestrel將記錄 .localhostlocalhost 地址的信息消息,以明確可以使用這兩個名稱。

雖然網頁瀏覽器會自動將名稱解析 *.localhost 為本機環回位址,但其他應用程式可能會將名稱視為 *.localhost 常規網域名稱,並嘗試透過其對應的 DNS 堆疊解析它們。 如果您的 DNS 組態未將名稱解析 *.localhost 為位址,則無法連線。 當不在網頁瀏覽器中時,您可以繼續使用一般 localhost 名稱來定址您的應用程式。

ASP.NET Core HTTPS 開發憑證 (包括dotnet dev-certs https指令) 已更新,以確保憑證可與網域名稱搭配*.dev.localhost使用。 安裝 .NET 10 SDK Preview 7 之後,請在命令列上執行 dotnet dev-certs https --trust 來信任新的開發人員憑證,以確保您的系統已設定為信任新的憑證。

憑證會 *.dev.localhost 將名稱列為主體替代名稱 (SAN),而不是 *.localhost 因為對頂層網域名稱使用萬用字元憑證無效。

ASP.NET Core Emptyweb) 和 Blazor Web Appblazor) 的專案範本已更新為新選項,指定該選項時,會將建立的專案設定為使用.dev.localhost網域名稱後綴,並將其與專案名稱結合,以允許在類似以下地址https://myapp.dev.localhost:5036瀏覽應用程式:

$ dotnet new web -n MyApp --localhost-tld
The template "ASP.NET Core Empty" was created successfully.

Processing post-creation actions...
Restoring D:\src\MyApp\MyApp.csproj:
Restore succeeded.

$ cd .\MyApp\
$ dotnet run --launch-profile https
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://myapp.dev.localhost:7099
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7099/
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://myapp.dev.localhost:5036
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5036/
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\src\local\10.0.1xx\MyApp

MVC 和 Minimal API 中的 Json+PipeReader 還原序列化支援

公關:https://github.com/dotnet/aspnetcore/pull/62895

https://github.com/dotnet/core/blob/dotnet10-p7-libraries/release-notes/10.0/preview/preview7/libraries.md#pipereader-support-for-json-serializer

MVC、最小 API 和 HttpRequestJsonExtensions.ReadFromJsonAsync 方法都已更新為使用新的 Json+PipeReader 支持,而無需從應用程式更改任何程式碼。

對於大部分的應用程式,新增此支援不會影響其行為。 但是,如果應用程序使用自定義 JsonConverter,則轉換器可能無法正確處理 Utf8JsonReader.HasValueSequence 。 這可能會導致資料遺失和錯誤,例如 ArgumentOutOfRangeException還原序列化時。

快速解決方法 (特別是如果您不擁有正在使用的自訂 JsonConverter ) 是將開關設定 "Microsoft.AspNetCore.UseStreamBasedJsonParsing"AppContext"true"。 這應該是暫時的解決方法,並且 JsonConverter 應該更新以支援 HasValueSequence

若要修正 JsonConverter 實作,有一個快速修正程式會從 和 ReadOnlySequence 配置陣列,如下所示:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

還有一個更複雜(但高性能)的修復,這將涉及為處理提供 ReadOnlySequence 單獨的代碼路徑:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

從記憶體集區自動驅逐

、IIS 和 HTTP.sys 所使用的 Kestrel記憶體集區現在會在應用程式閑置或負載較少時自動收回記憶體區塊。 此功能會自動執行,不需要手動啟用或設定。

為何記憶體收回很重要

先前,即使未使用,集區配置的記憶體仍會保持預留狀態。 當應用程式閑置一段時間時,此功能會將記憶體釋放回系統。 這項收回可減少整體記憶體使用量,並協助應用程式在不同的工作負載下保持回應。

使用記憶體逐出指標

計量已新增至伺服器實作所使用的預設記憶體集區。 新的計數名稱稱為 "Microsoft.AspNetCore.MemoryPool"

如需計量及其使用方式的相關信息,請參閱 ASP.NET 核心計量

管理記憶體集區

除了藉由收回不必要的記憶體區塊更有效率地使用記憶體集區之外,.NET 10 也會改善建立記憶體集區的體驗。 其方式是提供內建 IMemoryPoolFactoryMemoryPoolFactory 實作。 它會透過依賴注入將實作提供給您的應用程式。

下列程式代碼範例示範使用內建記憶體集區處理站實作來建立記憶體集區的簡單背景服務。 這些集區受益於具有自動逐出功能:

public class MyBackgroundService : BackgroundService
{
    private readonly MemoryPool<byte> _memoryPool;

    public MyBackgroundService(IMemoryPoolFactory<byte> factory)
    {
        _memoryPool = factory.Create();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(20, stoppingToken);
                // do work that needs memory
                var rented = _memoryPool.Rent(100);
                rented.Dispose();
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }
    }
}

若要使用您自己的記憶體集區工廠,請建立一個類別以實作 IMemoryPoolFactory 並使用相依性注入進行註冊,如下列範例所示。 以這種方式建立的記憶體池也受益於自動淘汰功能:

services.AddSingleton<IMemoryPoolFactory<byte>,
CustomMemoryPoolFactory>();

public class CustomMemoryPoolFactory : IMemoryPoolFactory<byte>
{
    public MemoryPool<byte> Create()
    {
        // Return a custom MemoryPool implementation
        // or the default, as is shown here.
        return MemoryPool<byte>.Shared;
    }
}

可自定義 HTTP.sys 的安全性描述元

您現在可以為 HTTP.sys 要求佇列指定自訂安全性描述元。 上的 HttpSysOptions 新屬性能使您更細緻地控制要求佇列的存取權限。 此細微的控制可讓您根據應用程式的需求量身打造安全性。

您可以使用新屬性執行哪些動作

HTTP.sys 中的 要求佇列 是一種核心層級結構,可暫時儲存連入 HTTP 要求,直到您的應用程式準備好處理這些要求為止。 藉由自定義安全性描述元,您可以允許或拒絕特定使用者或群組存取要求佇列。 在您希望在作業系統層級限制或委派 HTTP.sys 要求處理時,這會非常有用。

如何使用新屬性

屬性只有在建立新的RequestQueueSecurityDescriptor要求佇列時才會套用。 屬性不會影響現有的要求佇列。 若要使用這個屬性,請在設定 HTTP.sys 伺服器時,將它設定為 GenericSecurityDescriptor 實例。

例如,下列程式代碼允許所有已驗證的使用者,但拒絕來賓:

using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.AspNetCore.Server.HttpSys;

// Create a new security descriptor
var securityDescriptor = new CommonSecurityDescriptor(isContainer: false, isDS: false, sddlForm: string.Empty);

// Create a discretionary access control list (DACL)
var dacl = new DiscretionaryAcl(isContainer: false, isDS: false, capacity: 2);
dacl.AddAccess(
    AccessControlType.Allow,
    new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);
dacl.AddAccess(
    AccessControlType.Deny,
    new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);

// Assign the DACL to the security descriptor
securityDescriptor.DiscretionaryAcl = dacl;

// Configure HTTP.sys options
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseHttpSys(options =>
{
    options.RequestQueueSecurityDescriptor = securityDescriptor;
});

如需詳細資訊,請參閱 ASP.NET Core 中的HTTP.sys Web 伺服器實作

提供更好支援以測試使用最上層語句的應用程式

.NET 10 現在有較佳的支援,可測試使用 最上層語句的應用程式。 前任開發人員必須手動將 public partial class Program 新增至 Program.cs 檔案,讓測試專案可以參考 Program class。 要求 public partial class Program,是因為 C# 9 的最上層語句功能會產生一個被宣告為 Program class

在 .NET 10 中,如果程式設計人員未明確宣告 ,就會使用來源產生器 來產生 public partial class Program 宣告。 此外,已新增一個分析器來檢測 public partial class Program 何時被明確宣告,並建議開發人員將其移除。

Image

下列 PR 對於這項功能有貢獻:

使用新的 JSON 修補程式實作 System.Text.Json

JSON 補丁

  • 這是描述要套用至 JSON 檔之變更的標準格式。
  • 定義於 RFC 6902 中,且在 RESTful API 中廣泛使用,以對 JSON 資源執行部分更新。
  • 表示可套用以修改 JSON 檔的作業序列(例如,Add、Remove、Replace、Move、Copy、Test)。

在 Web 應用程式中,JSON Patch 通常用於 PATCH 作業,以執行資源的部分更新。 用戶端可以只傳送包含變更的 JSON Patch 檔,而不是傳送更新的整個資源。 修補可減少承載大小並提升效率。

此版本引進了一種新的 Microsoft.AspNetCore.JsonPatch 實作,基於 System.Text.Json 串行化。 這項功能:

  • 符合現代 .NET 做法,使用針對 .NET 優化的 System.Text.Json 庫。
  • 相較於舊版 Newtonsoft.Json實作,提供改善的效能和降低的記憶體使用量。

下列基準檢驗會比較新 System.Text.Json 實作與舊版 Newtonsoft.Json 實作的效能。

Scenario Implementation Mean 配置的記憶體
應用程式效能評定 Newtonsoft.JsonPatch 271.924 微秒 25 KB
System.Text.JsonPatch 1.584 微秒 3 KB
反序列化基準 Newtonsoft.JsonPatch 19.261 微秒 43 KB
System.Text.JsonPatch 7.917 微秒 7 KB

這些基準檢驗指出新的實作能大幅提升效能,並降低記憶體使用量。

Notes:

  • 新的實作並不是舊版實作的直接替代品。 特別是,新的實作不支援動態類型,例如 ExpandoObject
  • JSON Patch 標準具有 固有的安全性風險。 由於這些風險固有於 JSON Patch 標準,因此新實作 不會嘗試降低固有的安全性風險。 開發人員有責任確保 JSON Patch 檔安全地套用至目標物件。 如需詳細資訊,請參閱 減輕安全性風險 一節。

Usage

若要使用 System.Text.Json啟用 JSON 修補程式支援,請安裝 Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet 套件。

dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease

此套件提供 JsonPatchDocument<T> 類別來表示用於 T 類型物件的 JSON Patch 文件,以及使用 System.Text.Json 進行 JSON Patch 文件序列化和反序列化的自定義邏輯。 類別JsonPatchDocument<T>的關鍵方法是ApplyTo,它將修補作業套用至類型為T的目標物件。

下列範例示範如何使用 ApplyTo 方法將 JSON Patch 檔套用至 物件。

範例:套用 JsonPatchDocument

下列範例示範:

  1. addreplaceremove 作業。
  2. 對巢狀屬性的操作
  3. 新增一個新項目至陣列。
  4. 在 JSON 修補程式檔中使用 JSON 字串列舉轉換器。
// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com",
  PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
  Address = new Address
  {
    Street = "123 Main St",
    City = "Anytown",
    State = "TX"
  }
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/FirstName", "value": "Jane" },
  { "op": "remove", "path": "/Email"},
  { "op": "add", "path": "/Address/ZipCode", "value": "90210" },
  {
    "op": "add",
    "path": "/PhoneNumbers/-",
    "value": { "Number": "987-654-3210", "Type": "Work" }
  }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document
patchDoc!.ApplyTo(person);

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// {
//   "firstName": "Jane",
//   "lastName": "Doe",
//   "address": {
//     "street": "123 Main St",
//     "city": "Anytown",
//     "state": "TX",
//     "zipCode": "90210"
//   },
//   "phoneNumbers": [
//     {
//       "number": "123-456-7890",
//       "type": "Mobile"
//     },
//     {
//       "number": "987-654-3210",
//       "type": "Work"
//     }
//   ]
// }

ApplyTo方法通常會遵循System.Text.Json的慣例和選項來處理JsonPatchDocument,包括以下選項所控制的行為:

  • NumberHandling:數值屬性是否從字串讀取。
  • PropertyNameCaseInsensitive:屬性名稱是否區分大小寫。

System.Text.Json與新JsonPatchDocument<T>實作之間的關鍵差異:

  • 目標對象的運行時間類型,而不是宣告的類型,會決定哪些屬性 ApplyTo 會修補。
  • System.Text.Json 反序列化依賴宣告的類型以識別符合條件的屬性。

範例:運用 JsonPatchDocument 進行錯誤處理

套用 JSON 修補程式檔時可能會發生各種錯誤。 例如,目標物件可能沒有指定的屬性,或者指定的值可能與屬性類型不相容。

JSON 修補程式也支持 test 作業。 作業 test 會檢查指定的值是否等於目標屬性,如果不是,則傳回錯誤。

下列範例示範如何正常處理這些錯誤。

Important

傳遞至 ApplyTo 方法的物件會在原地被修改。 如果有任何作業失敗,呼叫端有責任捨棄這些變更。

// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com"
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/Email", "value": "janedoe@gmail.com"},
  { "op": "test", "path": "/FirstName", "value": "Jane" },
  { "op": "replace", "path": "/LastName", "value": "Smith" }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document, catching any errors
Dictionary<string, string[]>? errors = null;
patchDoc!.ApplyTo(person, jsonPatchError =>
    {
        errors ??= new ();
        var key = jsonPatchError.AffectedObject.GetType().Name;
        if (!errors.ContainsKey(key))
        {
            errors.Add(key, new string[] { });
        }
        errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray();
    });
if (errors != null)
{
    // Print the errors
    foreach (var error in errors)
    {
        Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}");
    }
}

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// Error in Person: The current value 'John' at path 'FirstName' is not equal 
// to the test value 'Jane'.
// {
//   "firstName": "John",
//   "lastName": "Smith",              <<< Modified!
//   "email": "janedoe@gmail.com",     <<< Modified!
//   "phoneNumbers": []
// }

降低安全性風險

使用 Microsoft.AspNetCore.JsonPatch.SystemTextJson 套件時,請務必瞭解並降低潛在的安全性風險。 下列各節概述與 JSON Patch 相關聯的已識別安全性風險,並提供建議的防護功能,以確保套件的安全使用。

Important

這不是完整的威脅清單。 應用程式開發人員必須進行自己的威脅模型檢閱,以判斷應用程式專屬的完整清單,並視需要提出適當的風險降低措施。 例如,提供集合修補作業的應用程式應該考慮演算法複雜度攻擊的可能性,尤其是在這些作業於集合開頭插入或移除元素時。

藉由針對自己的應用程式執行完整的威脅模型,並解決所識別的威脅,同時遵循下列建議的緩和措施,這些套件的取用者可以將 JSON Patch 功能整合到其應用程式中,同時將安全性風險降至最低。

這些套件的取用者可以將 JSON Patch 功能整合到其應用程式中,同時將安全性風險降至最低,包括:

  • 針對自己的應用程式執行完整的威脅模型。
  • 解決已識別的威脅。
  • 請遵循下列各節中建議的緩和措施。
透過記憶體放大進行拒絕服務攻擊(DoS)
  • 案例:惡意用戶端會提交 copy 多次重複大型物件圖形的作業,導致記憶體耗用量過多。
  • 影響:潛在的Of-Memory 超限(OOM)狀況,導致服務中斷。
  • Mitigation:
    • 在呼叫 ApplyTo之前,先驗證傳入 JSON 修補程式檔的大小和結構。
    • 驗證必須是應用程式專屬的,但範例驗證看起來可能如下所示:
public void Validate(JsonPatchDocument<T> patch)
{
    // This is just an example. It's up to the developer to make sure that
    // this case is handled properly, based on the app's requirements.
    if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count()
        > MaxCopyOperationsCount)
    {
        throw new InvalidOperationException();
    }
}
商業邏輯顛覆
  • 案例:修補作業可以操作具有隱含不變量的欄位(例如,內部旗標、識別碼或計算欄位),這可能會違反業務限制。
  • 影響:數據完整性問題和非預期的應用程序行為。
  • Mitigation:
    • 使用安全且可修改的屬性明確定義的 POCO 物件。
    • 避免在目標對象中公開敏感性或安全性關鍵屬性。
    • 如果未使用 POCO 物件,請在套用操作之後驗證修補的物件,以確保不會違反商業規則和不變條件。
驗證和授權
  • 案例:未驗證或未授權的客戶端傳送惡意 JSON Patch 請求。
  • 影響:未經授權存取以修改敏感數據或中斷應用程式行為。
  • Mitigation:
    • 使用適當的驗證和授權機制保護接受 JSON 修補程式要求的端點。
    • 限制對具有適當許可權的受信任客戶端或使用者的存取。

使用 RedirectHttpResult.IsLocalUrl 檢查 URL 是否屬於本地地址

使用新的 RedirectHttpResult.IsLocalUrl(url) 輔助方法來檢測 URL 是否為本地主機。 如果符合以下條件,則將 URL 視為本機:

使用 虛擬路徑"~/" 的 URL 也屬於本地。

IsLocalUrl 有助於在重新導向至 URL 之前先驗證 URL,以防止 開啟重新導向攻擊

if (RedirectHttpResult.IsLocalUrl(url))
{
    return Results.LocalRedirect(url);
}

感謝您 @martincostello 此貢獻!

重大突破性變更

使用 .NET 中的重大變更中的 文章,尋找將應用程式升級至較新版本的 .NET 時可能套用的重大變更。