iT邦幫忙

2026 iThome 鐵人賽

DAY 30
0
Modern Web

現在就學C# 與 ASP.NET Core系列 第 30 篇

Day 30|OpenAPI / Swagger:API 做好了,別人怎麼知道怎麼用?

  • 分享至 

  • xImage
  •  

前幾天我們逐步完成了 Product API:

Routing
↓
Model Binding
↓
DTO / Validation
↓
Dependency Injection
↓
Middleware
↓
Global Exception Handling

現在已經可以建立:

GET    /api/products
GET    /api/products/1
POST   /api/products
PUT    /api/products/1
DELETE /api/products/1

但接下來會遇到一個很實際的問題:

如果今天有另一位前端工程師要串接這支 API,他怎麼知道 API 要怎麼使用?

他需要知道:

有哪些 Endpoint?

使用 GET、POST 還是 PUT?

Request 要傳什麼?

Response 會回什麼?

有哪些 HTTP Status?

要怎麼測試?

如果只能另外手動整理文件:

API 修改
↓
文件也要修改
↓
忘記更新
↓
文件和實際 API 不一致

維護成本就會開始提高。

因此今天要介紹:

OpenAPI
Swagger UI

讓 API 可以產生規格文件,並提供介面方便開發者閱讀與測試。


1. OpenAPI、OpenAPI Document、Swagger UI 是什麼?

這幾個名詞常常一起出現,所以很容易混在一起。

可以先這樣理解:

OpenAPI
→ API 規格標準

OpenAPI Document
→ 按照 OpenAPI 規格描述 API 的文件

Swagger UI
→ 讀取 OpenAPI Document
→ 顯示成可以閱讀與操作的網頁

因此:

OpenAPI 是規格;Swagger UI 是使用這份規格的工具。

例如 Product API:

GET /api/products/{id}

OpenAPI Document 可以描述:

Path
→ /api/products/{id}

HTTP Method
→ GET

Parameter
→ id

Response
→ Product

Status
→ 200 / 404

OpenAPI Document 通常會使用:

JSON
或
YAML

今天不需要自己手動撰寫這份文件。

ASP.NET Core 可以根據 API 提供的 Metadata 自動產生 OpenAPI Document。


2. OpenAPI Document 怎麼產生?

我們目前的 Controller 已經包含很多 API 資訊。

例如:

[ApiController]
[Route("api/[controller]")]
public class ProductsController
    : ControllerBase
{
}

Action:

[HttpGet("{id:int}")]
public ActionResult<Product> GetById(
    int id
)

以及 DTO:

public class CreateProductRequest
{
    public string Name
    {
        get;
        set;
    } = string.Empty;

    public decimal Price
    {
        get;
        set;
    }
}

這些資訊都可以形成 API Metadata:

Route
HTTP Method
Parameter
Request Body
Response Type

ASP.NET Core 可以根據這些 Metadata:

Controller / DTO
↓
API Metadata
↓
ASP.NET Core
↓
OpenAPI Document

例如最後提供:

/openapi/v1.json

Swagger UI 再去讀取這份 OpenAPI Document。

所以今天最重要的主線是:

Controller / DTO
↓
API Metadata
↓
OpenAPI Document
↓
Swagger UI

3. .NET 9+ 的 OpenAPI 做法

如果搜尋舊版 ASP.NET Core 教學,很常看到:

builder.Services.AddSwaggerGen();

app.UseSwagger();

app.UseSwaggerUI();

這是常見的:

Swashbuckle

做法。

從 .NET 9 開始,ASP.NET Core 已經提供內建的 OpenAPI Document 產生功能。

可以使用:

builder.Services.AddOpenApi();

以及:

app.MapOpenApi();

產生 OpenAPI Document。

今天可以直接記:

.NET 9+

ASP.NET Core
→ 產生 OpenAPI Document

Swagger UI
→ 額外加入
→ 顯示 OpenAPI Document

也就是:

ASP.NET Core 內建 OpenAPI 文件產生能力,但不會自動附帶 Swagger UI。

今天會採用:

ASP.NET Core OpenAPI
+
Swagger UI

4. 安裝 Swagger UI

我們目前使用 VS Code 與 dotnet CLI。

在專案目錄執行:

dotnet add package NSwag.AspNetCore

如果專案尚未包含:

Microsoft.AspNetCore.OpenApi

也可以加入:

dotnet add package Microsoft.AspNetCore.OpenApi

今天兩者的角色:

Microsoft.AspNetCore.OpenApi
→ 產生 OpenAPI Document

NSwag.AspNetCore
→ 提供 Swagger UI

這裡有一個很重要的觀念:

今天不是使用 NSwag 產生 OpenAPI Document,而是使用它提供 Swagger UI。

OpenAPI Document 仍然由 ASP.NET Core:

AddOpenApi()
MapOpenApi()

處理。


5. 修改 Program.cs

目前 Day 29 的 Program.cs:

using MyApi2.Exceptions;
using MyApi2.Services;

var builder =
    WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddScoped<
    IProductService,
    ProductService
>();

builder.Services.AddProblemDetails();

builder.Services.AddExceptionHandler<
    GlobalExceptionHandler
>();

var app = builder.Build();

app.UseExceptionHandler();

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

現在加入 OpenAPI:

using MyApi2.Exceptions;
using MyApi2.Services;

var builder =
    WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddScoped<
    IProductService,
    ProductService
>();

builder.Services.AddProblemDetails();

builder.Services.AddExceptionHandler<
    GlobalExceptionHandler
>();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();

    app.UseSwaggerUi(options =>
    {
        options.DocumentPath =
            "/openapi/v1.json";
    });
}

app.UseExceptionHandler();

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

今天主要新增三個地方。

第一個:

builder.Services.AddOpenApi();

可以理解成:

註冊 OpenAPI 相關服務

第二個:

app.MapOpenApi();

提供 OpenAPI Document。

預設可以透過:

/openapi/v1.json

取得。

第三個:

app.UseSwaggerUi(options =>
{
    options.DocumentPath =
        "/openapi/v1.json";
});

代表:

Swagger UI
↓
讀取
/openapi/v1.json
↓
顯示 API 文件

所以:

AddOpenApi()
↓
準備 OpenAPI

MapOpenApi()
↓
提供 OpenAPI Document

UseSwaggerUi()
↓
讀取 OpenAPI Document
↓
顯示 Swagger UI

6. 為什麼放在 Development?

剛才我們寫:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();

    app.UseSwaggerUi(options =>
    {
        options.DocumentPath =
            "/openapi/v1.json";
    });
}

也就是只有:

Development

環境才提供 OpenAPI Document 與 Swagger UI。

因為 API 文件可能會揭露:

Endpoint
Request Structure
Response Structure
Parameter
API 架構資訊

正式環境是否公開 API 文件,應該依照實際專案需求與安全政策決定。

所以今天先採用:

Development
→ 開啟

Production
→ 預設不開

7. 執行 OpenAPI 與 Swagger UI

執行:

dotnet run

Terminal 可能看到:

Now listening on:
https://localhost:xxxx

實際 Port 依專案為準。

先開啟:

https://localhost:xxxx/openapi/v1.json

就可以看到 OpenAPI Document。

內容可能很多,現在不需要全部看懂。

只需要知道:

這份 JSON
↓
描述目前 Application 的 API

裡面會包含:

/api/products
/api/products/{id}

以及:

GET
POST
PUT
DELETE

接著開啟:

https://localhost:xxxx/swagger

Swagger UI 會把 OpenAPI JSON 轉成比較容易閱讀的 API 文件介面。

可能會看到:

GET
/api/products

GET
/api/products/{id}

POST
/api/products

PUT
/api/products/{id}

DELETE
/api/products/{id}

這些 Endpoint 並不是 Swagger UI 自己猜出來的。

而是來自:

[Route("api/[controller]")]

[HttpGet]

[HttpGet("{id:int}")]

[HttpPost]

[HttpPut("{id:int}")]

[HttpDelete("{id:int}")]

也就是:

Controller Metadata
↓
OpenAPI Document
↓
Swagger UI

8. Swagger UI 怎麼知道 Request Body?

例如:

[HttpPost]
public ActionResult<Product> Create(
    CreateProductRequest request
)
{
    Product product =
        _productService.Create(request);

    return CreatedAtAction(
        nameof(GetById),
        new { id = product.Id },
        product
    );
}

其中:

CreateProductRequest request

代表 Request Body 使用:

CreateProductRequest

假設 DTO:

public class CreateProductRequest
{
    public string Name
    {
        get;
        set;
    } = string.Empty;

    public decimal Price
    {
        get;
        set;
    }
}

OpenAPI 可以產生對應的 Schema。

Swagger UI 可能顯示:

{
  "name": "string",
  "price": 0
}

流程可以理解成:

CreateProductRequest
↓
API Metadata
↓
OpenAPI Schema
↓
Swagger UI
↓
顯示 Request Body

所以 DTO 不只是:

Controller 接收資料的型別

它也會影響:

API Contract

如何被描述。


9. 用 Swagger UI 測試 API

Swagger UI 不只是顯示文件,也可以直接送出 HTTP Request。

例如:

GET /api/products

展開 Endpoint 後:

Try it out
↓
Execute

Swagger UI 會真的送出:

HTTP GET /api/products

並顯示:

Request URL
Response Status
Response Body
Response Headers

例如:

[
  {
    "id": 1,
    "name": "Mouse",
    "price": 1000
  }
]

因此 Swagger UI 同時也是:

簡單的 API 測試工具

測試 POST

例如:

POST /api/products

Swagger UI 會根據:

CreateProductRequest

產生 Request Body。

例如:

{
  "name": "Keyboard",
  "price": 2500
}

執行後:

Swagger UI
↓
HTTP POST /api/products
↓
Model Binding / Validation
↓
ProductsController
↓
ProductService
↓
201 Created

所以:

Swagger UI 最後送出的仍然是真正的 HTTP Request。

它不是另一套特殊的 Controller 測試機制。


10. Swagger UI 和 Postman 有什麼不同?

Swagger UI 和 Postman 都可以測試 API,但主要角色不同。

工具 主要用途
OpenAPI 描述 API 規格
Swagger UI 根據 OpenAPI 顯示與快速測試 API
Postman 建立、測試與管理 HTTP Request

Swagger UI 的優勢是:

API Metadata
↓
OpenAPI Document
↓
自動顯示 Endpoint

很適合:

閱讀 API
查看 Request / Response
快速測試

而 Postman 通常還會處理:

Request Collection
Environment
測試流程
團隊管理

所以兩者並不是互相取代。


11. Response 也可以描述得更清楚

假設:

[HttpGet("{id:int}")]
public ActionResult<Product> GetById(
    int id
)

實際上可能回傳:

200 OK
404 Not Found

但:

程式真的會回傳某個 Status,不代表 OpenAPI 一定能完整知道所有可能的 Response。

如果希望 Response Metadata 更明確,可以加入:

[ProducesResponseType(
    typeof(Product),
    StatusCodes.Status200OK
)]
[ProducesResponseType(
    StatusCodes.Status404NotFound
)]
[HttpGet("{id:int}")]
public ActionResult<Product> GetById(
    int id
)
{
    Product? product =
        _productService.GetById(id);

    if (product is null)
    {
        return NotFound();
    }

    return Ok(product);
}

其中:

[ProducesResponseType(...)]

可以先理解成:

補充這個 Action 可能產生哪些 Response。

例如:

200 OK
→ Product

404 Not Found

這些資訊可以成為:

Response Metadata

讓 OpenAPI 文件更加完整。

所以這裡有一個很重要的觀念:

OpenAPI 是根據 API Metadata 產生文件,因此 Metadata 越完整,產生的 API 文件通常也會越完整。

今天不用急著把所有 Action 都加上:

[ProducesResponseType]

先理解它的用途即可。


12. 一次看懂完整流程

現在回頭看 Product API:

ProductsController / DTO
↓
Route / Action / Request / Response Metadata
↓
ASP.NET Core
↓
OpenAPI Document
↓
/openapi/v1.json
↓
Swagger UI
↓
開發者閱讀 / 測試 API

程式碼主要對應三個位置。

第一個:

builder.Services.AddOpenApi();

代表:

註冊 OpenAPI

第二個:

app.MapOpenApi();

代表:

提供 OpenAPI Document

第三個:

app.UseSwaggerUi(options =>
{
    options.DocumentPath =
        "/openapi/v1.json";
});

代表:

Swagger UI
↓
讀取 OpenAPI Document
↓
顯示 API 文件

所以今天最重要的 Mental Model:

Controller / DTO
↓
API Metadata
↓
OpenAPI Document
↓
Swagger UI
↓
閱讀 / 測試 API

Day 30 小結

今天只要分清楚三個角色:

OpenAPI
→ API 規格標準

OpenAPI Document
→ 描述 API 的規格文件

Swagger UI
→ 讀取 OpenAPI Document
→ 顯示與測試 API

而 ASP.NET Core 負責:

API Metadata
↓
產生 OpenAPI Document

如果希望文件描述得更完整,也可以透過:

[ProducesResponseType]

補充 Response Metadata。

OpenAPI 負責描述 API;Swagger UI 負責讓開發者方便閱讀與操作這份 API 規格。


上一篇
Day 29|Middleware 與 Global Exception Handling
系列文
現在就學C# 與 ASP.NET Core 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言