フッターコンテンツにスキップ
Iron Academy Logo
C#を学ぶ
C#を学ぶ

その他のカテゴリー

.NET Aspire で Linux 上に Get By ID エンドポイントを追加する

[[academy-video-youtube({"vid": "hMhA5sLHUpY", "start_time": "0", "title": "Linux上で for .NET AspireにおけるGet By IDエンドポイントの追加", "creator": "Tim Corey", "length": "15m 20s"})]]

データベーステーブルからすべてのレコードを返すことは、リストページに役立ちますが、ほとんどの API 消費者はその識別子によって単一のレコードを取得する必要もあります。 その 2 番目のエンドポイントでは、"すべてを取得"ルートには必要なかった決定を導入します: 単一オブジェクトに対してどの戻り値タイプが意味をなすか、ID がどのレコードとも一致しない場合どうするか、呼び出し元への失敗を正しい HTTP ステータスコードでどのように伝えるか。

彼のビデオ "Linux上で for .NET AspireにおけるGet By IDエンドポイントの追加" では、Tim CoreyはTiny Ticket APIを続け、GET /api/tickets/{id}エンドポイントを追加します。 既存のルートをコピーペーストすることから始まり、ライブでエッジケースの処理をウォークスルーします: 配列ではなく単一のオブジェクトを返す、null結果をチェックする、IDが存在しない場合に404 Not Foundを返す際に TypedResults を使用して、チケットを持っている場合には200 OKを返します。Linux上でのC#をフォローしている場合や適切なステータスコード応答が必要な最小限のAPIを構築している場合、このエピソードは完全な思考プロセスをカバーしています。

開始点として Get All ルートをコピーする

[0:35 - 1:45] TimはAPIサービスのGET /api/ticketsエンドポイントを複製します。 新しいルートにはチケットIDのパスパラメータが必要で、URLパターンがint idパラメータを受け取ります。 ストアドプロシージャの参照はspTickets_Getに切り替えます。

app.MapGet("/api/tickets/{id:int}", async (int id, IDbConnection db) =>
{
    var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
    // Initial version: returns a list, which we'll fix next
    return tickets;
});
app.MapGet("/api/tickets/{id:int}", async (int id, IDbConnection db) =>
{
    var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
    // Initial version: returns a list, which we'll fix next
    return tickets;
});

メモする価値のあるネーミングの便宜:ストアドプロシージャパラメータは小文字のidで、C#パラメータ名と完全に一致しています。 これはDapperが匿名オブジェクトnew { id }を直接マップできることを意味します。 もしSQLパラメータが異なるケースを使っていたら、匿名オブジェクトにはnew { Id = id }のように明示的なプロパティ割り当てが必要になります。

リストの代わりに単一オブジェクトを返す

[2:39 - 4:16] Swagger を通じた最初のテストで問題が明らかに: ID 2 を渡すと、正しいチケットが含まれた 200 が返されますが、レスポンスボディは JSON 配列にラップされています。 呼び出し元が ID によって単一のリソースを要求する場合、単一のオブジェクトが期待されています。1 つの要素を持つコレクションではありません。

クエリ結果に.FirstOrDefault()を追加するとラッピングが修正されます。 nullを返します。 それにより配列問題は解決されますが、新たな疑問が生まれます: ID がどのレコードとも一致しない場合、API は何を返すべきでしょうか?

var output = tickets.FirstOrDefault();
var output = tickets.FirstOrDefault();

単一行の変更により、既存レコードに対しては正しいレスポンスシェイプが生成されます。 しかし、データベースに存在しないID 4でのテストでは、より深いギャップが明らかになります。レスポンスはnullとして200ステータスコードで返ってきます。 それは技術的には有効な HTTP ですが、呼び出し元を誤解させます: 200 はリクエストが成功しリソースが見つかったことを意味しますが、実際には何も一致していませんでした。

TypedResults で見つからなかったことを処理する

[4:41 - 11:44] このセクションでは、Tim がリアルタイムで設計上の決定を検討する様子があります。最終コードだけでなく視覚的に見る価値がある部分です。 彼の思考プロセスは複数の反復を経て進みます:

まず、彼はリストが空のときに例外をスローする.First()を使用することを検討します。 それにより 500 エラーが発生します。これはヌル 200 よりも悪いです。500 はサーバーバグを示唆し、リソースの欠如ではありません。

次に彼は一旦撤退してヌルチェックを構築します。 彼は結果を変数に格納し、それがヌルであるかどうかをチェックし、各ケースのために異なるレスポンスを返します。 課題は、最小限の API ハンドラーが複数のレスポンスシェイプを返すことができる場合には、戻り値型を明示的に宣言する必要があることです。

解決策はTypedResultsで、これは応答タイプをメソッドのシグネチャで指定できます:

app.MapGet("/api/tickets/{id:int}", async Task<Results<Ok<TicketModel>, NotFound>> (int id, IDbConnection db) =>
{
    var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
    var output = tickets?.FirstOrDefault();

    if (output is null)
    {
        return TypedResults.NotFound();
    }

    return TypedResults.Ok(output);
});
app.MapGet("/api/tickets/{id:int}", async Task<Results<Ok<TicketModel>, NotFound>> (int id, IDbConnection db) =>
{
    var tickets = await db.LoadSqlAsync<TicketModel>("spTickets_Get", new { id });
    var output = tickets?.FirstOrDefault();

    if (output is null)
    {
        return TypedResults.NotFound();
    }

    return TypedResults.Ok(output);
});

その戻り型TicketModelボディを持つ200を生成するか、ボディのない404を生成することをフレームワーク(およびSwagger)に伝えます。 VS Code 内でのカラーブラケットマッチングは、ジェネリックな結果型によって素早く積み上がるネスティングされたアングルブラケットを移動するのに役立ちます。

テスト中に表面化する微妙なバグの一つ:最初のバージョンではreturnしません。 エンドポイントはOkパスにフォールスルーします。 Swaggerが欠落しているIDで200を示し続けるのを見て、Timはreturnを追加し、次の実行で404が正しく表示されます。

Swagger で両方のパスをテストする

[11:44 - 14:09] 最終コードが設定された状態で、Tim はSwagger UIで両方のシナリオを実行します。 ID 3 を渡すと、チケットオブジェクトを含む 200 が返されます。 ID 4 を渡すと、空のレスポンスボディで 404 が返されます。

彼はまた、Swagger インターフェース内で初めてのユーザーを混乱させる可能性のある詳細を指摘します: 実行ボタン下の"Responses"セクションには、可能性のあるレスポンスコード (200 および 404) が表示され、実際の結果ではありません。 実際のサーバーレスポンスは、そのセクションの上にある別のパネルで表示されます。 この二つのパネルを混同することは、"なぜ 200 を受け取っているのか?"という混乱の共通の原因です。

TypedResultsアプローチはSwaggerドキュメントを自動的に改善します。 戻り型がNotFoundの両方を宣言しているため、Swaggerはそれぞれのスキーマで両方を潜在的な結果として表示します。 APIドキュメントを読む呼び出し元は、開発者が別々のOpenAPI注釈を書くことなく、404ケースを処理する必要があることを知っています。

まとめ: 機能の前にエッジケース

[14:09 - 15:09] 単純な全取得エンドポイントのコピー&ペーストから始まったものが、API設計の深い演習へと発展しました。 最終バージョンは、ハッピーパス(レコードが見つかった場合)、予想された失敗(レコードが見つからなかった場合)、予想外のシナリオ(クエリがnullを返す)のための防御的なnullチェックを処理します。 ティムのライブでこれらのケースを扱うアプローチは、制作エンドポイントが必要とする反復的な思考を示しています。

結論

[15:09 - 15:20] 最小限のAPIに対してIDによる取得エンドポイントを追加することは、ルート定義を超えた3つの決定を含みます: コレクションをアンラップするためのTypedResultsを宣言します。 Results<Ok<t>, NotFound>パターンは、成功または不在を伝達する必要がある任意のエンドポイント全体で再利用可能です。

シリーズナビゲーション:この記事は、Tiny Ticketアプリを構築するLinux上のC#シリーズの一部です。前回: Swagger UIの追加。 次: POST挿入エンドポイントの追加

例:最小限のAPIハンドラーが複数の可能性のあるステータスコードを返す場合、常にResults<>ジェネリックでそれらを宣言してください。 これにより、正確なSwaggerドキュメントが自動的に生成され、コンパイラーがすべてのコードパスが有効な結果型を返すことを確認するように強制されます。

彼のYouTubeチャンネル動画全編を視聴し、Linux上のC#シリーズで堅牢なAPIエンドポイントを構築するためのインサイトをさらに得てください。

Hero Worlddot related to .NET Aspire で Linux 上に Get By ID エンドポイントを追加する
Hero Affiliate related to .NET Aspire で Linux 上に Get By ID エンドポイントを追加する

好きなことを共有することで収入を増やす

.NET、C#、Java、Python、またはNode.jsを使用する開発者向けのコンテンツを作成しますか?あなたの専門知識を副収入に変えましょう!

アイアンサポートチーム

私たちは週5日、24時間オンラインで対応しています。
チャット
メール
電話してね