在昨天的文章中,學習到了 Routing 與 Controller 的運作原理。今天,我們要進一步探討 Web API 中非常核心的部分 —— Model 與 DTO(Data Transfer Object),以及 ASP.NET Core 如何透過 Model Binding 自動將用戶輸入的資料轉換成程式可用的物件。
在 ASP.NET Core 的世界中:
而這樣的設計可以避免資料過多暴露(防止 Overposting 攻擊),且可以清楚區分「資料儲存層」與「資料呈現層」,讓 API 的輸入輸出更容易維護與測試。
Model Binding 是 ASP.NET Core 自動幫我們把 HTTP 請求資料(例如表單、查詢字串、路由參數或 JSON Body)轉換成物件的過程。當我們宣告一個方法像這樣:
[HttpPost]
public IActionResult Create([FromBody] Student student)
{
...
}
ASP.NET Core 就會讀取請求的 JSON 內容,依照屬性名稱(Name Matching)對應 Student 類別,自動建立物件實例、驗證輸入是否合法,最後再將結果傳給 Create() 方法,也就是說,開發者不必手動解析 Request Body,框架會自動處理。
Model Binding 可以從以下幾個來源取得值:
來源 | 範例 | 用法 |
---|---|---|
Route | /students/10 |
[FromRoute] int id |
Query String | /students?id=10 |
[FromQuery] int id |
Form | POST 表單資料 |
[FromForm] |
Body (JSON/XML) | JSON 格式內容 | [FromBody] |
Header | HTTP 標頭 | [FromHeader] |
若沒有明確指定,框架會依據型別自動判斷,簡單型別(string、int、bool...)→ 查 Route、Query、Form。複合型別(class)→ 查 Body(例如 JSON)。
ASP.NET Core 提供多個屬性來控制綁定行為:
1️⃣ [Bind],只允許特定屬性參與綁定,例如:
[Bind("LastName,FirstName,HireDate")]
public class Instructor { ... }
或直接用在參數:
[HttpPost]
public IActionResult Create([Bind("Name,Age")] Student student)
適合「新增(Create)」場景,用於防止多餘欄位被綁定,但較不建議用在「編輯(Edit)」場景,因為未包含的屬性會變成 null。
2️⃣ [BindRequired],表示該屬性必須被綁定,否則會自動觸發 Model State 錯誤。
public class Instructor
{
[BindRequired]
public DateTime HireDate { get; set; }
}
3️⃣ [BindNever],防止某屬性被綁定,常用於保護 Id、CreatedDate 等系統欄位。
public class Instructor
{
[BindNever]
public int Id { get; set; }
}
4️⃣ [ModelBinder],自訂綁定邏輯或重新命名屬性。
[HttpPost]
public IActionResult Create(
[ModelBinder(Name = "instructor_id")] Instructor instructor)
或使用自訂的 binder:
[ModelBinder(typeof(MyCustomBinder))]
public MyType MyProperty { get; set; }
ASP.NET Core 支援自動綁定陣列與字典:
public IActionResult Post(int[] selectedCourses)
可接受以下輸入:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
selectedCourses[]=1050&selectedCourses[]=2000
同樣地,Dictionary 也能自動綁定:
public IActionResult Post(Dictionary<int, string> selectedCourses)
輸入:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
C# 9 的 Record 型別能讓 Model 更簡潔,ASP.NET Core 原生支援這類型的綁定與驗證。
public record Person(
[Required] string Name,
[Range(0, 150)] int Age,
[BindNever] int Id);
Record 必須有「單一公開建構式」,建構式參數名稱需與屬性名稱一致(區分大小寫),驗證屬性應寫在「建構式參數」上。
而路由與 Query String 會使用 不依文化(Invariant Culture) 的格式進行綁定,如果想改成依據使用者文化設定(例如日期、數字格式),可以自訂 IValueProviderFactory。
以下為一些特殊的資料型別與其說明:
型別 | 說明 |
---|---|
IFormFile / IEnumerable<IFormFile> |
上傳檔案 |
CancellationToken |
用於取消長時間執行的請求 |
FormCollection |
直接讀取整個表單資料 |
Model Binding 處理簡單資料,而 Input Formatter 處理整個 Request Body。ASP.NET Core 內建支援 JSON(System.Text.Json),也可加入 XML 支援。範例:
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.WriteIndented = true;
});
若要支援 XML:
builder.Services.AddControllers()
.AddXmlSerializerFormatters();
Model 與 DTO 要分開設計,降低耦合度、提升安全性,善用 [BindNever] 與 [BindRequired] 避免意外修改敏感欄位。若需自訂輸入格式,可擴充 Input Formatter 或 Model Binder,對於 Record Type 的驗證屬性,請記得放在建構式參數上。
Model Binding 是 ASP.NET Core Web API 的靈魂之一,它讓開發者不用手動解析輸入資料,就能安全、簡潔地獲得正確型別的物件,在實務專案中,搭配 DTO 設計與驗證屬性,能大幅提升 API 的可維護性與安全性。