ASP.NET Core 是一個現代化、高效能的開源 Web 框架,其核心優勢在於其設計理念與功能特性,使其成為當代 Web 開發的理想選擇。
跨平台相容性:真正的「一次編寫,隨處執行」。開發者可在 Windows, Linux, macOS 上使用 .NET SDK 進行開發與部署。
- 這意味著團隊成員可以使用各自偏好的作業系統,而應用程式最終可以部署到成本效益最高的 Linux 伺服器或 Docker 容器中,提供極高的環境靈活性與成本效益。
高效能與輕量級架構:ASP.NET Core 從底層進行了效能優化,它內建了 Kestrel,一個跨平台、非同步的高效能 Web 伺服器。
- 框架大量採用非同步程式設計模型 (async/await),能以極低的資源開銷和執行緒佔用來處理大量並行請求,其效能與可擴充性可輕鬆應對從小型專案到企業級應用的不斷增長的流量需求。
模組化與靈活性:框架本身是一個最小化的核心,開發者可以透過 NuGet 套件管理器,像堆疊積木一樣,僅選擇必要的元件來建構應用程式。
- 從實體框架 (Entity Framework Core) 到身分驗證,再到 JSON 處理,所有功能都是模組化的。這種設計不僅減小了應用程式的體積,也使其易於維護,並能隨著時間推移靈活地添加或升級新功能。
內建依賴注入 (DI):框架原生深度整合了依賴注入容器,這不僅僅是個功能,而是一種核心設計哲學。
- 它促進了模組化、鬆散耦合的程式碼結構,讓不同元件之間的依賴關係更加清晰,大幅提升了應用程式的可測試性(可輕易替換依賴項進行單元測試)與可擴充性。
統一的程式設計模型:開發者可以使用相同的工具、語言 (C#) 和設計模式(如 MVC、Razor Pages)來開發 Web UI 應用程式和 Web API。
- Blazor 的出現更進一步,讓開發者能用 C# 取代 JavaScript 來建構豐富的用戶端互動介面,實現了前後端技術棧的統一,簡化了開發流程與團隊協作。
強大的路由與中介軟體 (Middleware):
- 路由:提供強大且靈活的路由系統,能有效地將 HTTP 請求的 URL 對應到指定的處理端點 (Endpoint),支援傳統路由、屬性路由以及最小 API 的路由範本,並可定義路由約束。
- 中介軟體:所有進入 ASP.NET Core 的請求都會通過一個可自訂的「請求處理管線」。管線由一系列中介軟體元件組成,每個元件負責一項特定任務(如記錄日誌、靜態檔案服務、身分驗證),處理完後再將請求傳遞給下一個元件,如同工廠的流水線,結構清晰且易於擴充。
- 安全性與部署選項:安全性是框架的設計重點。它內建整合的身分驗證與授權功能、跨站請求偽造 (CSRF) 保護、資料保護 API 等。
- 同時提供極其靈活的部署選項,可部署於 IIS、Nginx、Apache 等反向代理伺服器後,也能直接打包成獨立執行的應用程式,或封裝在 Docker 容器中,輕鬆部署於雲端平台 (Azure, AWS, GCP)、本地伺服器或容器協同運作平台 (Kubernetes)。
- 成本效益:作為一個完全開源的框架(MIT 授權),ASP.NET Core 無需任何授權費用,
- 它可以與 Linux 等高成本效益的託管選項完美結合,並搭配使用 Visual Studio Code、PostgreSQL 等免費的開發工具與資料庫,顯著降低從開發到部署的總體擁有成本。
在 ASP.NET Core 中,有兩種主流方式來建立 API 端點:傳統控制器 (Controller) 和 最小 API (Minimal APIs)。
步驟一:建立資料模型 (Product.cs)
這種方式遵循 MVC (Model-View-Controller) 設計模式,結構清晰,透過類別與方法組織商業邏輯,特別適合功能複雜、需要長期維護的大型應用。
建立新項目 dotnet new webapi -n 項目名稱
這是定義資料結構的簡單 C# 類別。
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
步驟二:建立控制器 (ProductsController.cs)
控制器是一個繼承自 ControllerBase 的類別,[ApiController] 屬性會啟用多項便利功能,例如自動模型驗證 (返回 400 Bad Request) 和更詳細的問題描述。
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
[Route(“api/[controller]”)] // 定義路由範本為 /api/products
[ApiController]
public class ProductsController : ControllerBase
{
// 使用靜態列表模擬資料庫
private static List<Product> _products = new List<Product>
{
new Product { Id = 1, Name = “無線耳機”, Price = 99.99m },
new Product { Id = 2, Name = “智慧手錶”, Price = 199.99m }
};
// 1. 獲取資源 (GET) api/products
[HttpGet]
public ActionResult<IEnumerable<Product>> Get()
{
return Ok(_products); // 返回 200 OK 狀態碼與所有產品列表
}
// GET api/products/1
[HttpGet(“{id}”)]
public ActionResult<Product> Get(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product == null)
{
return NotFound(); // 若找不到,返回 404 Not Found
}
return Ok(product); // 找到產品,返回 200 OK
}
// 2. 創建資源 (POST) api/products
[HttpPost]
public IActionResult Post([FromBody] Product newProduct)
{
if (newProduct == null || string.IsNullOrEmpty(newProduct.Name))
{
return BadRequest(“產品名稱不可為空。”);
}
newProduct.Id = _products.Any() ? _products.Max(p => p.Id) + 1 : 1;
_products.Add(newProduct);
// 返回 201 Created,這是 RESTful API 的最佳實踐
// 它告訴客戶端資源已成功創建,並在 Location 標頭中提供新資源的 URL
return CreatedAtAction(nameof(Get), new { id = newProduct.Id }, newProduct);
}
// 3. 更新資源 (PUT) api/products/1
[HttpPut(“{id}”)]
public IActionResult Put(int id, [FromBody] Product updatedProduct)
{
var existingProduct = _products.FirstOrDefault(p => p.Id == id);
if (existingProduct == null)
{
return NotFound();
}
existingProduct.Name = updatedProduct.Name;
existingProduct.Price = updatedProduct.Price;
return NoContent(); // 返回 204 No Content,表示操作成功,但伺服器無須返回任何內容
}
// 4. 刪除資源 (DELETE) api/products/1
[HttpDelete(“{id}”)]
public IActionResult Delete(int id)
{
var productToRemove = _products.FirstOrDefault(p => p.Id == id);
if (productToRemove == null)
{
return NotFound();
}
_products.Remove(productToRemove);
return NoContent(); // 同樣返回 204 No Content
}
}
[FromBody]:指示框架從 HTTP 請求的 Body 中解析 JSON 或 XML 資料並綁定到方法參數。
IActionResult / ActionResult<T>:作為方法的返回型別,允許返回包含 HTTP 狀態碼和可選內容的標準化響應。ActionResult<T> 提供了強型別的好處,同時保留了返回不同狀態碼的靈活性。
步驟一:設定檔與資料模型 (Program.cs)
最小 API 是 .NET 6 後引入的一種更簡潔、快速的 API 開發方式。它移除了 Controller 的模板程式碼,直接在 Program.cs 中使用 Lambda 函式定義端點,特別適合微服務、簡單後端或快速原型開發。
// 待辦事項資料模型
public class Todo
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool IsComplete { get; set; } = false;
}
// 模擬資料庫
var todos = new List<Todo>
{
new Todo { Id = 1, Title = “學習最小 API”, IsComplete = false },
new Todo { Id = 2, Title = “完成專案範例”, IsComplete = true }
};
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
步驟二:使用 app.Map 方法實作 API 端點
這些方法直接將路由範本、HTTP 方法和處理邏輯綁定在一起。框架會自動從路由、查詢字串或請求主體中推斷和綁定參數。
// 1. Read 獲取資源 (GET) /api/todos
app.MapGet(“/api/todos”, () => Results.Ok(todos));
app.MapGet(“/api/todos/{id}”, (int id) =>
{
var todo = todos.FirstOrDefault(t => t.Id == id);
return todo is null ? Results.NotFound() : Results.Ok(todo);
});
// 2. Create 創建資源 (POST) /api/todos
app.MapPost(“/api/todos”, (Todo newTodo) =>
{
newTodo.Id = todos.Any() ? todos.Max(t => t.Id) + 1 : 1;
todos.Add(newTodo);
return Results.Created($”/api/todos/{newTodo.Id}”, newTodo);
});
Any() : 是否包含任何元素
// 3. Update 更新資源 (PUT) /api/todos/{id}
app.MapPut(“/api/todos/{id}”, (int id, Todo updatedTodo) =>
{
var existingTodo = todos.FirstOrDefault(t => t.Id == id);
if (existingTodo is null) return Results.NotFound();
existingTodo.Title = updatedTodo.Title;
existingTodo.IsComplete = updatedTodo.IsComplete;
return Results.NoContent();
});
// 4. Delete 刪除資源 (DELETE) /api/todos/{id}
app.MapDelete(“/api/todos/{id}”, (int id) =>
{
var todoToRemove = todos.FirstOrDefault(t => t.Id == id);
if (todoToRemove is null) return Results.NotFound();
todos.Remove(todoToRemove);
return Results.NoContent();
});
app.Run();
路由參數約束:可以在路由範本中定義參數型別或可選性,例如 /{id:int}(約束為整數)、/{name:string?}(可選的字串)。
萬用字元:{*extraPath} 可用於匹配任意後續路徑,常用於檔案伺服器或代理。
URL 範例 : /api/products?pageSize=10&pageNumber=2
?pageNumber是用來篩選或控制 API 行為的。
DI 是一種核心設計模式,其目標是實現鬆散耦合 (Loose Coupling),讓程式碼更具彈性、可維護性與可測試性。ASP.NET Core 透過一個內建的服務容器 (Service Container) 來自動管理所有註冊服務的生命週期與依賴關係。
核心流程:
1.定義抽象 (介面):建立一個介面來定義功能合約。這是依賴方和被依賴方之間的「插座標準」。
2.建立實作 (類別):建立一個或多個實作該介面的具體類別。
3.註冊服務:在 Program.cs 中,使用 builder.Services 告訴服務容器「當需要某個介面時,應該提供哪個實作類別的實例」。
4.使用服務 (注入):在需要該功能的類別(例如 Controller 或其他服務)的建構子中請求介面,容器會自動解析依賴關係鏈,並將已註冊的實例「注入」進來。開發者無需手動創建 (new) 依賴物件。
服務的生命週期 (Lifetime):
在註冊服務時,指定生命週期至關重要,它決定了物件實例的創建與銷毀時機,直接影響應用程式的效能與行為。
AddSingleton 單例模式 : 我要都一樣的狀態,整個應用程式從啟動到關閉都只會有一個實例。
- builder.Services.AddScoped介面類別, 實作方法>();
- 適合用於全域共享且無狀態的服務,例如:設定檔、日誌記錄器、快取管理員。
AddScoped 範圍模式 : 這次我只要這個狀態,在一個 HTTP 請求的生命週期內,只會有一個實例,請求結束後實例就會刪除。
- builder.Services.AddSingleton<介面類別, 實作方法>();
- 適合用於處理請求相關狀態的服務,例如:資料庫連線上下文 (DbContext)、交易處理服務。
AddTransient 暫時模式 : 我要都不一樣的狀態,每次被請求時,都會建立一個全新的實例。
- builder.Services.AddTransient<介面類別, 實作方法>();
- 適合用於輕量且不共享的服務,例如:簡單的工具函式庫、執行簡單計算的服務。
// 抽象-加熱
public interface IHeater { void Heat(); }
// 實作 1:電熱水器
public class ElectricHeater : IHeater{
public void Heat() => Console.WriteLine(“開始用電加熱…”); }
// 實作 2:太陽能熱水器
public class SolarHeater : IHeater {
public void Heat() => Console.WriteLine(“開始用太陽能加熱…”); }
// 依賴類別:咖啡機
public class CoffeeMaker
{
private readonly IHeater _heater;
// 依賴從建構子注入,CoffeeMaker 只知道 IHeater,不知道具體是哪種加熱器
public CoffeeMaker(IHeater heater) { _heater = heater; }
public void Brew() { _heater.Heat(); }
}
public void Brew()
{
_heater.Heat();
Console.WriteLine(“咖啡煮好了。”);
}
}
Program.cs
var builder = WebApplication.CreateBuilder(args);
// 註冊服務,指定生命週期
// 當有人需要 IHeater 的時候,給他 ElectricHeater。
builder.Services.AddScoped<IHeater, ElectricHeater>();
// 當有人需要 IHeater 的時候,給他 SolarHeater。
builder.Services.AddScoped<IHeater, SolarHeater>();
// 註冊 IHeater 實作 ElectricHeater 或是 SolarHeater。
builder.Services.AddScoped<CoffeeMaker>();
使用服務 (自動注入)
不需要自己 new 出 CoffeeMaker 或 ElectricHeater,系統會自動幫你處理好。
@inject CoffeeMaker coffee
<button @onclick=”coffee.Brew”>煮咖啡</button>
中介軟體是組成 ASP.NET Core 請求處理管線 (Request Pipeline) 的核心元件。可以將其想像成一條洋蔥般的處理鏈,每個請求都會從外層到內層依序通過這個管線,響應則會從內層到外層反向傳回。每個中介軟體都可以檢視和修改請求,然後決定是將其傳遞給下一個中介軟體,還是直接終止流程並返回響應。
順序至關重要,因為後面的中介軟體依賴於前面中介軟體的處理結果。例如,身分驗證 (UseAuthentication) 必須在授權 (UseAuthorization) 之前,因為系統必須先知道使用者是誰,才能判斷他有何權限。
常見中介軟體範例 (Program.cs):
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
1. 錯誤處理 (通常放在最前面,以捕捉後續流程中拋出的任何錯誤)
app.UseExceptionHandler(“/Error”);
2. 靜態檔案處理 (讓伺服器能直接提供 CSS, JS, 圖片等檔案,若匹配則直接返回,不進入後續管線)
app.UseStaticFiles();
3. 路由匹配 (解析 URL,決定由哪個 Controller Action 或 Minimal API 端點處理)
app.UseRouting();
4. CORS (跨來源資源共用) 處理 (若有需要,應放在路由和授權之間)
app.UseCors();
5. 身分驗證 (讀取 Cookie 或 Token,識別使用者是誰)
app.UseAuthentication();
6. 授權檢查 (根據使用者的身分和角色,判斷其是否有權限訪問該端點)
app.UseAuthorization();
7. 執行端點 (執行最終匹配到的 Controller Action 或 Minimal API 的邏輯)
app.MapControllers();
app.MapRazorPages();
app.Run(); // 這是終端中介軟體,若請求到達此處仍未被處理,則應用程式結束
JSON 是現代 Web API 中最主要的資料交換格式。System.Text.Json 是 .NET 內建的高效能函式庫,負責 C# 物件與 JSON 字串之間的轉換。
- 序列化 (Serialization):將 C# 物件轉換為 JSON 字串。使用 JsonSerializer.Serialize() 方法。
- 反序列化 (Deserialization):將 JSON 字串轉換回 C# 物件。使用 JsonSerializer.Deserialize<T>() 方法。
在 ASP.NET Core 中,這個過程通常是自動且透明的。當你在 API 端點返回一個 C# 物件時,框架會自動使用 System.Text.Json 將其序列化為 JSON 響應。同樣地,當請求的 Body 包含 Content-Type: application/json 的資料時,框架會自動將其反序列化並綁定到標記了 [FromBody] 的方法參數上。
自訂序列化行為
可以透過 JsonSerializerOptions 來精細控制序列化過程,例如:
- 命名策略:將 C# 的 PascalCase 屬性名轉換為 JavaScript 慣用的 camelCase。
- 忽略特定屬性:在序列化時排除某些屬性。
- 處理 Null 值:選擇是否忽略值為 null 的屬性。
var product = new Product { Id = 1, Name = “滑鼠” };
string jsonString = JsonSerializer.Serialize(product);
// 輸出: {“Id”:1,”Name”:”滑鼠”,”Price”:0}
// 使用方法: JsonSerializer.Serialize()
string receivedJson = “{\”id\”:2,\”name\”:\”鍵盤\”,\”price\”:129.99}”;
# 反序列化時預設不區分大小寫,所以 JSON 中的 “id” 能對應到 C# 的 “Id”
Product deserializedProduct = JsonSerializer.Deserialize<Product>(receivedJson);
- 使用方法: JsonSerializer.Deserialize<T>()
Console.WriteLine(deserializedProduct.Name); // 輸出: 鍵盤
Console.WriteLine(deserializedProduct.Price); // 輸出: 129.99
這是一款極其輕便且強大的工具,讓開發者可以直接在 VS Code 編輯器中編寫和發送 HTTP 請求來測試 API,無需切換到 Postman 等外部應用程式。
運作方式:透過建立一個 .http 或 .rest 檔案來定義 HTTP 請求,語法直觀易懂。
語法:
每個請求以請求行 (HTTP 方法 + URL) 開始。
可選的標頭 (如 Content-Type: application/json) 寫在下一行。
若有請求主體 (Body),空一行後再撰寫 JSON 或其他格式的內容。
使用 ### 分隔多個請求。
支援變數,可在檔案開頭定義 @baseUrl = http://localhost:5000,然後在請求中使用 {{baseUrl}}/api/todos,方便切換環境。
使用:插件會在每個請求上方顯示一個 “Send Request” 按鈕,點擊後即可發送請求,並在右側視窗以美觀的格式顯示響應狀態碼、標頭和主體。
範例 (requests.http):
@baseUrl = http://localhost:5000
“Send Request”
### 獲取所有待辦事項 GET
GET {{baseUrl}}/api/todos
Content-Type: application/json
“Send Request”
### 新增一個待辦事項 POST
POST {{baseUrl}}/api/todos
Content-Type: application/json
{
“title”: “撰寫報告”,
“isComplete”: false
}
OpenAPI 是一個與語言無關的 API 描述規範,而 Swagger 是一套實現該規範的工具組,兩者結合是現代 API 開發的標準實踐。
OpenAPI vs. Swagger:可以將 OpenAPI 想像成一份 API 的「藍圖」或「合約」(通常是 JSON 或 YAML 格式),而 Swagger UI 則是將這份藍圖視覺化、變成可互動網站的工具。
核心功能:
自動生成文件:透過在程式碼中添加的屬性(如 [HttpGet])和 XML 註解 (<summary>, <remarks>),Swashbuckle.AspNetCore 套件能自動生成精確的 OpenAPI 規範文件。
互動式 UI:提供一個網頁介面 (預設在 /swagger),將 OpenAPI 文件轉譯成美觀、可點擊、可直接線上測試的 API 文件網站。開發者和前端團隊無需任何額外工具,即可了解並測試 API 的所有端點、參數和響應模型。
程式碼產生:可根據 OpenAPI 規範,使用 Swagger Codegen 或其他工具自動生成多種語言的客戶端 SDK 程式碼,大幅簡化前端或客戶端應用的開發工作。
設定步驟:
安裝 NuGet 套件:dotnet add package Swashbuckle.AspNetCore
註冊服務 (Program.cs):
builder.Services.AddEndpointsApiExplorer(); // 探索 API 端點所需
builder.Services.AddSwaggerGen(); // 註冊 Swagger 生成器服務
啟用中介軟體 (Program.cs):
var app = builder.Build();
// 通常只在開發環境中啟用,避免在生產環境暴露 API 細節
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
// 產生 OpenAPI JSON 文件 (預設在 /swagger/v1/swagger.json)
app.UseSwaggerUI(); // 啟用互動式 UI 介面
}
訪問:啟動應用程式後,訪問 /swagger 即可看到自動生成的 API 文件頁面。