☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
ASP.NET Core 中級

⚡ Minimal API

📌 什麼是 Minimal API?

Minimal API 是 ASP.NET Core 6 引入的一種輕量級 API 開發方式,不需要 Controller、不需要一堆檔案,直接在 Program.cs 裡就能定義 API 端點。

想像你經營兩種餐廳:

  • Controller-based API 像是大型連鎖餐廳——有經理(Controller)、服務生(Action Method)、菜單系統(Routing),分工明確但架構龐大
  • Minimal API 像是路邊攤——老闆一個人搞定點餐和出餐,快速、簡單、直接

如果你用過 Node.js 的 Express.js,Minimal API 的風格會讓你感到非常熟悉!


🚀 基本用法:MapGet / MapPost / MapPut / MapDelete

// Program.cs - 這就是你的整個 API!
var builder = WebApplication.CreateBuilder(args); // 建立應用程式建構器
var app = builder.Build(); // 建構應用程式

// GET:取得資料(像點菜單上的餐點)
app.MapGet("/api/hello", () => "你好,世界!"); // 最簡單的 GET 端點

// GET:取得所有商品
app.MapGet("/api/products", () =>
{
    // 回傳商品清單(實際上會從資料庫取得)
    var products = new[]
    {
        new { Id = 1, Name = "筆電", Price = 30000 },   // 第一個商品
        new { Id = 2, Name = "滑鼠", Price = 500 },     // 第二個商品
        new { Id = 3, Name = "鍵盤", Price = 2000 }     // 第三個商品
    };
    return Results.Ok(products); // 回傳 200 OK 和商品清單
});

// GET:根據 ID 取得單一商品
app.MapGet("/api/products/{id}", (int id) =>
{
    // 用 id 去查找商品(這裡用假資料示範)
    if (id == 1) // 如果找到了
        return Results.Ok(new { Id = 1, Name = "筆電", Price = 30000 }); // 回傳商品
    return Results.NotFound(new { Message = $"找不到 ID 為 {id} 的商品" }); // 回傳 404
});

// POST:建立新資料(像填寫點餐單送到廚房)
app.MapPost("/api/products", (Product product) =>
{
    // product 參數會自動從 Request Body 的 JSON 反序列化
    Console.WriteLine($"收到新商品:{product.Name}"); // 印出商品名稱
    return Results.Created($"/api/products/{product.Id}", product); // 回傳 201 Created
});

// PUT:更新資料(像修改已經送出的訂單)
app.MapPut("/api/products/{id}", (int id, Product product) =>
{
    // id 從路由參數來,product 從 body 來
    Console.WriteLine($"更新商品 {id}:{product.Name}"); // 印出更新資訊
    return Results.NoContent(); // 回傳 204 No Content(更新成功,不需要回傳內容)
});

// DELETE:刪除資料(像取消訂單)
app.MapDelete("/api/products/{id}", (int id) =>
{
    Console.WriteLine($"刪除商品 {id}"); // 印出刪除資訊
    return Results.NoContent(); // 回傳 204 No Content
});

app.Run(); // 啟動應用程式

🔗 參數繫結:資料從哪裡來?

// [FromQuery]:從 URL 的查詢字串取得(像在網址列輸入搜尋條件)
// 請求:GET /api/search?keyword=筆電&page=1
app.MapGet("/api/search", ([FromQuery] string keyword, [FromQuery] int page) =>
{
    // keyword = "筆電",page = 1(自動從 URL 取得)
    return Results.Ok(new { Keyword = keyword, Page = page }); // 回傳搜尋條件
});

// [FromRoute]:從路由取得(像從地址中取出門牌號碼)
// 請求:GET /api/users/42
app.MapGet("/api/users/{userId}", ([FromRoute] int userId) =>
{
    return Results.Ok(new { UserId = userId }); // 回傳使用者 ID
});

// [FromBody]:從請求主體取得(像打開包裹取出裡面的東西)
// 請求:POST /api/orders,Body 是 JSON
app.MapPost("/api/orders", ([FromBody] Order order) =>
{
    // order 物件會自動從 JSON 反序列化
    return Results.Created($"/api/orders/{order.Id}", order); // 回傳建立結果
});

// [FromHeader]:從 HTTP 標頭取得(像看信封上的寄件人資訊)
app.MapGet("/api/protected", ([FromHeader(Name = "X-Api-Key")] string apiKey) =>
{
    if (apiKey != "my-secret-key") // 驗證 API Key
        return Results.Unauthorized(); // 未授權
    return Results.Ok("歡迎!"); // 授權成功
});

📦 使用 MapGroup 分組

// MapGroup 讓你把相關的 API 端點分組(像把同一類的菜放在菜單的同一頁)
var productGroup = app.MapGroup("/api/products"); // 建立 /api/products 分組

// 以下所有路由都會自動加上 /api/products 前綴
productGroup.MapGet("/", () => Results.Ok("取得所有商品")); // GET /api/products/
productGroup.MapGet("/{id}", (int id) => Results.Ok($"取得商品 {id}")); // GET /api/products/{id}
productGroup.MapPost("/", (Product p) => Results.Created($"/api/products/{p.Id}", p)); // POST /api/products/
productGroup.MapDelete("/{id}", (int id) => Results.NoContent()); // DELETE /api/products/{id}

// 巢狀分組(像菜單裡的子分類)
var adminGroup = app.MapGroup("/api/admin") // 管理員 API 分組
    .RequireAuthorization(); // 這個分組下的所有端點都需要授權

adminGroup.MapGet("/users", () => Results.Ok("管理員:取得所有使用者")); // 需要授權才能存取
adminGroup.MapDelete("/users/{id}", (int id) => Results.NoContent()); // 需要授權才能刪除

🔧 Minimal API 的 Filters

// 端點篩選器(像餐廳門口的安檢,進去前先檢查一下)
app.MapGet("/api/items", () => Results.Ok("通過檢查!"))
    .AddEndpointFilter(async (context, next) =>
    {
        // 在端點執行「之前」做的事
        Console.WriteLine("進入端點前...\n"); // 記錄日誌

        var result = await next(context); // 執行端點處理(像放行讓客人進去)

        // 在端點執行「之後」做的事
        Console.WriteLine("離開端點後...\n"); // 記錄日誌

        return result; // 回傳結果
    });

// 自訂驗證篩選器(像門口的保鏢,檢查你的證件)
app.MapPost("/api/items", (Item item) => Results.Ok(item))
    .AddEndpointFilter(async (context, next) =>
    {
        var item = context.GetArgument<Item>(0); // 取得第一個參數
        if (string.IsNullOrEmpty(item.Name)) // 如果名稱為空
        {
            return Results.BadRequest("商品名稱不可為空"); // 回傳 400 錯誤
        }
        return await next(context); // 驗證通過,繼續執行
    });

📊 Minimal API vs Controller-based API 比較

項目 Minimal API Controller-based API
程式碼量 少,適合小型 API 多,但結構清晰
學習曲線 低,快速上手 較高,需理解 MVC 模式
檔案結構 可以全部寫在 Program.cs 需要 Controller 資料夾和檔案
適用場景 微服務、小型 API、原型 大型企業應用、複雜 API
模型驗證 需手動或用 Filter 內建 [ApiController] 自動驗證
Swagger 支援,但需額外設定 自動整合
可測試性 可測試,但需要技巧 容易透過 DI 測試

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:沒有做輸入驗證

// ❌ 錯誤寫法:直接信任使用者輸入(像不檢查就讓所有人進門)
app.MapPost("/api/users", (User user) =>
{
    // 沒有驗證 user 的內容就直接存入資料庫
    db.Users.Add(user); // 如果 user.Name 是 null 呢?如果 email 格式不對呢?
    db.SaveChanges(); // 存入垃圾資料!
    return Results.Created($"/api/users/{user.Id}", user); // 回傳
});
// ✅ 正確寫法:先驗證再處理
app.MapPost("/api/users", (User user) =>
{
    // 驗證必填欄位
    if (string.IsNullOrWhiteSpace(user.Name)) // 名稱不可為空
        return Results.BadRequest(new { Error = "名稱為必填" }); // 回傳 400

    if (string.IsNullOrWhiteSpace(user.Email)) // Email 不可為空
        return Results.BadRequest(new { Error = "Email 為必填" }); // 回傳 400

    // 驗證通過才存入
    db.Users.Add(user); // 存入資料庫
    db.SaveChanges(); // 儲存變更
    return Results.Created($"/api/users/{user.Id}", user); // 回傳 201
});

解釋: 不驗證輸入就像不鎖門就出門——遲早會出問題。使用者可能送來空值、超長字串、或惡意內容,永遠不要信任來自外部的資料。

❌ 錯誤 2:沒有處理例外

// ❌ 錯誤寫法:沒有 try-catch(像在高速公路上不繫安全帶)
app.MapGet("/api/data/{id}", (int id) =>
{
    var data = db.Items.Find(id); // 如果資料庫連線失敗?
    return Results.Ok(data); // data 可能是 null!
});
// ✅ 正確寫法:處理各種可能的錯誤情況
app.MapGet("/api/data/{id}", (int id) =>
{
    try
    {
        var data = db.Items.Find(id); // 嘗試查找資料
        if (data == null) // 資料不存在
            return Results.NotFound(new { Error = $"找不到 ID={id} 的資料" }); // 404
        return Results.Ok(data); // 200 OK
    }
    catch (Exception ex) // 捕捉所有例外
    {
        return Results.Problem($"伺服器錯誤:{ex.Message}"); // 回傳 500
    }
});

解釋: API 是對外的窗口,任何未處理的例外都會導致回傳 500 錯誤,還可能洩漏程式內部資訊。就像餐廳廚房失火了,不能直接讓客人看到火焰,要先處理好再告知客人「抱歉,暫時無法供餐」。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊