次の方法で共有


.NET 10 の ASP.NET Core の新機能

この記事では、.NET 10 の ASP.NET Core における最も重要な変更点と、関連するドキュメントへのリンクについて説明します。

この記事は、新しいプレビュー リリースが利用可能になると更新されます。 重大な変更については、「 .NET での破壊的変更」を参照してください。

Blazor

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

新規および更新された Blazor Web App セキュリティ サンプル

次の記事でリンクされている Blazor Web App セキュリティ サンプルを追加および更新しました。

OIDC と Entra のサンプル ソリューションには、外部 Web API を安全に構成して呼び出す方法を示す別の Web API プロジェクト (MinimalApiJwt) が含まれるようになりました。 Web API の呼び出しは、トークン ハンドラーと、OIDC ID プロバイダーの名前付き HTTP クライアント、または Microsoft Entra ID 用の Microsoft Identity Web パッケージ/API を使用して示されています。

サンプル ソリューションは、 Program ファイル内の C# コードで構成されます。 アプリ設定ファイル (たとえば、appsettings.json) からソリューションを構成するには、OIDC または Entra 記事の JSON 構成プロバイダー (アプリ設定) セクションを使用した新しい Supply 構成を参照してください。

また、Entra サンプルには、Web ファーム ホスティング シナリオに暗号化された分散トークン キャッシュの使用に関する新しいガイダンスも含まれています。

QuickGrid RowClass パラメーター

新しい RowClass パラメーターを使用して、行項目に基づいてグリッドの行にスタイルシート クラスを適用します。 次の例では、各行に対して GetRowCssClass メソッドを呼び出して、行項目に基づいてスタイルシート クラスを条件付きで適用します。

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

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

詳細については、「 ASP.NET Core Blazor 'QuickGrid' コンポーネント」を参照してください。

静的 Web アセットとしての Blazor スクリプト

.NET の以前のリリースでは、Blazor スクリプトは、ASP.NET Core 共有フレームワークの埋め込みリソースから提供されます。 .NET 10 以降では、Blazor スクリプトは、自動圧縮とフィンガープリントを備えた静的 Web 資産として機能します。

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

ルート テンプレートの強調表示

[Route]属性では、ルート テンプレートの構造を視覚化するのに役立つルート構文の強調表示がサポートされるようになりました。

カウンター値のルート属性のルート テンプレート パターンが構文の強調表示を示す

以前は、NavigationManager.NavigateTo によって同一ページのナビゲーションでページの最上部までスクロールしていました。 この動作は.NET 10 で変更されているため、同じページに移動するときにブラウザーがページの上部までスクロールしなくなりました。 つまり、クエリ文字列やフラグメントの変更など、現在のページのアドレスを更新するときにビューポートがリセットされなくなります。

Blazor Web App プロジェクト テンプレートに追加された再接続 UI コンポーネント

Blazor Web App プロジェクト テンプレートには、クライアントがサーバーへの WebSocket 接続を失ったときに再接続 UI を開発者が制御できるように、併置されたスタイルシートや JavaScript ファイルを含む ReconnectModal コンポーネントが含まれるようになりました。 コンポーネントはプログラムによってスタイルを挿入しないため、style-src ポリシーのより厳密なコンテンツ セキュリティ ポリシー (CSP) 設定に準拠します。 以前のリリースでは、CSP 違反を引き起こす可能性のある方法で、既定の再接続 UI がフレームワークによって作成されていました。 既定の再接続 UI は、プロジェクト テンプレートの ReconnectModal コンポーネントや同様のカスタム コンポーネントを使用するなどして、アプリが再接続 UI を定義していない場合でもフォールバックとして使用されることに注意してください。

新しい再接続 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.EnableMatchAllForQueryStringAndFragmentに設定されているAppContextを使用します。

ShouldMatchNavLink メソッドをオーバーライドして、一致する動作をカスタマイズすることもできます。

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

詳細については、「 ASP.NET Core Blazor ルーティングとナビゲーション」を参照してください。

QuickGrid列オプションを閉じる

新しい QuickGrid メソッドを使用して、HideColumnOptionsAsync 列オプション UI を閉じることができるようになりました。

次の例では、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));
}

応答ストリーミングはオプトイン形式であり、オプトアウトする方法について

以前の Blazor リリースでは、 HttpClient 要求の応答ストリーミングがオプトインされました。 応答ストリーミングが既定で有効になりました。

これは、HttpContent.ReadAsStreamAsync (HttpResponseMessage.Content) のresponse.Content.ReadAsStreamAsync()を呼び出すとBrowserHttpReadStreamが返され、MemoryStreamではなくなったため、重大な変更です。 BrowserHttpReadStream では、 Stream.Read(Span<Byte>)などの同期操作はサポートされていません。 コードで同期操作を使用している場合は、応答ストリーミングをオプトアウトするか、 Stream を自分で MemoryStream にコピーできます。

応答ストリーミングをグローバルにオプトアウトするには、次のいずれかの方法を使用します。

  • <WasmEnableStreamingResponse>の値を持つプロジェクト ファイルにfalseプロパティを追加します。

    <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
    
  • DOTNET_WASM_ENABLE_STREAMING_RESPONSE環境変数をfalseまたは0に設定します。

個々の要求の応答ストリーミングをオプトアウトするには、SetBrowserResponseStreamingEnabledfalseHttpRequestMessageに設定します (次の例requestMessage)。

requestMessage.SetBrowserResponseStreamingEnabled(false);

詳細については、Fetch API 要求オプションを使用したHttpClientHttpRequestMessageを参照してください (Web API の呼び出しに関する記事)。

クライアント側のフィンガープリント

昨年、.NET 9 のリリースでは、 内の静的資産の Blazor Web Appが導入されました。これには、静的資産のルーティングエンドポイント規則 (MapStaticAssets)ImportMap コンポーネント、およびフィンガープリントされた JavaScript モジュールを解決するための プロパティ (ComponentBase.Assets) が含まれます。 .NET 10 の場合、スタンドアロン Blazor WebAssembly アプリの JavaScript モジュールのクライアント側フィンガープリントをオプトインできます。

スタンドアロンの Blazor WebAssembly アプリでは、ビルド/公開時に、フレームワークが index.html 内のプレースホルダーをビルド中に計算された値で上書きし、静的アセットのフィンガープリントに置き換えます。 フィンガープリントは、 blazor.webassembly.js スクリプト ファイル名に配置されます。

フィンガープリント機能を採用するには、 wwwwoot/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 相互運用機能のインポートを解決する場合、インポート マップは、フィンガープリントされたファイルを解決するブラウザーによって使用されます。

スタンドアロン Blazor WebAssembly アプリで環境を設定する

Properties/launchSettings.json ファイルは、スタンドアロン Blazor WebAssembly アプリで環境を制御するために使用されなくなりました。

.NET 10 以降では、アプリのプロジェクト ファイル (<WasmApplicationEnvironmentName>) で .csproj プロパティを使用して環境を設定します。

次の例では、アプリの環境を Stagingに設定します。

<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

既定の環境は次のとおりです。

  • Development ビルド用。
  • Production (発行用)。

インライン化されたブート構成ファイル

Blazorのブート構成は、.NET 10 のリリース以前は blazor.boot.json という名前のファイルに存在していたが、 dotnet.js スクリプトにインライン化されています。 これは、開発者が次のような blazor.boot.json ファイルを直接操作する開発者にのみ影響します。

現時点では、上記のアプローチに関する文書化された代替戦略はありません。 上記のいずれかの方法が必要な場合は、いずれかの記事の下部にある「ドキュメントの問題を 開く 」リンクを使用して、シナリオを説明する新しいドキュメントの問題を開きます。

コンポーネントとサービスからの状態を保持するための宣言型モデル

[SupplyParameterFromPersistentComponentState]属性を使用して、永続化する状態をコンポーネントやサービスから宣言的に指定できるようになりました。 この属性を持つプロパティは、プリレンダリング中に 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 {
    [SupplyParameterFromPersistentComponentState]
    public List<Movie>? MoviesList { get; set; }

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

状態は、同じ型の複数のコンポーネントに対してシリアル化できます。また、カスタム サービスの種類とレンダリング モードでRegisterPersistentService コンポーネント ビルダー (Razor) でAddRazorComponentsを呼び出すことで、アプリの周囲で使用するサービスで宣言型の状態を確立できます。 詳細については、「 Prerender ASP.NET Core Razor コンポーネント」を参照してください。

新しい JavaScript 相互運用機能

Blazor は、次の JS 相互運用機能のサポートを追加します。

  • コンストラクター関数を使用して JS オブジェクトのインスタンスを作成し、インスタンスを参照するための IJSObjectReference/IJSInProcessObjectReference .NET ハンドルを取得します。
  • データ プロパティとアクセサー プロパティの両方で、 JS オブジェクト プロパティの値を読み取りまたは変更します。

次の非同期メソッドは、既存のIJSRuntime メソッドと同じスコープ動作を持つIJSObjectReferenceおよびIJSRuntime.InvokeAsyncで使用できます。

  • InvokeNewAsync(string identifier, object?[]? args): 指定した JS コンストラクター関数を非同期的に呼び出します。 この関数は、 new 演算子を使用して呼び出されます。 次の例では、 jsInterop.TestClass はコンストラクター関数を持つクラスであり、 classRefIJSObjectReferenceです。

    var classRef = await JSRuntime.InvokeNewAsync("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 の num が作成されます。

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

各メソッドには、CancellationToken 引数またはTimeSpan タイムアウト引数を受け取るオーバーロードが用意されています。

既存の IJSInProcessRuntime メソッドと同じスコープ動作を持つIJSInProcessObjectReferenceおよびIJSInProcessObjectReference.Invokeでは、次の同期メソッドを使用できます。

  • InvokeNew(string identifier, object?[]? args): 指定した JS コンストラクター関数を同期的に呼び出します。 この関数は、 new 演算子を使用して呼び出されます。 次の例では、 jsInterop.TestClass はコンストラクター関数を持つクラスであり、 classRefIJSInProcessObjectReferenceです。

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var classRef = inProcRuntime.InvokeNew("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 の num が作成されます。

    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 の静的ファイル」をご覧ください。

以前は、静的サーバー側レンダリング (SSR) 中に NavigationManager.NavigateTo を呼び出すと、NavigationException がスローされ、リダイレクト応答に変換される前に実行が中断されていました。 これにより、デバッグ中に混乱が発生し、対話型レンダリングと一貫性がなく、 NavigateTo 後のコードは引き続き正常に実行されます。

静的 SSR の実行中に NavigationManager.NavigateTo を呼び出しても、もはや NavigationException が発生しません。 代わりに、例外をスローせずにナビゲーションを実行することで、対話型レンダリングと一貫して動作します。

NavigationExceptionがスローされることに依存しているコードは更新すべきです。 たとえば、既定のBlazorIdentity UI では、IdentityRedirectManagerは、対話型レンダリング中に呼び出されないように、InvalidOperationExceptionを呼び出した後に以前にRedirectToをスローしました。 この例外と [DoesNotReturn] 属性を 削除する必要があります。

NavigationExceptionをスローする元の動作に戻すには、次のAppContextスイッチを設定してください。

AppContext.SetSwitch(
    "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.EnableThrowNavigationException", 
    isEnabled: true);

静的 SSR およびグローバル対話型レンダリングに NavigationManager を使用した応答が見つかりません

NavigationManagerには、静的サーバー側レンダリング (静的 SSR) またはグローバル対話型レンダリング中に要求されたリソースが見つからないシナリオを処理するNotFound メソッドが含まれるようになりました。

  • 静的なサーバー側レンダリング (静的 SSR):NotFound呼び出すと、HTTP 状態コードが 404 に設定されます。
  • ストリーミングレンダリング: レスポンスが既に開始されている場合は、例外を投げます。
  • 対話型レンダリング: Blazor ルーター (Router コンポーネント) に通知して、見つからないコンテンツをレンダリングします。

2025 年 6 月のプレビュー 5 では、ページ単位/コンポーネントごとのレンダリングのサポートが予定されています。

NavigationManager.OnNotFoundが呼び出されたときに通知にNotFound イベントを使用できます。

詳細と例については、 ASP.NET Core Blazor ルーティングとナビゲーションに関するページを参照してください。

Blazor ルーターに NotFoundPage パラメーターがある

Blazor では、存在しないページに移動するときに "見つかりません" ページを表示する方法が改善されました。 NavigationManager.NotFound パラメーターを使用してRouter コンポーネントにページの種類を渡すことで、NotFoundPage時にレンダリングするページを指定できます。 この方法は、ルーティングをサポートし、コードの再実行ミドルウェア全体で機能し、NotFound以外のシナリオと互換性があるため、前のBlazor フラグメントよりも推奨されます。 NotFound フラグメントとNotFoundPageの両方が定義されている場合、NotFoundPageによって指定されたページが優先されます。

<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 Core Blazor ルーティングとナビゲーション」を参照してください。

メトリックとトレース

このリリースでは、 Blazor アプリの包括的なメトリックとトレース機能が導入され、コンポーネントのライフサイクル、ナビゲーション、イベント処理、回線管理の詳細な監視が提供されます。

詳しくは、「ASP.NET Core Blazor のパフォーマンスに関するベスト プラクティス」をご覧ください。

Blazor Hybrid

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

新しい .NET MAUIBlazor Hybrid 記事とサンプルには Blazor Web App と ASP.NET Core Identity が含まれています

ASP.NET Core .NET MAUIを使用して、Blazor HybridIdentity および Web アプリ用の新しい記事とサンプル アプリが追加されました。

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

SignalR

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

最小限の API

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

null 許容値型の場合、フォーム投稿で空の文字列を null として扱います

最小限の API で複雑なオブジェクトで [FromForm] 属性を使用する場合、フォームポストの空の文字列値が解析エラーの原因ではなく null に変換されるようになりました。 この動作は、最小限の API の複雑なオブジェクトに関連付けられていないフォーム投稿の処理ロジックと一致します。

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 ランタイムは、次に定義されているすべての検証を実行できます。

  • クエリ
  • ヘッダ
  • 要求本文

検証は、 DataAnnotations 名前空間の属性を使用して定義されます。 開発者は、次の方法で検証システムの動作をカスタマイズします。

検証が失敗した場合、ランタイムは検証エラーの詳細を含む 400 Bad Request 応答を返します。

最小限の 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();

ASP.NET Core for .NET 10 で導入された最小限の 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);
});

Server-Sent イベント (SSE) のサポート

ASP.NET Core では、TypedResults.ServerSentEvents API を使用した ServerSentEvents の結果の返しがサポートされるようになりました。 この機能は、最小限の API とコントローラー ベースのアプリの両方でサポートされています。

Server-Sent イベントは、サーバーが 1 つの HTTP 接続経由でイベント メッセージのストリームをクライアントに送信できるようにするサーバー プッシュ テクノロジです。 .NET では、イベント メッセージは SseItem<T> オブジェクトとして表され、イベントの種類、ID、および T型のデータ ペイロードが含まれる場合があります。

TypedResults クラスには、結果を返すために使用できる 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、JSON オブジェクトとしてクライアントに返すServerSentEvents

OpenAPI

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

OpenAPI 3.1 のサポート

ASP.NET Core では、.NET 10 で OpenAPI バージョン 3.1 ドキュメントを生成するためのサポートが追加されました。 マイナーなバージョンアップにもかかわらず、OpenAPI 3.1は、特にJSONスキーマのドラフト2020-12を完全にサポートするOpenAPI仕様の重要な更新です。

生成された OpenAPI ドキュメントに表示される変更の一部を次に示します。

  • null 許容型のスキーマに、nullable: true プロパティが含まれなくなりました。
  • nullable: true プロパティの代わりに、type キーワードがあり、その値は型の 1 つとして null を含む配列です。
  • C# int または long として定義されたプロパティまたはパラメーターが、 type: integer フィールドなしで生成された OpenAPI ドキュメントに表示され、値を数字に制限する pattern フィールドが追加されました。 これは、NumberHandlingJsonSerializerOptions プロパティが AllowReadingFromString (ASP.NET Core Web アプリの既定値) に設定されている場合に発生します。 C# の intlong を OpenAPI ドキュメントで type: integerとして表せるようにするには、 NumberHandling プロパティを Strict に設定します。

この機能では、生成されたドキュメントの既定の OpenAPI バージョンが3.1。 バージョンは、AddOpenApi デリゲート パラメーターで configureOptionsOpenApiVersion プロパティを明示的に設定することで変更できます。

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

ビルド時に OpenAPI ドキュメントを生成する場合は、--openapi-version MSBuild 項目でOpenApiGenerateDocumentsOptionsを設定することで、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設定するかどうかを評価します。

最も重要な変更の 1 つは、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 では、生成された OpenAPI ドキュメントを YAML 形式で提供できるようになりました。 YAML は JSON よりも簡潔な記述が可能であり、中括弧や引用符を推測できる場合は省略できます。 YAML では複数行の文字列もサポートされています。これは、長い説明に役立ちます。

生成された OpenAPI ドキュメントを YAML 形式で処理するようにアプリを構成するには、次の例に示すように、MapOpenApi 呼び出しのエンドポイントを ".yaml" または ".yml" サフィックスで指定します。

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

サポート対象:

  • YAML は現在、OpenAPI エンドポイントから提供される OpenAPI でのみ使用できます。
  • ビルド時に YAML 形式で OpenAPI ドキュメントを生成することは、今後のプレビューで追加されます。

生成された OpenAPI ドキュメントを YAML 形式で提供するためのサポートを追加したこの PR 参照してください。

API コントローラーの ProducesResponseType に関する応答の説明

ProducesAttributeProducesResponseTypeAttributeおよび ProducesDefaultResponseType 属性は、応答の説明を設定する省略可能な文字列パラメーター 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 ではProducesResponseTypeはサポートされていません。

Sander ten Brinke によるコミュニティへの貢献🙏

OpenAPI ドキュメントに XML ドキュメントのコメントを埋め込みます

ASP.NET Core OpenAPI ドキュメントの生成には、OpenAPI ドキュメント内のメソッド、クラス、およびメンバー定義に関する XML ドキュメント コメントからのメタデータが含まれるようになりました。 この機能を使用するには、プロジェクト ファイルの XML ドキュメント コメントを有効にする必要があります。 これを行うには、次のプロパティをプロジェクト ファイルに追加します。

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

ビルド時に、OpenAPI パッケージはソース ジェネレーターを利用して、現在のアプリケーション アセンブリ内の XML コメントとプロジェクト参照を検出し、ソース コードを出力して OpenAPI ドキュメント トランスフォーマーを介してドキュメントに挿入します。

C# ビルド プロセスではラムダ式に配置された XML ドキュメント コメントはキャプチャされないため、XML ドキュメント コメントを使用して最小限の API エンドポイントにメタデータを追加するには、エンドポイント ハンドラーをメソッドとして定義し、メソッドに XML ドキュメント コメントを配置してから、MapXXX メソッドからそのメソッドを参照する必要があります。 たとえば、XML ドキュメント コメントを使用して、最初にラムダ式として定義された最小限の 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 クラスに追加されますが、プロジェクト内の任意のクラスに追加できます。

前の例では、<summary><remarks>、および XML ドキュメント コメント <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>

ASP.NET Core Web API (ネイティブ AOT) テンプレートに追加された Microsoft.AspNetCore.OpenApi

ASP.NET Core Web API (ネイティブ AOT) プロジェクト テンプレート (短い名前webapiaot) には、既定で Microsoft.AspNetCore.OpenApi パッケージを使用した OpenAPI ドキュメント生成のサポートが含まれるようになりました。 このサポートは、新しいプロジェクトの作成時に --no-openapi フラグを使用して無効になります。

これは、 @sander1095によるコミュニティへの貢献でした。 この貢献に感謝します。

DI コンテナーでの IOpenApiDocumentProvider のサポート。

.NET 10 の ASP.NET Core では、依存関係挿入 (DI) コンテナーで IOpenApiDocumentProvider がサポートされています。 開発者は、 IOpenApiDocumentProvider をアプリに挿入し、それを使用して OpenAPI ドキュメントにアクセスできます。 この方法は、バックグラウンド サービスやカスタム ミドルウェアなど、HTTP 要求のコンテキスト外で OpenAPI ドキュメントにアクセスする場合に便利です。

以前は、HTTP サーバーを起動せずにアプリケーションのスタートアップ ロジックを実行するには、no-op HostFactoryResolver 実装でIServerを使用できます。 この新機能は、分散アプリケーションのホスティングと発行に関するアスパイアのフレームワークの一部である、アスパイアの IDistributedApplicationPublisherに着想を得た合理化された API を提供することで、このプロセスを簡略化します。

詳細については、 dotnet/aspnetcore #61463 を参照してください。

XML コメント ジェネレーターの機能強化

XML コメント生成は、.NET 10 の複合型を以前のバージョンの .NET よりも適切に処理します。

  • これは、より広い範囲の型に対して正確で完全な XML コメントを生成します。
  • より複雑なシナリオを処理します。
  • 以前のバージョンでビルド エラーを引き起こす複合型の処理を適切にバイパスします。

これらの機能強化により、特定のシナリオのエラー モードがビルド エラーから不足しているメタデータに変更されます。

さらに、他のアセンブリの XML コメントにアクセスするように XML ドキュメント コメント処理を構成できるようになりました。 これは、現在のアセンブリの外部で定義されている型 (ProblemDetails名前空間のMicrosoft.AspNetCore.Http型など) のドキュメントを生成する場合に便利です。

この構成は、プロジェクト ビルド ファイル内のディレクティブを使用して行われます。 次の例は、 Microsoft.AspNetCore.Http アセンブリ内の型 ( ProblemDetails クラスを含む) の XML コメントにアクセスするように XML コメント ジェネレーターを構成する方法を示しています。

<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 コメントを今後のプレビューに含める予定です。

トランスフォーマーでの OpenApiSchemas の生成のサポート

開発者は、Core OpenAPI ドキュメントの生成と同じロジックを使用して C# 型のスキーマ ASP.NET 生成し、OpenAPI ドキュメントに追加できるようになりました。 その後、OpenAPI ドキュメント内の他の場所からスキーマを参照できます。

ドキュメント、操作、およびスキーマ トランスフォーマーに渡されるコンテキストには、型のスキーマを生成するために使用できる新しい GetOrCreateSchemaAsync メソッドが含まれています。 このメソッドには、生成されたスキーマの追加メタデータを指定する省略可能な ApiParameterDescription パラメーターもあります。

OpenAPI ドキュメントへのスキーマの追加をサポートするために、operation および Schema トランスフォーマー コンテキストに Document プロパティが追加されました。 これにより、任意のトランスフォーマーがドキュメントの AddComponent メソッドを使用して OpenAPI ドキュメントにスキーマを追加できます。

ドキュメント、操作、またはスキーマ トランスフォーマーでこの機能を使用するには、コンテキストで提供される 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.NET Preview.18 に更新されました

ASP.NET Core OpenAPI ドキュメントの生成で使用される OpenAPI.NET ライブラリが v2.0.0-preview18 にアップグレードされました。 v2.0.0-preview18 バージョンでは、更新されたライブラリ バージョンとの互換性が向上しています。

以前の v2.0.0-preview17 バージョンには、いくつかのバグ修正と機能強化が含まれており、いくつかの破壊的変更も導入されました。 重大な変更は、ドキュメント、操作、またはスキーマ トランスフォーマーを使用するユーザーにのみ影響する必要があります。 開発者に影響を与える可能性があるこのバージョンの破壊的変更には、次のようなものがあります。

認証と承認

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

認証と承認のメトリック

ASP.NET Core の特定の認証および承認イベントのメトリックが追加されました。 この変更により、次のイベントのメトリックを取得できるようになりました。

  • 認証:
    • 認証された要求の期間
    • 試行回数
    • 数えることを禁止
    • サインイン数
    • サインアウト数
  • 認可:
    • 承認が必要な要求の数

次の図は、アスパイア ダッシュボードの認証済み要求期間メトリックの例を示しています。

[アスパイア] ダッシュボードの認証済み要求期間

詳細については、「 コア承認と認証のメトリック ASP.NET」を参照してください。

その他

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

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

詳細については、「HTTP.sys web server implementation in ASP.NET Core」 (ASP.NET Core への HTTP.sys Web サーバーの実装) を参照してください。

最上位レベルのステートメントを使用したアプリのテストのサポートの強化

.NET 10 では、 最上位レベルのステートメントを使用するアプリのテストのサポートが強化されました。 以前の開発者は、テスト プロジェクトが public partial class Programを参照できるように、Program.cs ファイルに Program class を手動で追加する必要がありました。 C# 9 の最上位レベルのステートメント機能によって、public partial class Program として宣言された Program class が生成されたため、 が必要でした。

.NET 10 では、プログラマが明示的に宣言しなかった場合、 ソース ジェネレーター を使用して public partial class Program 宣言を生成します。 さらに、public partial class Program が明示的に宣言されたタイミングを検出し、削除するよう開発者に勧めるアナライザーが追加されました。

画像

この機能には、以下の PR が貢献しています。

新しい JSON パッチがSystem.Text.Jsonを使用して実装されました。

JSON パッチ:

  • JSON ドキュメントに適用する変更を記述するための標準形式です。
  • RFC 6902 で定義されており、JSON リソースの部分的な更新を実行するために RESTful API で広く使用されています。
  • JSON ドキュメントを変更するために適用できる一連の操作 (追加、削除、置換、移動、コピー、テストなど) を表します。

Web アプリでは、JSON Patch は、一般的に PATCH 操作でリソースの部分的な更新を実行するために使用されます。 クライアントは、更新プログラムのリソース全体を送信するのではなく、変更のみを含む JSON パッチ ドキュメントを送信できます。 修正プログラムを適用すると、ペイロードのサイズが減少し、効率が向上します。

このリリースでは、Microsoft.AspNetCore.JsonPatchシリアル化に基づくSystem.Text.Jsonの新しい実装が導入されています。 この機能の効果:

  • .NET 用に最適化された System.Text.Json ライブラリを利用して、最新の .NET プラクティスに合わせて調整します。
  • 従来の Newtonsoft.Jsonベースの実装と比較して、パフォーマンスが向上し、メモリ使用量が削減されます。

次のベンチマークでは、新しい System.Text.Json 実装のパフォーマンスと、従来の Newtonsoft.Json 実装を比較します。

シナリオ 実装 Mean (平均値) 割り当てられたメモリ
アプリケーション ベンチマーク Newtonsoft.JsonPatch 271.924 μs 25 KB
System.Text.JsonPatch 1.584 μs 3 KB
逆シリアル化ベンチマーク Newtonsoft.JsonPatch 19.261 μs 43 KB
System.Text.JsonPatch 7.917 マイクロ秒 7 KB

これらのベンチマークでは、新しい実装でパフォーマンスの大幅な向上とメモリ使用量の削減が強調されています。

注記:

  • 新しい実装は、レガシ実装のドロップインの代わりではありません。 特に、新しい実装では動的な型 ( ExpandoObjectなど) はサポートされていません。
  • JSON Patch 標準には固有の セキュリティ リスクがあります。 これらのリスクは JSON Patch 標準に固有であるため、新しい実装 では固有のセキュリティ リスクを軽減しようとはしません。 JSON パッチ ドキュメントがターゲット オブジェクトに安全に適用されるようにするのは開発者の責任です。 詳細については、「 セキュリティ リスクの軽減」セクションを 参照してください。

使用方法

System.Text.Jsonで JSON Patch のサポートを有効にするには、Microsoft.AspNetCore.JsonPatch.SystemTextJson NuGet パッケージをインストールします。

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

このパッケージには、JsonPatchDocument<T>型のオブジェクトの JSON Patch ドキュメントを表すT クラスと、System.Text.Jsonを使用して JSON Patch ドキュメントをシリアル化および逆シリアル化するためのカスタム ロジックが用意されています。 JsonPatchDocument<T> クラスのキー メソッドはApplyToであり、T型のターゲット オブジェクトにパッチ操作を適用します。

次の例では、 ApplyTo メソッドを使用して JSON Patch ドキュメントをオブジェクトに適用する方法を示します。

例: JsonPatchDocument の適用

その具体的な例を次に示します:

  1. addreplace、およびremove操作。
  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 Patch では、 test 操作もサポートされています。 test操作は、指定した値がターゲット プロパティと等しいかどうかを確認し、そうでない場合はエラーを返します。

次の例では、これらのエラーを適切に処理する方法を示します。

Von Bedeutung

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 に関連付けられている特定されたセキュリティ リスクについて説明し、パッケージの安全な使用を確保するための推奨される軽減策を提供します。

Von Bedeutung

これは、脅威の完全な一覧ではありません。 アプリ開発者は、独自の脅威モデル レビューを実施して、アプリ固有の包括的な一覧を決定し、必要に応じて適切な軽減策を考え出す必要があります。 たとえば、コレクションをパッチ操作に公開するアプリでは、それらの操作がコレクションの先頭に要素を挿入または削除する場合に、アルゴリズムの複雑さの攻撃の可能性を考慮する必要があります。

独自のアプリに対して包括的な脅威モデルを実行し、以下の推奨される軽減策に従って特定された脅威に対処することで、これらのパッケージのコンシューマーは、セキュリティ リスクを最小限に抑えながら、JSON パッチ機能をアプリに統合できます。

これらのパッケージのコンシューマーは、次のようなセキュリティ リスクを最小限に抑えながら、JSON パッチ機能をアプリに統合できます。

  • 独自のアプリに対して包括的な脅威モデルを実行します。
  • 特定された脅威に対処します。
  • 次のセクションで推奨される軽減策に従います。
メモリ増幅によるサービス拒否 (DoS)
  • シナリオ: 悪意のあるクライアントが、大きなオブジェクト グラフを複数回複製する copy 操作を送信すると、メモリが過剰に消費されます。
  • 影響: 潜在的なOut-Of-Memory (OOM)状態が発生し、サービスが中断する可能性があります。
  • 軽減策:
    • 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();
    }
}
ビジネスロジックの覆し
  • シナリオ: パッチ操作では、ビジネス上の制約に違反する暗黙的なインバリアント (内部フラグ、ID、計算フィールドなど) を持つフィールドを操作できます。
  • 影響:データ整合性の問題と意図しないアプリの動作。
  • 軽減策:
    • 変更しても安全な明示的に定義されたプロパティを持つ POCO オブジェクトを使用します。
    • ターゲット オブジェクトで機密性の高いプロパティまたはセキュリティ クリティカルなプロパティを公開しないようにします。
    • POCO オブジェクトが使用されていない場合は、操作の適用後に修正プログラムが適用されたオブジェクトを検証して、ビジネス ルールとインバリアントが違反していないことを確認します。
認証と承認
  • シナリオ: 認証されていないクライアントまたは未承認のクライアントが、悪意のある JSON パッチ要求を送信します。
  • 影響:機密データを変更したり、アプリの動作を中断したりするための未承認のアクセス。
  • 軽減策:
    • 適切な認証と承認メカニズムを使用して、JSON パッチ要求を受け入れるエンドポイントを保護します。
    • 適切なアクセス許可を持つ信頼されたクライアントまたはユーザーへのアクセスを制限します。

RedirectHttpResult.IsLocalUrl を使用して URL がローカルかどうかを検出する

新しい RedirectHttpResult.IsLocalUrl(url) ヘルパー メソッドを使用して、URL がローカルかどうかを検出します。 次の条件に該当する場合、URL はローカルと見なされます。

仮想パスを使用する URL もローカルです。

IsLocalUrl は、 開いているリダイレクト攻撃を防ぐために URL にリダイレクトする前に URL を検証する場合に便利です。

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

@martincostelloさん、この投稿をしていただき、ありがとうございます。

ASP.NET Core での HTTP.sys Web サーバーの実装