プログラミングとソフトウェア開発

ASP.NET Core で unions と閉じた階層を使用する方法

.NET Blog では、Minimal APIs、MVC、SignalR、Blazor、OpenAPI を通じて、System.Text.Json で union 型と閉じた階層を活用する方法を説明しています。また、曖昧さが生じるケースと、これらの型をサポートしないバインディング ソースについても示しています。

2026-09-10
2 分で読めます
11 閲覧数
فريق تحرير certi.news
ASP.NET Core で unions と閉じた階層を使用する方法

.NET 11 の ASP.NET Core では、union 型と閉じた階層を System.Text.Json で実用的に使用できるようになり、複数の型のいずれか、または特定の型ファミリに属する値を表現できます。このサポートは Minimal APIs、MVC コントローラー、SignalR、Blazor、OpenAPI ドキュメントに及びますが、読み取りと書き込みの方法、およびパラメーター バインディングの制限には重要な違いがあります。

union が適しているのはどのような場合か?

union は、JSON のラッパーや discriminator フィールドを必要とせず、独立した選択肢を受け入れるコントラクトに適しています。たとえば、整数または文字列になり得る値や、ブール値またはより詳細なオプションを含むオブジェクトを受け入れる設定などです。シリアル化時には、アクティブなケースが自然な形式で直接書き込まれます。union 型専用のラッパーや discriminator は追加されません。

JSON の構造から明確に判別できるケースは、自動的にシリアル化されます。しかし、Cat と Dog の 2 つのオブジェクトを含む union のように、ケースが同じ構造を共有している場合、System.Text.Json は JSON トークンだけから型を判定できません。この場合は、正しいケースを選択するカスタム JsonTypeClassifier を提供できます。同じことは、数値または文字列を受け入れる一部のリクエスト コントラクトにも当てはまります。Web 用の JSON 設定では文字列から数値を読み取れるため、文字列がどちらのケースとしても解釈可能になることがあります。

閉じた階層は union とは異なる

closed 修飾子を使用したからといって、多態的なシリアル化が自動的に有効になるわけではありません。PaymentAuthorized のような具体的な派生型を扱う場合、System.Text.Json はそれを他の型と同様に処理し、discriminator なしで paymentId と amount を含む JSON を生成します。

一方、API で基底型 PaymentEvent を使用する場合は、作成すべき派生型を指定する必要があります。基底型で JsonPolymorphic(InferClosedTypePolymorphism = true) を使用するか、JsonSerializerOptions.InferClosedTypePolymorphism を介して有効にできます。これにより、System.Text.Json は閉じた階層のシリアル化で派生型を推論し、その名前を識別子として使用します。たとえば、$type の値として PaymentAuthorized を使用します。

2 つのモデルのどちらを選択するかは、コントラクトの性質によって決まります。union は必ずしも 1 つのファミリに属さない選択肢を表すのに対し、閉じた階層は、明確な discriminator を必要とする、関連性のあるイベントまたはエンティティの集合に適しています。

ASP.NET Core の各レイヤーでのサポート

Minimal APIs では、通常の実行パスでも Request Delegate Generator 経由でも、union をリクエスト ボディのパラメーターや戻り値の型として使用でき、両方の経路で同じ動作になります。また、union を別のモデル内に配置したり、IAsyncEnumerable<T> の要素にしたり、AsParameters を使用するコンテナー内に含めたりすることもできます。ConfigureHttpJsonOptions の設定は、引き続きシリアル化に影響します。

MVC コントローラーでは、アクションのパラメーターおよび戻り値の型として union をサポートしており、Task<TUnion> と ValueTask<TUnion> も含まれます。MVC は JsonSerializerDefaults.Web の設定を使用するため、HTTP 入力における数値と文字列の曖昧さの問題も適用されます。

SignalR では、パラメーター、戻り値、ストリーム要素について、JsonHubProtocol を通じてサポートが機能します。このプロトコルを使用する場合、int または string 型の union には classifier は必要ありません。JsonHubProtocol は、このケースでは JSON の文字列トークンを曖昧なものとして扱わないためです。ただし、UnionPet(Cat, Dog) のように、ケースがいずれも StartObject トークンを共有する union では、引き続き classifier が必要です。SignalR は、MessagePack または Newtonsoft.Json の hub protocol を使用する場合、この機能をサポートしません。

Blazor と OpenAPI

プロセス内の Blazor コンポーネント パラメーターは直接代入によって処理されるため、シリアル化は必要ありません。しかし、JavaScript interop、保存されたコンポーネント状態、prerendering 中のパラメーターは System.Text.Json を経由するため、union と同じ規則に従います。たとえば、アクティブなケースに応じて、ラッパーや discriminator を追加せず、union を Boolean またはオプション オブジェクトとして JavaScript に渡せます。

OpenAPI では、union は各ケースに 1 つの型を含む anyOf スキーマとして表現されます。ケースは、独立した型として現れる場合も、同じ Cat と Dog のコンポーネント名を再利用します。これは discriminator を持つ多態型とは異なり、多態型のスキーマは識別性のある名前で公開されます。また、ApiExplorer は、同じステータス コードとコンテンツ タイプに対して複数のレスポンス型を anyOf 内に表現できます。

考慮すべき制限

union のサポートは System.Text.Json に依存するため、JSON の解析を経由しないバインディング ソースでは機能しません。これには、クエリ文字列の値、ルート値、ヘッダー、フォーム フィールドが含まれます。?id=42 のような値だけでは、意図したものが int、string、Guid のいずれなのかを特定できません。Blazor では、[SupplyParameterFromQuery] と [SupplyParameterFromForm] を使用したフォーム バインディングにもこの制限が適用されます。この記事では、これらのソースからの union バインディングは、現在も検討およびフィードバック収集中であると述べています。

編集上の読み取り:ここでの本質的な価値は、JSON に新しい形式を追加することではなく、.NET 11 エコシステムの広範な部分で複数ケースのコントラクトの扱いを統一することです。ただし、設計の成否はケースを判別できるかどうかとデータ ソースに左右されます。そのため、早い段階で classifier または discriminator を定義し、シリアル化のサポートがすべてのパラメーター バインディング方式のサポートを意味するとは想定しないことが重要です。

ニュースの出典
ف
著者

فريق تحرير certi.news

同じカテゴリー

おすすめ記事

すべてのニュースを見る