前幾天我們逐步完成了 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 可以產生規格文件,並提供介面方便開發者閱讀與測試。
這幾個名詞常常一起出現,所以很容易混在一起。
可以先這樣理解:
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。
我們目前的 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
如果搜尋舊版 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
我們目前使用 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()
處理。
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
剛才我們寫:
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
→ 預設不開
執行:
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
例如:
[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
如何被描述。
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 /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 測試機制。
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
測試流程
團隊管理
所以兩者並不是互相取代。
假設:
[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]
先理解它的用途即可。
現在回頭看 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
今天只要分清楚三個角色:
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 規格。