跳至頁尾內容
Iron Academy Logo
學習C#
學習C#

其他類別

在Linux上新增.NET Aspire中按ID獲取端點

[[academy-video-youtube({"vid": "hMhA5sLHUpY", "start_time": "0", "title": "在Linux上為.NET Aspire新增一個通過ID獲取的端點", "creator": "Tim Corey", "length": "15m 20s"})]]

從資料庫表中返回每條記錄對於列出頁面很有用,但大多數API使用者也需要通過其標識符來獲取單個記錄。 第二個端點引入了一些"獲取所有"路徑不需要的決定:對單個物件與集合來說,哪種返回型別更合適,當ID不匹配任何記錄時應發生什麼,以及如何使用正確的HTTP狀態碼將失敗資訊傳遞給調用者。

在他的视频"在Linux上為.NET Aspire新增一個通過ID獲取的端點"中,Tim Corey 續寫Tiny Ticket API,新增了一個GET /api/tickets/{id}端點。 起初只是一個現有路徑的複製粘貼,然後演變成關於邊界情況處理的現場演示:返回單個物件而不是陣列,檢查空結果,以及使用404 Not Found。如果您在關注C# on Linux系列或在構建需要適當狀態碼響應的API,此集覆蓋了完整的思考過程。

複製獲取全部路徑作為起始點

[0:35 - 1:45] Tim打開API服務的GET /api/tickets端點。 新路徑需要一個票務ID的路徑參數,因此URL模式更改為包含int id參數。 儲存過程引用從spTickets_Get,需要一個ID參數。

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請求單個資源時,他們期望獲取的是單一物件,而不是包含一個元素的集合。

在查詢結果中附加.FirstOrDefault()可以解決包裝問題。 null。 這解決了陣列問題,但也引出一個新問題:當ID與任何記錄不匹配時,API應返回什麼?

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

單行更改為現有記錄產生了正確的響應形狀。 然而,使用資料庫中不存在的ID 4進行測試揭示了一個更深的漏洞。響應回來顯示null並附帶200狀態碼。 這在技術上確實是有效的HTTP,但卻誤導了調用者:200表示請求成功且資源已找到,而實際上卻沒有匹配。

使用TypedResults處理未找到的資源

[4:41 - 11:44] 這部分是Tim實時處理設計決策的地方,比起僅僅閱讀最終程式碼更具價值。 他的思維過程經歷了幾個迭代:

首先,他考慮使用.FirstOrDefault(),當列表為空時會拋出異常。 這會產生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。 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文件的調用者知道他們需要處理404情況,無需開發者撰寫額外的OpenAPI註解。

總結:優先處理邊界情況而非功能

[14:09 - 15:09] 簡單的獲取全部端點複製粘貼最終演變成一個更深入的API設計練習。 最終版本處理了快樂路徑(找到記錄),預期的失敗(未找到記錄),以及對於意外場景(查詢返回null)的防禦性空檢查。 Tim選擇現場處理這些案例,而不是展示打磨過的程式碼,展示了生產端點所需的那種迭代思考。

結論

[15:09 - 15:20] 在最小API中新增一個通過ID獲取的端點,涉及到路徑定義之外的三項決策:使用TypedResults在返回型別中,以便框架返回正確的HTTP狀態碼。 Results<Ok<t>, NotFound>模式可在任何需要傳達成功或缺席的端點中重複使用。

系列導航:這篇文章是C# on Linux系列的一部分,構建Tiny Ticket應用程式。前一篇:新增Swagger UI。 下一篇:新增POST插入端點

範例提示:當您的簡約API處理程式返回多個可能的狀態碼時,始終在Results<>泛型中聲明它們。 這自動生成準確的Swagger文件,並強制編譯器驗證每條程式碼路徑返回有效的結果型別。

觀看完整版影片在他的YouTube頻道,並從C# on Linux系列中獲得更多建立強健API端點的見解。

Hero Worlddot related to 在Linux上新增.NET Aspire中按ID獲取端點
Hero Affiliate related to 在Linux上新增.NET Aspire中按ID獲取端點

分享您所愛以賺取更多報酬

您是否為使用 .NET、C#、Java、Python 或 Node.js 的開發者建立內容?將您的專業知識轉化為額外收入!

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話