共用方式為


ASP.NET Core 9.0 的新功能

本文會重點說明 ASP.NET Core 9.0 最重要的變更,並附有相關文件的連結。

本文已針對 .NET 9 Preview 6 進行了更新。

Blazor

本章節會說明 Blazor 的新功能。

.NET MAUIBlazor Hybrid 和 Web 應用程式解決方案範本

新的解決方案範本可讓您更輕鬆地建立共用相同 UI 的 .NET MAUI 原生和 Blazor Web 用戶端應用程式。 此範本會顯示如何建立可將程式碼重複使用最大化並以 Android、iOS、Mac、Windows 和 Web 為目標的用戶端應用程式。

此範本的主要功能包括:

  • 選擇 Web 應用程式的 Blazor 互動式轉譯模式的能力。
  • 自動建立適當的專案,包括 Blazor Web 應用程式 (全域互動式自動轉譯) 和 .NET MAUIBlazor Hybrid 應用程式。
  • 建立的專案會使用共用 Razor 類別庫 (RCL) 來維護 UI 的 Razor 元件。
  • 其中包括的範例程式碼會示範如何使用相依性插入來為 Blazor Hybrid 應用程式和 Blazor Web 應用程式提供不同的介面實作。

若要開始使用,請安裝 .NET 9 SDK 並安裝包含範本的 .NET MAUI 工作負載:

dotnet workload install maui

使用下列命令從命令殼層中的專案範本建立解決方案:

dotnet new maui-blazor-web

範本也可在 Visual Studio 中使用。

注意

如果 Blazor 轉譯模式是在每頁/元件層級定義,則目前會發生例外狀況。 如需詳細資訊,請參閱 BlazorWebView 需要有方法可啟用覆寫 ResolveComponentForRenderMode (dotnet/aspnetcore #51235)

如需詳細資訊,請參閱使用 Blazor Web 應用程式建置 .NET MAUIBlazor Hybrid 應用程式

靜態資產傳遞最佳化

MapStaticAssets 是新的中介軟體,可協助最佳化任何 ASP.NET Core 應用程式中靜態資產的傳遞,包括 Blazor 個應用程式。

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

在執行階段偵測轉譯位置、互動性和指派的轉譯模式

我們已經引進一個新的 API,旨在簡化執行階段查詢元件狀態的流程。 此 API 可提供下列功能:

  • 確定元件的目前執行位置:這對於偵錯和最佳化元件效能尤其實用。
  • 檢查元件是否正在互動式環境中執行:這對於根據其環境的互動性而具有不同行為的元件很有幫助。
  • 為元件擷取已指派的轉譯模式:了解轉譯模式有助於最佳化轉譯流程並提高元件的整體效能。

如需詳細資訊,請參閱 ASP.NET Core Blazor 轉譯模式

已提高伺服器端重新連線體驗:

已對預設伺服器端重新連線體驗完成下列增強功能:

  • 當使用者巡覽回到線路已中斷的應用程式時,會立即嘗試重新連線,而不是等待下一個重新連線間隔期間。 這可提升瀏覽至瀏覽器索引標籤中已進入睡眠模式之應用程式時的使用者體驗。

  • 當重新連線嘗試到達伺服器,但伺服器已經釋放線路時,即會自動發生頁面重新整理。 這可以防止使用者必須手動重新整理頁面 (如有可能導致成功的重新連線)。

  • 重新連線計時會使用計算的輪詢策略。 預設情況下,在嘗試之間引進計算延遲之前,前幾次重新連線嘗試會在沒有重試間隔的情況下快速地連續發生。 您可以藉由指定函式來計算重試間隔來自訂重試間隔行為,如下列指數輪詢範例所示:

    Blazor.start({
      circuit: {
        reconnectionOptions: {
          retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
            previousAttempts >= maxRetries ? null : previousAttempts * 1000
        },
      },
    });
    
  • 預設重新連線 UI 的樣式已現代化。

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

簡化 Blazor Web Apps 的驗證狀態序列化

新的 API 可讓您更輕鬆地將驗證新增至現有的 Blazor Web 應用程式。 當您使用個別帳戶建立包含驗證的新 Blazor Web 應用程式,並啟用 WebAssembly 型互動功能時,專案在伺服器和用戶端專案中都會包括自訂 AuthenticationStateProvider

這些提供者會將使用者的驗證狀態流向瀏覽器。 在伺服器上進行驗證,而不是用戶端允許應用程式在預先轉譯期間,以及在初始化 Blazor WebAssembly 執行階段之前存取驗證狀態。

自訂 AuthenticationStateProvider 實作會使用保存元件狀態服務 (PersistentComponentState) 將驗證狀態序列化為 HTML 註解,並且從 WebAssembly 讀取它以建立新的 AuthenticationState 執行個體。

如果您已從 Blazor Web 應用程式專案範本開始,並選取 [個人帳戶] 選項,則此方法相當有效,但如果您嘗試在現有專案中新增驗證,則需要自行實作或複製大量程式碼。 現在有 API,現在屬於 Blazor Web 應用程式專案範本的一部分,在伺服器和用戶端專案中呼叫即可新增這項功能:

  • AddAuthenticationStateSerialization:新增必要的服務,以序列化伺服器上的驗證狀態。
  • AddAuthenticationStateDeserialization:新增必要的服務,以還原串行化瀏覽器中的驗證狀態。

根據預設,這些 API 只會序列化伺服器端名稱和角色宣告,以在瀏覽器中存取。 選項可以傳遞至 AddAuthenticationStateSerialization 以包括所有宣告。

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

將靜態伺服器端轉譯 (SSR) 頁面新增至全域互動式 Blazor Web 應用程式

隨著 .NET 9 的發行,現在要將靜態 SSR 頁面新增至採用全域互動功能的應用程式變得更簡單。

只有當應用程式具有無法使用互動式伺服器或 WebAssembly 轉譯的特定頁面時,此方法才有用。 例如,對於依賴讀取/寫入 HTTP cookie 且只能在要求/回應週期 (而不是互動式轉譯) 中運作的頁面,可採用此方法。 對於使用互動式轉譯的頁面,您不應強制其使用靜態 SSR 轉譯,因為其效率較低且對終端使用者的回應較差。

使用以 @attributeRazor 指示詞指派的新 [ExcludeFromInteractiveRouting] 屬性來標記任何 Razor 元件頁面:

@attribute [ExcludeFromInteractiveRouting]

套用該屬性會導致瀏覽至該頁面時退出互動式路由處理。 輸入導覽會被強制執行完整頁面重新載入,而不是透過互動式路由處理來解析頁面。 完整頁面重新載入會強制最上層根元件 (通常是 App 元件 (App.razor)) 從伺服器重新轉譯,從而允許應用程式切換到不同的最上層轉譯模式。

HttpContext.AcceptsInteractiveRouting 擴充方法可讓元件偵測是否已將 [ExcludeFromInteractiveRouting] 套用至目前頁面。

App 元件中,使用下列範例中的模式:

  • 未以 [ExcludeFromInteractiveRouting] 標註的頁面預設為具有全域互動功能的 InteractiveServer 轉譯模式。 您可以使用 InteractiveWebAssemblyInteractiveAuto 來取代 InteractiveServer,以指定不同的預設全域轉譯模式。
  • [ExcludeFromInteractiveRouting] 標註的頁面會採用靜態 SSR (PageRenderMode 被指派 null)。
<!DOCTYPE html>
<html>
<head>
    ...
    <HeadOutlet @rendermode="@PageRenderMode" />
</head>
<body>
    <Routes @rendermode="@PageRenderMode" />
    ...
</body>
</html>

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? PageRenderMode
        => HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}

使用 HttpContext.AcceptsInteractiveRouting 擴充方法的替代方法是使用 HttpContext.GetEndpoint()?.Metadata 手動讀取端點中繼資料。

ASP.NET Core Blazor 轉譯模式中的參考文件會涵蓋這項功能。

建構函式插入

Razor 元件支援建構函式插入。

在下列範例中,部份 (code-behind) 類別會使用 主要建構函式 插入 NavigationManager 服務:

public partial class ConstructorInjection(NavigationManager navigation)
{
    protected NavigationManager Navigation { get; } = navigation;
}

如需詳細資訊,請參閱 ASP.NET Core Blazor 相依性插入

互動式伺服器元件的 WebSocket 壓縮

根據預設,互動式伺服器元件會啟用 WebSocket 連線的壓縮,並為 'self' 設定 frame-ancestors 內容安全性原則 (CSP) 指示詞組,這只允許將應用程式內嵌在啟用壓縮時或提供 WebSocket 內容的設定時,將應用程式內嵌在來源的 <iframe> 中。

ConfigureWebSocketOptions 設定為 null 可停用壓縮,這可降低 應用程式遭受攻擊的弱點,但可能會導致效能降低:

.AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

使用 'none' 值設定較嚴格的 frame-ancestors CSP (需要單一引用),會允許 WebSocket 壓縮,但會防止瀏覽器將應用程式內嵌至任何 <iframe>:

.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

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

在 Blazor 中處理鍵盤組合事件

新的 KeyboardEventArgs.IsComposing 屬性會指出鍵盤事件是否為組合工作階段的一部分。 追蹤鍵盤事件的組合狀態對於處理國際字元輸入方法至關重要。

已將 OverscanCount 參數新增至 QuickGrid

QuickGrid 元件現在會公開 OverscanCount 屬性,該屬性會指定啟用虛擬化時,在可見區域之前和之後轉譯多少個其他資料列。

預設 OverscanCount 為 3。 下列範例會將 OverscanCount 增加為 4:

<QuickGrid ItemsProvider="itemsProvider" Virtualize="true" OverscanCount="4">
    ...
</QuickGrid>

SignalR

本章節會說明 SignalR 的新功能。

SignalR 中樞中的多型類型支援

中樞方法現在可以接受基底類別,而不是衍生類別,以啟用多型案例。 基底類型必須 加上註解才能允許多型

public class MyHub : Hub
{
    public void Method(JsonPerson person)
    {
        if (person is JsonPersonExtended)
        {
        }
        else if (person is JsonPersonExtended2)
        {
        }
        else
        {
        }
    }
}

[JsonPolymorphic]
[JsonDerivedType(typeof(JsonPersonExtended), nameof(JsonPersonExtended))]
[JsonDerivedType(typeof(JsonPersonExtended2), nameof(JsonPersonExtended2))]
private class JsonPerson
{
    public string Name { get; set; }
    public Person Child { get; set; }
    public Person Parent { get; set; }
}

private class JsonPersonExtended : JsonPerson
{
    public int Age { get; set; }
}

private class JsonPersonExtended2 : JsonPerson
{
    public string Location { get; set; }
}

SignalR 的改善活動

SignalR 現在有名為 Microsoft.AspNetCore.SignalR.Server 的 ActivitySource,其會發出中樞方法呼叫的事件:

  • 每個方法都是它自己的活動,因此在中樞方法呼叫期間發出活動的任何活動都位於中樞方法活動之下。
  • 中樞方法活動無父系。 這表示它們不會組合在長時間執行的 SignalR 連線下。

下列範例使用 .NET Aspire 儀表板OpenTelemetry 套件:

<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />

將下列啟動程式碼新增至 Program.cs 檔案:

// Set OTEL_EXPORTER_OTLP_ENDPOINT environment variable depending on where your OTEL endpoint is
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        if (builder.Environment.IsDevelopment())
        {
            // We want to view all traces in development
            tracing.SetSampler(new AlwaysOnSampler());
        }

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.AspNetCore.SignalR.Server");
    });

builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());

以下是 Aspire 儀表板的範例輸出:

SignalR 中樞方法呼叫事件的活動清單

最小 API

本節說明基本 API 的新功能。

已將 InternalServerErrorInternalServerError<TValue> 新增至 TypedResults

TypedResults 類別是從最小 API 傳回強型別 HTTP 狀態碼式回應的實用工具。 TypedResults 現在包含從端點傳回「500 內部伺服器錯誤」回應的 Factory 方法和類型。 以下是傳回 500 回應的範例:

var app = WebApplication.Create();

app.MapGet("/", () => TypedResults.InternalServerError("Something went wrong!"));

app.Run();

OpenAPI

適用於 OpenAPI 文件產生的內建支援

OpenAPI 規格是用於描述 HTTP API 的標準。 該標準可讓開發人員定義 API 的圖形,這些 API 可以插入用戶端產生器、伺服器產生器、測試工具、文件等等。 在 .NET 9 Preview 中,ASP.NET Core 可提供內建支援,以透過 Microsoft.AspNetCore.OpenApi 套件產生代表控制器型或最小 API 的 OpenAPI 文件。

下列醒目提示的程式碼會呼叫:

  • AddOpenApi,以將必要的相依性註冊到應用程式的 DI 容器中。
  • MapOpenApi,以在應用程式的路由中註冊必要的 OpenAPI 端點。
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

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

app.Run();

使用下列命令在專案中安裝 Microsoft.AspNetCore.OpenApi 套件:

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

執行應用程式並瀏覽至 openapi/v1.json,以檢視產生的 OpenAPI 文件:

OpenAPI 文件

您也可以藉由新增 Microsoft.Extensions.ApiDescription.Server 套件,在建置階段產生 OpenAPI 文件:

dotnet add package Microsoft.Extensions.ApiDescription.Server --prerelease

在應用程式的專案檔中,新增下列項目:

<PropertyGroup>
  <OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)</OpenApiDocumentsDirectory>
  <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
</PropertyGroup>

在專案目錄中執行 dotnet build 並檢查所產生的 JSON 檔案。

建置階段的 OpenAPI 文件產生

ASP.NET Core 內建的 OpenAPI 文件產生,為各種自訂和選項提供支援。 它提供文件和作業轉換器,而且能夠管理相同應用程式的多份 OpenAPI 文件。

若要深入了解 ASP.NET Core 的新 OpenAPI 文件功能,請參閱新的 Microsoft.AspNetCore.OpenApi 文件

OpenAPI 套件的 Intellisense 完成增強功能

ASP.NET Core 的 OpenAPI 支援現在更容易存取且方便使用。 OpenAPI API 會以獨立套件的形式隨附,與共用架構區分。 到目前為止,這表示開發人員沒有適用於 OpenAPI API 的 Intellisense 等程式碼完成功能的便利性。

我們識別到此差距,引進了新的完成提供者和程式碼修正程式。 這些工具的設計目的是為了方便探索及使用 OpenAPI API,讓開發人員更輕鬆地將 OpenAPI 整合至其專案中。 完成提供者提供即時程式碼建議,而程式碼修正程式可協助修正常見的錯誤並改善 API 使用方式。 這項增強功能是我們持續致力於改善開發人員體驗並簡化 API 相關工作流程的一部分。

當使用者輸入 OpenAPI 相關 API 的陳述式時,完成提供者會顯示 API 的建議。 例如,在下列螢幕擷取畫面中,當使用者在支援的類型上輸入叫用陳述式時,會提供 AddOpenApiMapOpenApi 的完成,例如 IEndpointConventionBuilder

OpenAPI 完成

當接受完成且未安裝 Microsoft.AspNetCore.OpenApi 套件時,程式碼修正程式會提供快捷方式以自動在專案中安裝相依性。

自動套件安裝

支援參數和屬性上的 [Required][DefaultValue] 屬性

[Required][DefaultValue] 屬性套用至複雜類型內的參數或屬性時,OpenAPI 實作會將這些屬性對應至與參數或類型結構描述相關聯的 OpenAPI 文件中的 requireddefault 屬性。

例如,下列 API 會產生 Todo 類型的隨附結構描述。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}
{
	"required": [
	  "title",
	  "description",
	  "createdOn"
	],
	"type": "object",
	"properties": {
	  "id": {
	    "type": "integer",
	    "format": "int32"
	  },
	  "title": {
	    "type": "string"
	  },
	  "description": {
	    "type": "string",
	    "default": "A new todo"
	  },
	  "createdOn": {
	    "type": "string",
	    "format": "date-time"
	  }
	}
}

OpenAPI 文件上的結構描述轉換器支援

內建 OpenAPI 支援現在隨附結構描述轉換器的支援,可用來修改 System.Text.Json 和 OpenAPI 實作所產生的結構描述。 如同文件和作業轉換器,結構描述轉換器可以在 OpenApiOptions 物件上進行註冊。 例如,下列範例程式碼示範如何使用結構描述轉換器,將範例新增至類型的結構描述。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.OpenApi.Any;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.UseSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.Type == typeof(Todo))
        {
            schema.Example = new OpenApiObject
            {
                ["id"] = new OpenApiInteger(1),
                ["title"] = new OpenApiString("A short title"),
                ["description"] = new OpenApiString("A long description"),
                ["createdOn"] = new OpenApiDateTime(DateTime.Now)
            };
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}

驗證和授權

本章節會說明驗證和授權的新功能。

OIDC 和 OAuth 參數自訂

OAuth 和 OIDC 驗證處理程式現在有個 AdditionalAuthorizationParameters 選項,可讓您更輕鬆地自訂通常包含在重新導向查詢字串中的授權訊息參數。 在 .NET 8 和更早版本中,這需要在自訂處理程式中要求自訂 OnRedirectToIdentityProvider 回撥或覆寫 BuildChallengeUrl 方法。 以下是 .NET 8 程式碼的範例:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.Events.OnRedirectToIdentityProvider = context =>
    {
        context.ProtocolMessage.SetParameter("prompt", "login");
        context.ProtocolMessage.SetParameter("audience", "https://api.example.com");
        return Task.CompletedTask;
    };
});

上述範例現在可以簡化為下列程式碼:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

設定 HTTP.sys 擴充驗證旗標

您現在可以使用新的 EnableKerberosCredentialCachingCaptureCredentials 屬性在 HTTP.sys AuthenticationManager 上設定 HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHINGHTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys 旗標,以最佳化 Windows 驗證的處理方式。 例如:

webBuilder.UseHttpSys(options =>
{
    options.Authentication.Schemes = AuthenticationSchemes.Negotiate;
    options.Authentication.EnableKerberosCredentialCaching = true;
    options.Authentication.CaptureCredentials = true;
});

其他

下列章節會說明其他的新功能。

新的 HybridCache 程式庫

HybridCache API 可縮小現有的 IDistributedCacheIMemoryCache API 中的一些差距。 它也會增加新功能,例如:

  • “Stampede” 保護可防止平行擷取相同工作。
  • 可設定的序列化。

HybridCache 旨在取代現有的 IDistributedCacheIMemoryCache 使用方式,並提供簡單的 API 來新增快取程式碼。 其可提供同時適用於同處理序和跨處理序快取的整合 API。

若要查看 HybridCache API 的簡化方式,請將它與使用 IDistributedCache 的程式碼進行比較。 以下是使用 IDistributedCache 的範例:

public class SomeService(IDistributedCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        var key = $"someinfo:{name}:{id}"; // Unique key for this combination.
        var bytes = await cache.GetAsync(key, token); // Try to get from cache.
        SomeInformation info;
        if (bytes is null)
        {
            // Cache miss; get the data from the real source.
            info = await SomeExpensiveOperationAsync(name, id, token);

            // Serialize and cache it.
            bytes = SomeSerializer.Serialize(info);
            await cache.SetAsync(key, bytes, token);
        }
        else
        {
            // Cache hit; deserialize it.
            info = SomeSerializer.Deserialize<SomeInformation>(bytes);
        }
        return info;
    }

    // This is the work we're trying to cache.
    private async Task<SomeInformation> SomeExpensiveOperationAsync(string name, int id,
        CancellationToken token = default)
    { /* ... */ }
}

每次都要做很多工作,包括序列化之類的事情。 在快取遺漏案例中,您最終可能會有多個並行執行緒、全部都有快取遺漏、全部都擷取基礎資料、全部序列化,並且全部都將資料傳送至快取。

若要使用 HybridCache 簡化並改善此程式碼,我們首先需要新增程式庫 Microsoft.Extensions.Caching.Hybrid

<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0" />

註冊 HybridCache 服務,就像註冊 IDistributedCache 實作一樣:

services.AddHybridCache(); // Not shown: optional configuration API.

現在大部分的快取考量都可以卸除至 HybridCache

public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // Unique key for this combination.
            async cancel => await SomeExpensiveOperationAsync(name, id, cancel),
            token: token
        );
    }
}

我們可透過相依性插入提供 HybridCache 抽象類別的具體實作,但其目的是讓開發人員可以提供 API 的自訂實作。 HybridCache 實作可處理與快取相關的所有事宜,包括並行作業處理。 這裡的 cancel 權杖代表所有並行呼叫者的合併取消,而不只是我們可以看到的呼叫者取消 (也就是 token)。

您可以使用 TState 模式進一步最佳化高輸送量案例,以避免擷取到的變數和每個執行個體回撥所造成的一些額外負荷:

public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // unique key for this combination
            (name, id), // all of the state we need for the final call, if needed
            static async (state, token) =>
                await SomeExpensiveOperationAsync(state.name, state.id, token),
            token: token
        );
    }
}

HybridCache 會使用已設定的 IDistributedCache 實作 (如果有的話),以進行次要的跨處理序快取,例如,使用 Redis。 但即使沒有 IDistributedCacheHybridCache 服務仍會提供同處理序快取和「踩踏」保護。

有關物件重複使用的備註

在使用 IDistributedCache 的一般現有程式碼中,每次從快取中擷取物件都會導致還原序列化。 此行為表示每個並行呼叫者都會取得物件的不同執行個體,而該執行個體無法與其他執行個體互動。 結果是執行緒安全性,因為對相同物件執行個體的並行修改不會產生風險。

由於會根據現有的 IDistributedCache 程式碼來調整許多 HybridCache 使用方式,因此 HybridCache 預設會保留此行為,以避免引進並行錯誤 (bug)。 不過,指定的使用案例原本就是安全執行緒:

  • 如果快取的類型是不可變的。
  • 如果程式碼不修改它們。

在這種情況下,請通知 HybridCache 透過下列方式可安全地重複使用執行個體:

  • 將類型標示為 sealed。 C# 中的 sealed 關鍵字表示無法繼承該類別。
  • [ImmutableObject(true)] 屬性套用於它。 [ImmutableObject(true)] 屬性表示物件的狀態在建立之後即無法變更。

藉由重複使用執行個體,HybridCache 即可降低與每次呼叫的還原序列化相關聯的 CPU 和物件配置額外負荷。 在快取物件很大或受到頻繁存取的案例中,這可以提高效能。

其他 HybridCache 個功能

如同 IDistributedCacheHybridCache 可支援使用 RemoveKeyAsync 方法來依索引鍵移除。

HybridCache 也會提供 IDistributedCache 實作的選用 API,以避免 byte[] 配置。 這項功能由 Microsoft.Extensions.Caching.StackExchangeRedisMicrosoft.Extensions.Caching.SqlServer 套件的預覽版本實作。

序列化會在註冊服務的過程中設定,可透過 WithSerializer.WithSerializerFactory 方法支援類型特定和一般化序列化程式,並可從 AddHybridCache 呼叫鏈結。 根據預設,程式庫會在內部處理 stringbyte[],並使用 System.Text.Json 處理其他所有項目,但您可以使用 protobuf、xml 或其他任何項目。

HybridCache 可支援舊版 .NET 執行階段,甚至包括 .NET Framework 4.7.2 和 .NET Standard 2.0。

如需 HybridCache 的詳細資訊,請參閱 ASP.NET Core 中的 HybridCache 程式庫

開發人員例外狀況頁面改進項目

當應用程式在開發期間擲回未處理的例外狀況時,會隨即顯示 ASP.NET Core 開發人員例外狀況頁面。 開發人員例外狀況頁面可提供例外狀況和要求的詳細資訊。

Preview 3 已將端點中繼資料新增至開發人員例外狀況頁面。 ASP.NET Core 會使用端點中繼資料來控制端點行為,例如路由、回應快取、速率限制、OpenAPI 產生等等。 下圖會顯示開發人員例外狀況頁面 Routing 區段中的新中繼資料資訊:

開發人員例外狀況頁面上的新中繼資料資訊

在測試開發人員例外狀況頁面時,已識別出生活品質略有改善。 它們已隨附於 Preview 4:

  • 更好的文字換行。 長 cookie、查詢字串值和方法名稱無法再新增水平瀏覽器捲軸。
  • 在現代設計中可發現更大的文字。
  • 更一致的資料表大小。

下列動畫影像會顯示新的開發人員例外狀況頁面:

新的開發人員例外狀況頁面

字典偵錯改善

字典和其他索引鍵/值集合的偵錯顯示已改善版面配置。 索引鍵會顯示在偵錯工具的索引鍵資料行中,而不是與值串連。 下列影像顯示偵錯工具中字典的舊顯示與新顯示。

之前:

先前的偵錯工具體驗

新的偵錯工具體驗

ASP.NET Core 有許多索引鍵/值集合。 這個改良的偵錯體驗會套用至:

  • HTTP 標頭
  • 查詢字串
  • 表單
  • Cookie
  • 檢視資料
  • 路由資料
  • 功能

IIS 中應用程式回收期間的 503 修正程式

根據預設,當 IIS 收到回收或關機通知以及當 ANCM 告知受控伺服器啟動關機時,現在會有一秒的延遲。 延遲可透過 ANCM_shutdownDelay 環境變數或設定 shutdownDelay 處理常式設定來設定。 兩個值的單位都是毫秒。 延遲主要是為了減少以下情況下比賽的可能性:

  • IIS 尚未啟動移至新應用程式的佇列要求。
  • ANCM 會開始拒絕進入舊應用程式的新要求。

較緩慢的機器或 CPU 使用量較繁重的機器可能要調整此值,以降低 503 的可能性。

設定 shutdownDelay 的範例:

<aspNetCore processPath="dotnet" arguments="myapp.dll" stdoutLogEnabled="false" stdoutLogFile=".logsstdout">
  <handlerSettings>
    <!-- Milliseconds to delay shutdown by.
    this doesn't mean incoming requests will be delayed by this amount,
    but the old app instance will start shutting down after this timeout occurs -->
    <handlerSetting name="shutdownDelay" value="5000" />
  </handlerSettings>
</aspNetCore>

修正程式位於來自裝載套件組合且已全域安裝的 ANCM 模組中。

最佳化靜態 Web 資產傳遞

遵循提供靜態資產的生產最佳做法需要大量的工作和技術專長。 未經最佳化,例如壓縮、快取和指紋

  • 瀏覽器必須在每個頁面載入時提出其他要求。
  • 透過網路傳輸的位元組數超出了所需的位元組數。
  • 有時會將過時的檔案版本提供給用戶端。

建立高效能的 Web 應用程式需要最佳化資產來傳遞至瀏覽器。 可能的最佳化包括:

  • 提供指定的資產一次,直到檔案變更或瀏覽器清除其快取為止。 設定 ETag 標頭。
  • 在更新應用程式之後,防止瀏覽器使用舊版或過時的資產。 設定上次的修改標頭。
  • 設定適當的快取標頭
  • 使用快取中介軟體
  • 盡可能提供壓縮版本的資產。
  • 使用 CDN 來提供更接近使用者的資產。
  • 將提供給瀏覽器的資產大小降至最低。 此最佳化不包括縮製。

MapStaticAssets 是一個新的中介軟體,可協助最佳化應用程式中靜態資產的傳遞。 其設計目的是在與所有 UI 架構配合使用,包括 Blazor、Razor Pages 和 MVC。 通常是 UseStaticFiles 的直接替換項目:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

+app.MapStaticAssets();
-app.UseStaticFiles();
app.MapRazorPages();

app.Run();

MapStaticAssets 會透過結合建置和發佈時間流程來運作,以收集有關應用程式中所有靜態資源的資訊。 然後,執行階段庫會利用此資訊來有效地將這些檔案提供給瀏覽器。

在大部分情況下,MapStaticAssets 可以取代 UseStaticFiles,不過,它已針對服務應用程式在建置和發佈時間所了解的資產進行了最佳化。 如果應用程式提供來自其他位置的資產 (例如磁碟或內嵌資源),則應使用 UseStaticFiles

MapStaticAssets 提供了 UseStaticFiles 所沒有的以下優點:

  • 應用程式中所有資產的建置時間壓縮:
    • 開發期間的 gzip 和發佈期間的 gzip + brotli
    • 所有資產都會被壓縮,目標是將資產的大小降到最低。
  • 基於內容的 ETags:每個資源的 Etags 都是內容的 SHA-256 雜湊的 Base64 編碼字串。 這可確保瀏覽器只有在檔案的內容已變更時才會重新下載檔案。

下表顯示預設 Razor Pages 範本中 CSS 和 JS 檔案的原始和壓縮大小:

檔案 原始 Compressed 減少百分比
bootstrap.min.css 163 17.5 89.26%
jquery.js 89.6 28 68.75%
bootstrap.min.js 78.5 20 74.52%
總數 331.1 65.5 80.20%

下表顯示使用 Fluent UI Blazor 元件庫的原始和壓縮大小:

檔案 原始 Compressed 減少百分比
fluent.js 384 73 80.99%
fluent.css 94 11 88.30%
總數 478 84 82.43%

未壓縮的總大小為 478 KB,壓縮後的大小為 84 KB。

下表顯示使用 MudBlazorBlazor 元件庫的原始與壓縮大小:

檔案 原始 Compressed 減少
MudBlazor.min.css 541 37.5 93.07%
MudBlazor.min.js 47.4 9.2 80.59%
總數 588.4 46.7 92.07%

使用 MapStaticAssets 時,會自動進行最佳化。 當新增或更新程式庫時 (例如使用新的 JavaScript 或 CSS),資產將作為建置的一部分進行最佳化。 最佳化對於頻寬較低或連線不可靠的行動環境特別有利。

如需新檔案傳遞功能的詳細資訊,請參閱下列資源:

在伺服器上啟用動態壓縮與使用 MapStaticAssets

與伺服器上的動態壓縮相比,MapStaticAssets 具有以下優點:

  • 更簡單,因為沒有伺服器特定的組態。
  • 效能更高,因為資產會在建置時間被壓縮。
  • 可讓開發人員在建置過程中花費額外的時間來確保資產的大小最小。

考慮以下表格,將 MudBlazor 壓縮與 IIS 動態壓縮和 MapStaticAssets 進行比較:

IIS gzip MapStaticAssets MapStaticAssets 減少量
≅ 90 37.5 59%

ASP0026:當 [AllowAnonymous] 從「更遠」覆寫 [Authorize] 時,分析器會發出警告

[AllowAnonymous] 屬性相比,將 [Authorize] 屬性放置在「更接近」MVC 動作的位置會覆寫 [AllowAnonymous] 屬性並強制授權,這似乎是直觀的。 不過,不一定是這種情況。 重要的是屬性的相對順序。

下列程式碼顯示更接近 [Authorize] 屬性的範例,會由離得更遠的 [AllowAnonymous] 屬性覆寫。

[AllowAnonymous]
public class MyController
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on the class
    public IActionResult Private() => null;
}
[AllowAnonymous]
public class MyControllerAnon : ControllerBase
{
}

[Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
public class MyControllerInherited : MyControllerAnon
{
}

public class MyControllerInherited2 : MyControllerAnon
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
    public IActionResult Private() => null;
}
[AllowAnonymous]
[Authorize] // Overridden by the preceding [AllowAnonymous]
public class MyControllerMultiple : ControllerBase
{
}

在 .NET 9 Preview 6 中,我們引進了分析器,其中會醒目顯示此類的執行個體,其中更接近 [Authorize] 屬性會由遠離 MVC 動作的 [AllowAnonymous] 屬性覆寫。 警告會指向已覆寫的 [Authorize] 屬性,並顯示下列訊息:

ASP0026 [Authorize] overridden by [AllowAnonymous] from farther away

如果您看到此警告,則所要採取的正確動作取決於屬性背後的意圖。 如果不小心將端點公開給匿名使用者,則應該移除更遠的 [AllowAnonymous] 屬性。 如果 [AllowAnonymous] 屬性是要覆寫更接近的 [Authorize] 屬性,您可以在 [Authorize] 屬性之後重複 [AllowAnonymous] 屬性,以釐清意圖。

[AllowAnonymous]
public class MyController
{
    // This produces no warning because the second, "closer" [AllowAnonymous]
    // clarifies that [Authorize] is intentionally overridden.
    // Specifying AuthenticationSchemes can still be useful
    // for endpoints that allow but don't require authenticated users.
    [Authorize(AuthenticationSchemes = "Cookies")]
    [AllowAnonymous]
    public IActionResult Privacy() => null;
}