ASP.NET Core 9.0 の新機能

この記事では、ASP.NET Core 9.0 の最も大きな変更点について説明します。また、関連するドキュメントへのリンクも示します。

この記事は、.NET 9 リリース候補 2 で更新されました。

Blazor

このセクションでは、Blazor の新機能について説明します。

.NET MAUIBlazor Hybrid および Web アプリ ソリューション テンプレート

新しいソリューション テンプレートを使用すると、共通の UI を備えた .NET MAUI ネイティブ クライアント アプリと Blazor Web クライアント アプリを簡単に作成できます。 このテンプレートは、コードの再利用を最大化し、Android、iOS、Mac、Windows、Web をターゲットにするクライアント アプリ開発を行う方法を示します。

このテンプレートの主な特長:

  • どの Blazor 対話型レンダリング モードを Web アプリで使用するかを選択できます。
  • Blazor Web App (グローバル対話型オート レンダリング) と .NET MAUIBlazor Hybrid アプリを含む適切なプロジェクトの自動作成。
  • 作成されたプロジェクトは、共有 Razor クラス ライブラリ (RCL) を使用して、UI の Razor コンポーネントを維持します。
  • 依存関係インジェクションを使用して Blazor Hybrid アプリと Blazor Web App に異なるインターフェイス実装を提供する方法を示すサンプル コードが含まれています。

開始するには、.NET 9 SDK をインストールして、.NET MAUI ワークロードをインストールします。これには以下のテンプレートが含まれています。

dotnet workload install maui

次のコマンドを使用して、コマンド シェルでプロジェクト テンプレートからソリューションを作成します。

dotnet new maui-blazor-web

このテンプレートは、Visual Studio にも用意されています。

Note

現時点で、Blazor レンダリング モードがページごと/コンポーネント レベルで定義されていると例外がスローされます。 詳しくは、「BlazorWebView では ResolveComponentForRenderMode のオーバーライドを有効にする方法が必要 (dotnet/aspnetcore #51235)」をご覧ください。

詳細については、「Blazor Web Appで .NET MAUIBlazor Hybrid アプリを構築する」をご覧ください。

静的アセットの配信の最適化

MapStaticAssets は、Blazor アプリを含む ASP.NET Core アプリ内の静的アセットの配信の最適化に役立つ新しいミドルウェアです。

詳細については、次のいずれかのリソースを参照してください。

実行時にレンダリングの場所、対話機能、および割り当てられたレンダリング モードを検出する

実行時にコンポーネントの状態を照会するプロセスを簡略化するように設計された新しい 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 App 用のシンプルな認証状態シリアル化

新しい API では、既存の Blazor Web App に認証を追加することが容易になっています。 新しい Blazor Web App を、個別アカウントによる認証を使用して作成し、WebAssembly ベースの対話機能を有効にすると、そのプロジェクトでは、カスタム AuthenticationStateProvider がサーバー プロジェクトとクライアント プロジェクトの両方に含められます。

それらのプロバイダーは、ユーザーの認証状態をブラウザーに送信します。 クライアントではなくサーバーで認証を行う場合、アプリは、プリレンダリング中、.NET WebAssembly ランタイムが初期化される前に認証状態にアクセスできます。

カスタムの AuthenticationStateProvider の実装は、Persistent Component State サービス (PersistentComponentState) を使用して認証状態を HTML コメントにシリアル化し、その後に WebAssembly からそれを再度読み取って新しい AuthenticationState インスタンスを作成します。

この方法は、Blazor Web App プロジェクト テンプレートから開始して [個別アカウント] オプションを選択する場合はうまく機能しますが、既存プロジェクトに認証を追加する場合には、大量のコードを自力で実装またはコピーする必要があります。 そこで現在は、この機能を追加する手段として、サーバー プロジェクトとクライアント プロジェクトの両方から呼び出せる以下の API が用意されています。これらは現在、Blazor Web App プロジェクト テンプレートの一部となっています。

  • AddAuthenticationStateSerialization: サーバー上の認証状態をシリアル化するために必要なサービスを追加します。
  • AddAuthenticationStateDeserialization: ブラウザーの認証状態を逆シリアル化するために必要なサービスを追加します。

既定では、この API がシリアル化するのは、ブラウザーでアクセスするためのサーバー側の名前とロールの要求だけです。 オプションを AddAuthenticationStateSerialization に渡して、すべての要求を含めることができます。

詳細については、「ASP.NET Core のサーバーサイド Blazor アプリを保護する」をご覧ください。。

グローバル対話型 Blazor Web App に静的サーバー側レンダリング (SSR) ページを追加する

.NET 9 のリリースにより、グローバルなインタラクティビティを採用するアプリに静的 SSR ページを追加するアプローチが簡単になりました。

このアプローチが役に立つのは、インタラクティブ サーバーまたは WebAssembly レンダリングを使用して動作できない特定のページがアプリにある場合のみです。 たとえば、HTTP Cookie の読み取り/書き込みに依存し、インタラクティブ レンダリングの代わりに要求/応答サイクルでのみ動作できるページには、このアプローチを採用します。 インタラクティブ レンダリングを使用するページでは、静的 SSR レンダリングを強制的に使用しないでください。静的 SSR レンダリングは、エンド ユーザーにとって効率と応答性が低いからです。

@attribute Razor ディレクティブに割り当てられた新しい [ExcludeFromInteractiveRouting] 属性を使用して Razor コンポーネント ページをマークします。

@attribute [ExcludeFromInteractiveRouting]

この属性を適用すると、インタラクティブ ルーティングでページへのナビゲーションが終了します。 インバウンド ナビゲーションでは、インタラクティブ ルーティングを使用してページを解決するのではなく、ページ全体の再読み込みが強制的に実行されます。 ページ全体の再読み込みでは、最上位のルート コンポーネント (通常は App コンポーネント (App.razor)) がサーバーから再レンダリングするよう強制されるため、アプリは別の最上位レベルのレンダリング モードに切り替えることができます。

HttpContext.AcceptsInteractiveRouting 拡張メソッドを使用すると、このコンポーネントは [ExcludeFromInteractiveRouting] が現在のページに適用されているかどうかを検出できます。

App コンポーネントでは、次の例のパターンが使用されます。

  • [ExcludeFromInteractiveRouting] の注釈が付けられていないページは、グローバル インタラクティビティを使用した InteractiveServer レンダリング モードに既定で設定されます。 InteractiveServerInteractiveWebAssembly または InteractiveAuto に置き換えて、別の既定のグローバル レンダリング モードを指定できます。
  • [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 コンポーネントではコンストラクターの挿入をサポートしています。

次の例では、部分 (コードビハインド) クラスで、プライマリ コンストラクターを使用して NavigationManager サービスを挿入します。

public partial class ConstructorInjection(NavigationManager navigation)
{
    private void HandleClick()
    {
        navigation.NavigateTo("/counter");
    }
}

詳細については、「ASP.NET Core Blazor の依存関係の挿入」を参照してください。

対話型サーバー コンポーネントの Websocket 圧縮

既定では、対話型サーバー コンポーネンは WebSocket 接続の圧縮を有効にして、frame-ancestors コンテンツ セキュリティ ポリシー (CSP) ディレクティブを 'self' に設定します。これは、圧縮が有効になっている場合または WebSocket コンテキストの構成が提供されている場合にのみ、アプリの提供元の <iframe> にアプリを埋め込むことを許可します。

圧縮を無効にするには、ConfigureWebSocketOptionsnull に設定します。こうすると、攻撃対象のアプリの脆弱性は低くなりますが、パフォーマンスが低下するおそれがあります。

.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>

InputNumber コンポーネントは、type="range" 属性をサポートします

InputNumber<TValue> コンポーネントでは、type="range" 属性がサポートされるようになりました。これにより、通常はテキスト ボックスではなくスライダーまたはダイヤル コントロールとしてレンダリングされる、モデル バインドとフォーム検証をサポートする範囲入力が作成されます。

<EditForm Model="Model" OnSubmit="Submit" FormName="EngineForm">
    <div>
        <label>
            Nacelle Count (2-6): 
            <InputNumber @bind-Value="Model!.NacelleCount" max="6" min="2" 
                step="1" type="range" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private EngineSpecifications? Model { get; set; }

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

    private void Submit() {}

    public class EngineSpecifications
    {
        [Required, Range(minimum: 2, maximum: 6)]
        public int NacelleCount { get; set; }
    }
}

サーバー プロジェクトごとに複数の Blazor Web App

.NET 10 (2025 年 11 月) には、サーバー プロジェクトごとに複数の Blazor Web App のサポートが検討されます。

詳細については、「サーバー プロジェクト (dotnet/aspnetcore #52216) ごとの複数の Blazor Web アプリのサポート」をご覧ください。

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 ハブ メソッド呼び出しイベントのアクティビティ一覧

SignalR はトリミングとネイティブ AOT をサポートする

.NET 8 で開始のネイティブ AOT 体験を続けて、SignalR クライアントとサーバーの両方のシナリオで、トリミングとネイティブの事前 (AOT) コンパイルのサポートを有効にしました。 リアルタイムの Web 通信に SignalR を使用するアプリケーションで、ネイティブ AOT を使用する場合のパフォーマンス上の利点を活用できるようになりました。

作業の開始

最新の .NET 9 SDK をインストールします。

次のコマンドを使用して、コマンド シェルで webapiaot テンプレートからソリューションを作成します。

dotnet new webapiaot -o SignalRChatAOTExample

Program.cs ファイルの内容を次の SignalR コードに置き換えます。

using Microsoft.AspNetCore.SignalR;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddSignalR();
builder.Services.Configure<JsonHubProtocolOptions>(o =>
{
    o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapHub<ChatHub>("/chatHub");
app.MapGet("/", () => Results.Content("""
<!DOCTYPE html>
<html>
<head>
    <title>SignalR Chat</title>
</head>
<body>
    <input id="userInput" placeholder="Enter your name" />
    <input id="messageInput" placeholder="Type a message" />
    <button onclick="sendMessage()">Send</button>
    <ul id="messages"></ul>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.on("ReceiveMessage", (user, message) => {
            const li = document.createElement("li");
            li.textContent = `${user}: ${message}`;
            document.getElementById("messages").appendChild(li);
        });

        async function sendMessage() {
            const user = document.getElementById("userInput").value;
            const message = document.getElementById("messageInput").value;
            await connection.invoke("SendMessage", user, message);
        }

        connection.start().catch(err => console.error(err));
    </script>
</body>
</html>
""", "text/html"));

app.Run();

[JsonSerializable(typeof(string))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

前の例では、10 MB のネイティブ Windows 実行可能ファイルと 10.9 MB の Linux 実行可能ファイルが生成されます。

制限事項

  • 現在 JSON プロトコルのみがサポートされています。
    • 前のコードに示すように、JSON シリアル化とネイティブ AOT を使用するアプリでは、System.Text.Json ソース ジェネレーターを使用する必要があります。
    • これは、最小 API と同じアプローチに従います。
  • SignalR サーバーでは、T が ValueType (つまり、struct) である IAsyncEnumerable<T> 型と ChannelReader<T> 型のハブ メソッド パラメーターはサポートされていません。 これらの型を使用すると、開発時および発行済みアプリの起動時にランタイム例外が発生します。 詳細については、「SignalR: ネイティブ AOT (dotnet/aspnetcore #56179) の ValueTypes で IAsyncEnumerable <T> と ChannelReader <T> を使用する」をご覧ください。
  • 厳密に型指定されたハブ はネイティブ AOT (PublishAot) ではサポートされていません。 ネイティブ AOT で厳密に型指定されたハブを使用すると、ビルドと発行中に警告が発生し、ランタイム例外が発生します。 厳密に型指定されたハブとトリミング (PublishedTrimmed) の使用がサポートされています。
  • 非同期の戻り値の型では、TaskTask<T>ValueTask または ValueTask<T> のみがサポートされます。

最小 Api

このセクションでは、最小 API の新機能について説明します。

InternalServerErrorInternalServerError<TValue>TypedResults に追加しました

この TypedResults クラスは、厳密に型指定された HTTP 状態コードベースの応答を最小限の API から返すのに役立つ手段です。 TypedResults には、エンドポイントから "500 内部サーバー エラー" 応答を返すファクトリ メソッドと型が含まれるようになりました。 500 応答を返す例を次に示します。

var app = WebApplication.Create();

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

app.Run();

ルート グループで ProducesProblemProducesValidationProblem を呼び出す

ルート グループでの使用をサポートするために、ProducesProblem および ProducesValidationProblem 拡張メソッドが更新されました。 これらのメソッドは、ルート グループ内のすべてのエンドポイントが、OpenAPI メタデータの目的で ProblemDetails または ValidationProblemDetails 応答を返すことができることを示します。

var app = WebApplication.Create();

var todos = app.MapGroup("/todos")
    .ProducesProblem();

todos.MapGet("/", () => new Todo(1, "Create sample app", false));
todos.MapPost("/", (Todo todo) => Results.Ok(todo));

app.Run();

record Todo(int Id, string Title, boolean IsCompleted);

OpenAPI

このセクションでは、OpenAPI の新機能について説明します。

OpenAPI ドキュメント生成の組み込みサポート

OpenAPI 仕様は、HTTP API を記述するための標準です。 この標準により、開発者は、クライアント ジェネレーター、サーバー ジェネレーター、テスト ツール、ドキュメントなどに接続できる API の形状を定義できます。 .NET 9 プレビューでは、ASP.NET Core は、Microsoft.AspNetCore.OpenApi パッケージを使用して、コントローラー ベースまたは最小限の API を表す OpenAPI ドキュメントを生成するための組み込みサポートを提供します。

次の強調されているコードでは、次が呼び出されます。

  • 必要な依存関係をアプリの DI コンテナーに登録するための AddOpenApi
  • 必要な OpenAPI エンドポイントをアプリのルートに登録するための MapOpenApi
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 ドキュメント

OpenAPI ドキュメントは、Microsoft.Extensions.ApiDescription.Server パッケージを追加することで、ビルド時に生成することもできます。

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 パッケージがインストールされていない場合、codefixer で依存関係を自動的にプロジェクトにインストールするショートカットが提供されます。

パッケージの自動インストール

パラメーターとプロパティの [Required] 属性および [DefaultValue] 属性のサポート

[Required] 属性および [DefaultValue] 属性が複合型内のパラメーターまたはプロパティに適用される場合、OpenAPI の実装でパラメーターまたは型スキーマに関連付けられた OpenAPI ドキュメント内の required プロパティおよび default プロパティにマッピングされます。

たとえば、次の 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.AddSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.JsonTypeInfo.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; }
}

Microsoft.AspNetCore.OpenApi でのトランスフォーマー登録 API の機能強化

OpenAPI トランスフォーマーは、OpenAPI ドキュメント、ドキュメント内の操作、または API の型に関連付けられているスキーマの変更をサポートします。 OpenAPI ドキュメントにトランスフォーマーを登録するための API には、トランスフォーマーを登録するためのさまざまなオプションが用意されています。

以前は、トランスフォーマーを登録するために次のAPIが利用可能でした。

OpenApiOptions AddDocumentTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddDocumentTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions AddDocumentTransformer<IOpenApiDocumentTransformer>()
OpenApiOptions UseSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>)
OpenApiOptions AddOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task>)

新しい API セットは次のとおりです。

OpenApiOptions AddDocumentTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddDocumentTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions AddDocumentTransformer<IOpenApiDocumentTransformer>()

OpenApiOptions AddSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddSchemaTransformer(IOpenApiSchemaTransformer transformer)
OpenApiOptions AddSchemaTransformer<IOpenApiSchemaTransformer>()

OpenApiOptions AddOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddOperationTransformer(IOpenApiOperationTransformer transformer)
OpenApiOptions AddOperationTransformer<IOpenApiOperationTransformer>()

Microsoft.AspNetCore.OpenApi はトリミングとネイティブ AOT がサポートする

ASP.NET Core の新しい組み込み OpenAPI は、トリミングとネイティブ AOT もサポートします。

作業の開始

新しい ASP.NET Core Web API (ネイティブ AOT) プロジェクトを作成します。

dotnet new webapiaot

Microsoft.AspNetCore.OpenAPI パッケージを追加します。

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

このプレビューでは、トリミングの警告を回避するために、最新の Microsoft.OpenAPI パッケージを追加する必要もあります。

dotnet add package Microsoft.OpenApi

Program.csを更新して、OpenAPI ドキュメントの生成を有効にします。

+ builder.Services.AddOpenApi();

var app = builder.Build();

+ app.MapOpenApi();

アプリの発行

dotnet publish

アプリは、警告なしでネイティブ AOT を使用して発行します。

ルート グループでの ProducesProblemProducesValidationProblem の呼び出しをサポートする

ルート グループの ProducesProblem および ProducesValidationProblem 拡張メソッドが更新されました。 これらのメソッドを使用して、ルート グループ内のすべてのエンドポイントが OpenAPI メタデータの目的で ProblemDetails 応答または ValidationProblemDetails 応答を返すことができることを示します。

var app = WebApplication.Create();

var todos = app.MapGroup("/todos")
    .ProducesProblem(StatusCodes.Status500InternalServerError);

todos.MapGet("/", () => new Todo(1, "Create sample app", false));
todos.MapPost("/", (Todo todo) => Results.Ok(todo));

app.Run();

record Todo(int Id, string Title, bool IsCompleted);

Problem および ValidationProblem 戻り値の型は IEnumerable<KeyValuePair<string, object?>> 値を使用した構築をサポートする

.NET 9 より前のバージョンでは、ProblemValidationProblem 戻り値の型は、IDictionary<string, object?>の実装を使用して errors および extensions プロパティを初期化するために必要な最小 API になります。 このリリースでは、これらの構築 API が IEnumerable<KeyValuePair<string, object?>> を使用するオーバーロードをサポートしています。

var app = WebApplication.Create();

app.MapGet("/", () =>
{
    var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions",
                                                       extensions: extensions);
});

この投稿に関してGitHub ユーザー joegoldman2 に感謝します。

認証と権限承認

このセクションでは、認証と認可の新機能について説明します。

OpenIdConnectHandler はプッシュ承認要求 (PAR) のサポートを追加する

ASP.NET Core の OpenIdConnectHandler にプッシュ承認要求 (PAR) を追加していただいたJoe DeCock 氏 (Duende ソフトウェア) に感謝します。 Joe は、 彼の API 提案で PAR を有効にするための背景と動機を次のように説明しました。

プッシュされた承認要求 (PAR) は比較的新しい OAuth 標準です。これにより、フロント チャネルからバック チャネルに承認パラメーターを移動させることで、OAuth フローと OIDC フローのセキュリティを向上します。 つまり、ブラウザーのリダイレクト URL からバックエンド上のマシン http 呼び出しにマシンをリダイレクトするように承認パラメーターを移動します。

これにより、攻撃者は次の操作を実行できなくなります。

  • 承認パラメーターが表示され、PII がリークする可能性があります。
  • これらのパラメーターの改ざん。 たとえば、攻撃によって、要求されるアクセスのスコープが変更される可能性があります。

承認パラメーターをプッシュすると、要求 URL も短くなります。 Rich 承認要求など、より複雑な OAuth および OIDC 機能を使用すると、承認パラメーターが非常に長くなる可能性があります。 長い URL は、多くのブラウザーやネットワーク インフラストラクチャで問題を引き起こします。

PAR の使用は、OpenID 基盤内の FAPI ワーキング グループ によって推奨されます。 たとえば、 FAPI2.0 セキュリティ プロファイル では PAR を使用する必要があります。 このセキュリティ プロファイルは、オープン バンキング (主にヨーロッパ)、医療、およびセキュリティ要件の高い他の業界で作業している多くのグループによって使用されます。

PAR は、次のような多数の identity プロバイダーによってサポートされています。

.NET 9 では、identity プロバイダーの検出ドキュメントが PAR のサポートを提供する場合、既定で PAR を有効にすることにしました。これは、PAR をサポートするプロバイダーのセキュリティ強化を提供する必要があるためです。 identity プロバイダーの検出ドキュメントは、通常、.well-known/openid-configuration にあります。 これにより問題が発生した場合は、次のように OpenIdConnectOptions.PushedAuthorizationBehavior を使用して PAR を無効にすることができます。

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.

        // 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
        // and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
        // of type 'OpenIdConnectOptions' could be found
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

PAR が使用されている場合にのみ認証が成功するようにするには、代わりに PushedAuthorizationBehavior.Require を使用します。 この変更により、OpenIdConnectEvents に対する新しい OnPushAuthorization イベントも導入されます。これにより、プッシュされた承認要求をカスタマイズしたり、手動で処理したりできます。 詳細については、API 提案 をご覧ください。

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 拡張認証フラグを構成する

Windows 認証の処理方法を最適化するために、HTTP.sys AuthenticationManager で新しい EnableKerberosCredentialCaching および CaptureCredentials プロパティを使用することで、HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING および HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys フラグを構成できるようになりました。 次に例を示します。

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

その他

以下のセクションでは、その他の新機能について説明します。

新しい HybridCache ライブラリ

HybridCache API は、既存の IDistributedCache および IMemoryCache API のいくつかのギャップを埋めます。 また、次のような新しい機能も追加されます。

  • 同じ作業の並列フェッチを防ぐための "スタンピード" 保護
  • 構成可能なシリアル化。

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" />

IDistributedCache 実装を登録する場合と同様に、HybridCache サービスを登録します。

builder.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
        );
    }
}

Microsoft は、依存関係の挿入を使用して 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 は、Redis など使用を使用して、セカンダリ アウトプロセス キャッシュ用として構成された IDistributedCache 実装 (存在する場合) を使用します。 ただし、IDistributedCache がなくても、HybridCache サービスは引き続きインプロセス キャッシュと "スタンピード" 保護を提供します。

オブジェクトの再利用に関する注意事項

IDistributedCache を使用する一般的な既存のコードでは、キャッシュからオブジェクトを取得するたびに、逆シリアル化が行われます。 この動作は、各同時実行呼び出し元が、その他のインスタンスとは対話できない、オブジェクトの個別のインスタンスを取得することを意味します。 この結果、同じオブジェクト インスタンスに同時に変更を加えるリスクがなくなるため、スレッド セーフになります。

HybridCache の使用の多くは既存の IDistributedCache コードから適応されるため、HybridCache は、コンカレンシーのバグが発生しないように既定でこの動作を保持します。 ただし、次の特定のユース ケースは本質的にスレッド セーフです。

  • キャッシュされる型が不変である場合。
  • コードで型が変更されない場合。

このような場合、次の方法により、インスタンスを再利用しても安全であることを HybridCache に通知します。

  • 型を sealed としてマークします。 C# の sealed キーワードは、クラスを継承できないことを意味します。
  • それに [ImmutableObject(true)] 属性を追加します。 [ImmutableObject(true)] 属性は、オブジェクトの作成後にその状態を変更できないことを示します。

インスタンスを再利用することで、HybridCache は、呼び出しごとの逆シリアル化に関連する CPU とオブジェクトの割り当てのオーバーヘッドを削減できます。 この結果、キャッシュされたオブジェクトが大きいか頻繁にアクセスされるシナリオでパフォーマンスが向上する可能性があります。

その他の HybridCache 機能

IDistributedCache と同様に、HybridCache は、RemoveKeyAsync メソッドを使用したキーによる削除をサポートしています。

HybridCache には、byte[] の割り当てを回避するために、IDistributedCache 実装用の省略可能な API も用意されています。 この機能は、Microsoft.Extensions.Caching.StackExchangeRedis および Microsoft.Extensions.Caching.SqlServer パッケージのプレビュー バージョンによって実装されます。

シリアル化は、サービスの登録の一環として構成され、AddHybridCache 呼び出しからチェーンされる WithSerializer および .WithSerializerFactory メソッドを介した型固有シリアライザーと汎用シリアライザーがサポートされます。 既定では、このライブラリによって stringbyte[] が内部的に処理され、その他すべてに System.Text.Json が使用されますが、protobuf、xml またはその他を使用することもできます。

HybridCache は、.NET Framework 4.7.2 および .NET Standard 2.0 までの古い .NET ランタイムをサポートしています。

HybridCache に関する詳細については、「ASP.NET Core の HybridCache ライブラリ」を参照してください

開発者例外ページの機能強化

ASP.NET Core 開発者例外ページ は、開発中にアプリがハンドルされない例外をスローすると、表示されます。 開発者例外ページには、例外と要求に関する詳細な情報が表示されます。

プレビュー 3 では、開発者例外ページにエンドポイント メタデータが追加されました。 ASP.NET Core では、エンドポイント メタデータを使用して、ルーティング、応答キャッシュ、レート制限、OpenAPI 生成などのエンドポイント動作を制御します。 次の画像は、開発者例外ページの Routing セクションの新しいメタデータ情報を示しています。

開発者例外ページの新しいメタデータ情報

開発者例外ページのテスト中に、小幅の質の向上が確認されました。 これらは、プレビュー 4 で提供されています。

  • テキスト折り返しの改善。 長い Cookie、クエリ文字列の値、メソッド名にブラウザーの水平スクロール バーが追加されなくなりました。
  • 最新のデザインで使用されている、より大きなテキスト。
  • より一貫性のあるテーブル サイズ。

次のアニメーション画像は、新しい開発者例外ページを示しています。

新しい開発者例外ページ

ディクショナリのデバッグの機能強化

ディクショナリやその他のキー値コレクションのデバッグ表示のレイアウトが改善されました。 キーは、値と連結されるのではなく、デバッガーのキー列に表示されます。 次の図に、デバッガーでのディクショナリの新旧の表示を示します。

以前:

以前のデバッガー エクスペリエンス

以後:

新しいデバッガー エクスペリエンス

ASP.NET Core には、多くのキー値コレクションがあります。 この改善されたデバッグ エクスペリエンスは、次に適用されます。

  • HTTP ヘッダー
  • クエリ文字列
  • フォーム
  • Cookie
  • データの表示
  • ルート データ
  • 機能

IIS でのアプリのリサイクル中の 503 の修正

既定では現在、IIS にリサイクルまたはシャットダウンが通知されてから、ANCM がマネージド サーバーにシャットダウンを開始するよう指示するまでに 1 秒の遅延があります。 遅延は、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 ヘッダーを設定する。
  • アプリの更新後にブラウザーが以前のアセットや古くなったアセットを使用することを防ぐ。 Last-Modified ヘッダーを設定する。
  • 適切なキャッシュ ヘッダーを設定する。
  • キャッシュ ミドルウェアを使用する。
  • 可能な場合は圧縮バージョンのアセットを提供する。
  • CDN を使用してユーザーにより近いアセットを提供する。
  • ブラウザーに提供される資産のサイズを最小化します。 この最適化には縮小は含まれません。

MapStaticAssets は、アプリ内の静的アセットの配信を最適化するのに役立つ新しいミドルウェアです。 これは、Blazor、Razor Pages、MVC など、すべての UI フレームワークで動作するように設計されています。 これは通常、以下のように 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: [Authorize] が by [AllowAnonymous] によって "遠く "からオーバーライドされた場合に警告するアナライザー

[Authorize] 属性を [AllowAnonymous] 属性よりも 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 では、MVC アクションから遠い [AllowAnonymous] 属性によって近い [Authorize] 属性がオーバーライドされる、このようなインスタンスを強調表示するアナライザーを導入しました。 警告は、オーバーライドされた [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;
}

Kestrel 接続メトリックの改善

接続に失敗した理由に関するメタデータを含めることで、Kestrel の接続メトリックが大幅に改善されました。 kestrel.connection.duration メトリックに、接続の終了理由が error.type 属性に含まれるようになりました。

error.type 値の小さなサンプルを次に示します。

  • tls_handshake_failed - 接続に TLS が必要であり、TLS ハンドシェイクに失敗しました。
  • connection_reset - 要求の進行中に、クライアントによって接続が予期せず閉じられました。
  • request_headers_timeout - Kestrel が時間内に要求ヘッダーを受信しなかったため、接続を閉じました。
  • max_request_body_size_exceeded - アップロードされたデータが最大サイズを超えたため、Kestrel は接続を閉じました。

以前は、 Kestrel 接続の問題を診断するには、サーバーが、詳細で低レベルのログを記録する必要があります。 ただし、ログは生成と格納にコストがかかる場合があり、ノイズの中から適切な情報を見つけるのが難しい場合があります。

メトリックは、影響を最小限に抑えながら運用環境に残すことができる、はるかに安価な代替手段です。 収集されたメトリックは、ダッシュボードとアラートを駆動できます。 上位のレべルでメトリックにより問題が特定されたら、ログやその他のツールを使用してさらに調査を開始できます。

接続メトリックの改善は、多くのシナリオで役立つことを期待しています。

  • 接続の有効期間が短い場合に発生するパフォーマンスの問題を調査しています。
  • パフォーマンスと安定性に影響を与える Kestrel に対する継続的な外部攻撃を監視する。
  • Kestrel の組み込みのセキュリティ強化が防止された Kestrel に対する外部攻撃を記録する。

詳細については、ASP.NET Core メトリック をご覧ください。

Kestrel 名前付きパイプ エンドポイントをカスタマイズする

Kestrelの名前付きパイプのサポートが、高度なカスタマイズ オプションで改善されました。 名前付きパイプ オプションの新しい CreateNamedPipeServerStream メソッドを使用すると、エンドポイントごとにパイプをカスタマイズできます。

これが役立つ例は、access セキュリティが異なる 2 つのパイプ エンドポイントを必要とする Kestrel アプリです。 CreateNamedPipeServerStream オプションを使用すると、パイプ名に応じて、カスタム セキュリティ設定でパイプを作成できます。

var builder = WebApplication.CreateBuilder();

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("pipe1");
    options.ListenNamedPipe("pipe2");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeSecurity = CreatePipeSecurity(context.NamedPipeEndpoint.PipeName);

        return NamedPipeServerStreamAcl.Create(context.NamedPipeEndPoint.PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
            context.PipeOptions, inBufferSize: 0, outBufferSize: 0, pipeSecurity);
    };
});

例外の種類に基づいて状態コードを選択する ExceptionHandlerMiddleware オプション

ExceptionHandlerMiddleware を構成するときに新しいオプションを使用すると、アプリ開発者は要求処理中に例外が発生したときに返す状態コードを選択できます。 新しいオプションは、ExceptionHandlerMiddleware からの ProblemDetails 応答で設定されている状態コードを変更します。

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    StatusCodeSelector = ex => ex is TimeoutException
        ? StatusCodes.Status503ServiceUnavailable
        : StatusCodes.Status500InternalServerError,
});

特定のエンドポイントと要求での HTTP メトリックのオプトアウト

.NET 9 では、特定のエンドポイントと要求に対して HTTP メトリックをオプトアウトする機能が導入されています。 メトリックの記録をオプトアウトすると、正常性チェックなどの自動化されたシステムによって頻繁に呼び出されるエンドポイントに役立ちます。 通常、これらの要求のメトリックの記録は不要です。

エンドポイントへの HTTP 要求は、メタデータを追加することでメトリックから除外できます。 次のいずれか:

  • [DisableHttpMetrics] 属性を Web API コントローラー、SignalR ハブ、または gRPC サービスに追加します。
  • アプリの起動時にエンドポイントをマッピングするときに、 DisableHttpMetrics を呼び出します。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz").DisableHttpMetrics();
app.Run();

次に対して MetricsDisabled プロパティが IHttpMetricsTagsFeature に追加されました。

  • 要求がエンドポイントにマップされない高度なシナリオ。
  • 特定の HTTP 要求のメトリック収集を動的に無効にする。
// Middleware that conditionally opts-out HTTP requests.
app.Use(async (context, next) =>
{
    var metricsFeature = context.Features.Get<IHttpMetricsTagsFeature>();
    if (metricsFeature != null &&
        context.Request.Headers.ContainsKey("x-disable-metrics"))
    {
        metricsFeature.MetricsDisabled = true;
    }

    await next(context);
});

キーの削除に対するデータ保護のサポート

.NET 9 より前では、データの損失を防ぐために、データ保護キーは削除できない仕様でした。 キーを削除すると、その保護されたデータは取得できなくなります。 それらのキーは小さいため、通常はそれらが蓄積しても影響は最小限にとどまります。 ただし、非常に実行時間の長いサービスに対応するために、キーを削除するオプションが導入されました。 一般に、古いキーのみを削除する必要があります。 ストレージの節約と引き換えに、データ損失のリスクを受け入れることができる場合にのみ、キーを削除します。 データ保護キーは削除しないことをお勧めします。

using Microsoft.AspNetCore.DataProtection.KeyManagement;

var services = new ServiceCollection();
services.AddDataProtection();

var serviceProvider = services.BuildServiceProvider();

var keyManager = serviceProvider.GetService<IKeyManager>();

if (keyManager is IDeletableKeyManager deletableKeyManager)
{
    var utcNow = DateTimeOffset.UtcNow;
    var yearAgo = utcNow.AddYears(-1);

    if (!deletableKeyManager.DeleteKeys(key => key.ExpirationDate < yearAgo))
    {
        Console.WriteLine("Failed to delete keys.");
    }
    else
    {
        Console.WriteLine("Old keys deleted successfully.");
    }
}
else
{
    Console.WriteLine("Key manager does not support deletion.");
}

ミドルウェアでキー付き DI がサポートされる

ミドルウェアでは、コンストラクターと Invoke/InvokeAsync メソッドの両方でキー付き DI がサポートされるようになりました。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Linux 上の ASP.NET Core の HTTPS 開発証明書を信頼する

Ubuntu および Fedora ベースの Linux ディストリビューションでは、dotnet dev-certs https --trust は ASP.NET Core HTTPS 開発証明書を次の信頼できる証明書として構成するようになりました。

  • Chromium ブラウザー (Google Chrome、Microsoft Edge、Chromium など)。
  • Mozilla Firefox および Mozilla 派生ブラウザー。
  • .NET API (例: HttpClient)

以前は、--trust は Windows と macOS でのみ機能しました。 証明書の信頼はユーザーごとに適用されます。

OpenSSL で信頼を確立するには、dev-certs ツールを使用します。

  • ~/.aspnet/dev-certs/trust に証明書を入力します。
  • ディレクトリ上で OpenSSL の c_rehash ツールの簡易バージョン 実行します。
  • ユーザーに SSL_CERT_DIR 環境変数を更新するように要求します。

dotnet で信頼を確立するために、ツールは証明書を My/Root 証明書ストアに配置します。

NSS データベースの信頼を確立するために、ツールは home ディレクトリで Firefox プロファイル、~/.pki/nssdb~/snap/chromium/current/.pki/nssdb を検索します (存在する場合)。 見つかったディレクトリごとに、ツールによって nssdb にエントリが追加されます。

最新の Bootstrap、jQuery、および jQuery 検証バージョンに更新されたテンプレート

ASP.NET Core プロジェクト テンプレートとライブラリは、Bootstrap、jQuery、jQuery 検証の最新バージョンを使用するように更新されました。具体的には次のとおりです。

  • Bootstrap 5.3.3
  • jQuery 3.7.1
  • jQuery Validation 1.21.0