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

Web API 開發

Web API 是什麼?

Web API 就像是餐廳的外送窗口——不提供堂食(HTML 頁面),只提供打包好的餐點(JSON 資料)給外送平台(前端、手機 App)。

前端 App / 手機 App
    ↓ HTTP Request(JSON)
[ApiController]
    ↓ 處理
    ↑ HTTP Response(JSON)
前端 App / 手機 App

[ApiController] 基礎

// Controllers/ProductsApiController.cs
[ApiController]                                     // 標記為 API 控制器
[Route("api/[controller]")]                          // 路由:api/ProductsApi
public class ProductsApiController : ControllerBase  // 繼承 ControllerBase(不是 Controller)
{
    private readonly IProductService _service;        // 商品服務

    // 建構子注入
    public ProductsApiController(IProductService service)
    {
        _service = service;                           // 保存服務參考
    }

    // GET api/ProductsApi
    [HttpGet]                                        // 處理 GET 請求
    public ActionResult<List<Product>> GetAll()
    {
        var products = _service.GetAll();             // 取得所有商品
        return Ok(products);                          // 回傳 200 + JSON
    }

    // GET api/ProductsApi/5
    [HttpGet("{id}")]                                 // 路由參數
    public ActionResult<Product> GetById(int id)
    {
        var product = _service.GetById(id);           // 用 ID 查詢
        if (product == null)
            return NotFound();                        // 找不到回傳 404
        return Ok(product);                           // 回傳 200 + JSON
    }

    // POST api/ProductsApi
    [HttpPost]                                       // 處理 POST 請求
    public ActionResult<Product> Create(
        [FromBody] CreateProductDto dto)              // 從請求主體綁定
    {
        var product = _service.Create(dto);           // 建立商品
        return CreatedAtAction(                       // 回傳 201 Created
            nameof(GetById),                          // 指向 GetById Action
            new { id = product.Id },                  // 路由參數
            product);                                 // 回應主體
    }

    // PUT api/ProductsApi/5
    [HttpPut("{id}")]                                 // 處理 PUT 請求
    public IActionResult Update(
        int id,
        [FromBody] UpdateProductDto dto)              // 從請求主體綁定
    {
        if (!_service.Exists(id))
            return NotFound();                        // 找不到回傳 404
        _service.Update(id, dto);                     // 更新商品
        return NoContent();                           // 回傳 204 No Content
    }

    // DELETE api/ProductsApi/5
    [HttpDelete("{id}")]                              // 處理 DELETE 請求
    public IActionResult Delete(int id)
    {
        if (!_service.Exists(id))
            return NotFound();                        // 找不到回傳 404
        _service.Delete(id);                          // 刪除商品
        return NoContent();                           // 回傳 204 No Content
    }
}

Model Binding 模型綁定

// 各種綁定來源
[HttpGet("search")]
public IActionResult Search(
    [FromQuery] string keyword,                       // 從 URL 查詢字串:?keyword=手機
    [FromQuery] int page = 1,                         // 預設值為 1
    [FromHeader(Name = "X-Api-Key")] string? apiKey)  // 從 HTTP Header
{
    return Ok(new { keyword, page, apiKey });         // 回傳綁定結果
}

[HttpPost("upload")]
public IActionResult Upload(
    [FromForm] string description,                    // 從表單欄位
    [FromForm] IFormFile file)                         // 從表單檔案
{
    return Ok(new { description, file.FileName });    // 回傳檔案名稱
}

[HttpPut("{id}")]
public IActionResult Update(
    [FromRoute] int id,                               // 從路由參數
    [FromBody] UpdateDto dto)                          // 從請求主體(JSON)
{
    return Ok(new { id, dto });                       // 回傳更新資料
}

DTO(Data Transfer Object)

// DTOs/CreateProductDto.cs - 建立商品用的 DTO
public class CreateProductDto
{
    [Required(ErrorMessage = "商品名稱必填")]           // 必填驗證
    [StringLength(100, ErrorMessage = "名稱最多 100 字")]
    public string Name { get; set; } = "";             // 商品名稱

    [Range(0, 999999, ErrorMessage = "價格必須在 0~999999")]
    public decimal Price { get; set; }                  // 商品價格

    public string? Description { get; set; }            // 商品描述(可選)
}

// DTOs/ProductResponseDto.cs - 回應用的 DTO
public class ProductResponseDto
{
    public int Id { get; set; }                         // 商品 ID
    public string Name { get; set; } = "";              // 商品名稱
    public decimal Price { get; set; }                  // 價格
    // 注意:不包含敏感欄位如 Cost、Supplier 等
}

ActionResult 與 IActionResult

// ActionResult<T> → 有明確回傳型別(Swagger 能自動產生文件)
[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), 200)]           // 200 回傳 Product
[ProducesResponseType(404)]                            // 404 找不到
public ActionResult<Product> GetById(int id)
{
    var product = _service.GetById(id);                // 查詢商品
    if (product == null) return NotFound();            // 404
    return Ok(product);                                // 200 + Product JSON
}

// IActionResult → 回傳型別不固定
[HttpPost]
public IActionResult Create([FromBody] CreateProductDto dto)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);                 // 400 驗證失敗
    var product = _service.Create(dto);                // 建立商品
    return CreatedAtAction(                            // 201 Created
        nameof(GetById), new { id = product.Id }, product);
}

API 版本控制

// Program.cs - 設定 API 版本控制
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);  // 預設版本 1.0
    options.AssumeDefaultVersionWhenUnspecified = true; // 未指定時用預設版本
    options.ReportApiVersions = true;                  // 回應中回報版本資訊
});
// v1 控制器
[ApiController]
[Route("api/v{version:apiVersion}/products")]         // URL 路徑版本控制
[ApiVersion("1.0")]                                    // 版本 1.0
public class ProductsV1Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() =>
        Ok(new { version = "v1", data = "舊格式" });   // v1 的回應格式
}

// v2 控制器
[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("2.0")]                                    // 版本 2.0
public class ProductsV2Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() =>
        Ok(new { version = "v2", items = "新格式" });   // v2 的新回應格式
}

Swagger / OpenAPI

// Program.cs - 設定 Swagger
builder.Services.AddEndpointsApiExplorer();            // API 探索器
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo            // Swagger 文件設定
    {
        Title = "商品 API",                              // API 標題
        Version = "v1",                                  // 版本號
        Description = "商品管理 RESTful API"               // 說明
    });
});

var app = builder.Build();

// 只在開發環境啟用 Swagger UI
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();                                  // 啟用 Swagger JSON
    app.UseSwaggerUI();                                // 啟用 Swagger UI
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:所有情況都回傳 200

// ❌ 找不到也回傳 200(前端無法判斷是否成功)
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var product = _service.GetById(id);                // 查詢商品
    return Ok(product);                                // ❌ product 可能是 null!
}
// ✅ 正確使用 HTTP 狀態碼
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var product = _service.GetById(id);                // 查詢商品
    if (product == null)
        return NotFound(new { message = "商品不存在" }); // 404 找不到
    return Ok(product);                                // 200 找到了
}

為什麼? HTTP 狀態碼是 API 的通用語言,200 表示成功,404 表示找不到。前端靠狀態碼判斷如何處理回應。

❌ 錯誤 2:直接回傳 Entity(沒用 DTO)

// ❌ 直接回傳資料庫實體(洩漏敏感資料)
[HttpGet("{id}")]
public ActionResult<User> GetUser(int id)
{
    var user = _db.Users.Find(id);                     // 查詢使用者
    return Ok(user);                                   // ❌ 包含 PasswordHash!
}
// JSON 回應會包含:{ "passwordHash": "abc123...", ... }
// ✅ 使用 DTO 只回傳需要的欄位
[HttpGet("{id}")]
public ActionResult<UserDto> GetUser(int id)
{
    var user = _db.Users.Find(id);                     // 查詢使用者
    if (user == null) return NotFound();               // 找不到
    var dto = new UserDto                              // 建立 DTO
    {
        Id = user.Id,                                  // 只包含安全的欄位
        Username = user.Username,                      // 使用者名稱
        Email = user.Email                             // 電子郵件
    };
    return Ok(dto);                                    // 回傳 DTO
}

為什麼? 資料庫實體可能包含密碼雜湊、內部 ID 等敏感資訊。DTO 只暴露前端需要的欄位,保護資料安全。

❌ 錯誤 3:API 沒有驗證輸入

// ❌ 完全不驗證就直接用(可能收到垃圾資料)
[HttpPost]
public IActionResult Create([FromBody] CreateProductDto dto)
{
    _service.Create(dto);                              // ❌ dto 的欄位可能是 null!
    return Ok();
}
// ✅ 用 DataAnnotation + ModelState 驗證
[HttpPost]
public IActionResult Create([FromBody] CreateProductDto dto)
{
    if (!ModelState.IsValid)                            // 檢查驗證結果
        return BadRequest(ModelState);                  // 回傳 400 + 錯誤訊息
    var product = _service.Create(dto);                 // 驗證通過才建立
    return CreatedAtAction(nameof(GetById),
        new { id = product.Id }, product);              // 回傳 201
}

為什麼? 永遠不要信任前端傳來的資料!DataAnnotation 加上 ModelState 驗證可以在進入商業邏輯前就擋掉不合法的輸入。

💡 大家的想法 · 0

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