iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Software Development

程式設計沒有告訴你的事:30 天破解每一個 Why系列 第 22

Handler 的參數到底從哪裡來?Framework 怎麼做 Parameter Binding?

  • 分享至 

  • xImage
  •  

昨天研究 Reflection 時,我們已經知道:

Framework 可以在 Runtime 查看 Handler 的結構。

例如:

UpdateUser(int id, User user)

Framework 可以透過 Reflection 發現:

第一個參數叫 id,Type 是 int。

第二個參數叫 user,Type 是 User。

但這時又出現下一個問題。

知道 Handler 需要什麼參數,還不代表知道這些資料到底要去哪裡找。

例如:

int id

到底應該從網址 Path 裡取得?

還是 Query String?

User user

又應該從 Request Body 取得嗎?

如果還有:

string token

它可能又要從 Header 裡面找。

所以今天想理解的問題就是:

Framework 到底怎麼知道 Handler 的每一個參數,應該從 Request 的哪個地方取得?

這就是 Parameter Binding 開始真正發揮作用的地方。

💭 我原本以為……

以前看到:

GetUser(int id)

會覺得 Framework 自然就知道 id 是什麼。

例如網址:

/users/10

Framework 就會把:

10

放進:

id

看起來很理所當然。

但仔細想就會發現:

Request 裡可能同時存在很多地方都有資料。

例如:

Path:
/users/10

Query:
?includeOrders=true

Headers:
Authorization: ...

Body:
{"name":"Andy"}

那 Handler 如果長這樣:

UpdateUser(int id, bool includeOrders, User user)

Framework 必須知道:

id → Route

includeOrders → Query

user → Body

它不可能只是隨便找一個值塞進去。

所以這中間一定需要某種規則。

什麼是 Parameter Binding?

Parameter Binding 可以先理解成:

把 Request 中的資料,轉換並放進 Handler 所需要的參數。

例如 Request:

PUT /users/10?notify=true

Body:

{"name":"Andy"}

Handler:

UpdateUser(int id, bool notify, User user)

Framework 最後需要完成:

10

int id

true

bool notify

JSON

User user

最後才能真正呼叫:

UpdateUser(10, true, user)

所以整個 Binding 流程其實包含幾件事:

知道 Handler 有哪些參數

找到每個參數的資料來源

取得原始值

把原始值轉成正確 Type

建立最後的 Argument List

呼叫 Handler

Parameter 的資料來源有哪些?

目前我們前幾天已經研究過幾種常見資料來源。

Route

例如:

/users/10

如果 Route Pattern 是:

/users/{id}

那:

id = 10

Query

例如:

/search?keyword=book&page=2

可以取得:

keyword = book

page = 2

Header

例如:

Authorization: Bearer ...

可能會成為某個 Handler 所需要的資訊。

Body

例如:

{"name":"Andy","age":20}

可能被反序列化成:

User

所以可以整理成:

HttpRequest
├── Route Values
├── Query
├── Headers
└── Body

而 Parameter Binding 就是在這些地方找資料。

Route Binding 怎麼想?

假設 Route 是:

/users/{id}

實際 Request:

/users/10

經過 Routing 後,可以得到:

id = "10"

注意這時很可能還是一個字串。

但是 Handler 需要:

int id

所以 Framework 還要做:

"10"

Type Conversion

10

最後才能把 int 10 傳進 Handler。

這裡第一次看到:

Binding 不只是「找到資料」。

還包含:

把資料轉成正確 Type。

Query Binding 也是一樣

Request:

/products?page=2

解析 Query 後:

page = "2"

但 Handler:

GetProducts(int page)

需要的是 int。

所以還要:

"2"

Convert

2

如果是:

?active=true

Handler:

bool active

就需要:

"true"

Convert

true

所以 Query Parser 負責:

把 Query 拆成 Key / Value。

Parameter Binder 則進一步負責:

把 Value 轉成 Handler 真正需要的 Type。

Body Binding 又不太一樣

Body 可能不是簡單的一個:

"10"

而是一整份 JSON。

例如:

{"name":"Andy","age":20}

Handler:

CreateUser(User user)

這時 Binding 就會需要:

Request Body

Content-Type = application/json

JSON Deserialization

User

傳給 user

所以前一天學到的 JSON Deserialization,其實可以看成 Parameter Binding 裡的一種資料轉換方式。

簡單 Type:

"10"

int

複雜 Type:

JSON

User Object

Framework 到底怎麼判斷來源?

這裡就開始有很多不同的 Framework 設計。

最簡單的版本,可以使用「慣例」。

例如:

簡單型別先找 Route 或 Query。

複雜 Model 嘗試從 Body。

但這種方式很快就會遇到模糊情況。

例如:

Handler:

Search(string id)

Request 同時存在:

Route id = 10

Query id = 20

那 Framework 到底應該使用哪一個?

這時就需要更明確的規則。

Attribute 為什麼會出現?

ASP.NET Core 裡常常可以看到:

[FromRoute]

[FromQuery]

[FromBody]

[FromHeader]

以前看到這些 Attribute 時,可能只覺得:

這是在告訴 Framework 資料從哪裡來。

現在終於比較能理解它為什麼存在。

例如概念上:

[FromRoute] int id

就是明確告訴 Framework:

這個 id 不要亂猜。

請從 Route Value 取得。

而:

[FromQuery] string keyword

代表:

請從 Query 取得。

[FromBody] User user

則代表:

請從 Body 建立 User。

所以 Attribute 可以看成:

開發者提供給 Framework 的額外 Metadata。

Reflection 再次接回來了

昨天知道 Reflection 可以看到:

Parameter Name

Parameter Type

今天又可以再往前一步:

Reflection 還可以看到 Parameter 上面的 Attribute。

例如:

Parameter:
id

Type:
int

Attribute:
FromRoute

Framework 讀到這些 Metadata 後,就能決定:

資料來源 = Route

所以流程變成:

Handler Parameter

Reflection

Name
Type
Attributes

決定 Binding Source

從 HttpRequest 找資料

Convert

Argument

這就是 Reflection 和 Parameter Binding 開始真正接起來的地方。

如果沒有 Attribute 呢?

Framework 也不可能要求每一個參數全部標 Attribute。

否則寫一個簡單 Handler 會變得很麻煩。

所以真正 Framework 通常會有預設規則。

例如某些情況下可以根據:

Parameter Type

Parameter Name

Route Pattern

是否為 Complex Type

已註冊服務

其他 Metadata

推斷參數來源。

所以 Binding 可以有兩種感覺:

Explicit Binding

開發者明確告訴 Framework:

從哪裡拿。

Implicit Binding

Framework 根據既定規則推斷。

對 Mini Web Framework 來說,目前不用一開始就做到這麼複雜。

我們可以先從最簡單的規則開始。

例如:

如果 Route Values 有同名 Key
→ 從 Route

否則如果 Query 有同名 Key
→ 從 Query

如果 Parameter 是 Complex Type
→ 從 JSON Body

這樣就已經可以理解基本原理。

Type Conversion 為什麼也是 Framework 的工作?

Request 裡的 Route 和 Query 很多時候原始形式都是文字。

例如:

id = "10"

page = "2"

active = "true"

但是 Handler 可能希望:

int id

int page

bool active

Framework 如果直接把 string 傳進 int Parameter,當然不行。

所以 Binding 還需要一層:

Raw Value

Target Type

Conversion

例如:

"10" + typeof(int)

10

"true" + typeof(bool)

true

這也代表 Framework 必須知道:

Parameter 的 Type 是什麼。

又再次需要 Reflection。

如果轉換失敗怎麼辦?

例如:

Request:

/users/abc

但 Handler 是:

GetUser(int id)

Framework 拿到:

"abc"

然後想轉成 int。

當然會失敗。

這時 Framework 不能假裝:

id = 0

然後繼續執行。

比較合理的做法是:

Binding Failed

產生錯誤結果

不要正常執行 Handler

真正 Framework 可能會產生:

400 Bad Request

或加入 Model State Error 等資訊。

這也讓我發現:

Parameter Binding 不只是在「方便開發者」。

它其實也是 Request Validation 的第一道關卡之一。

資料格式不符合 Handler 的要求時,就應該在進入商業邏輯前被發現。

Parameter Binding 的完整流程

做到這裡,可以把今天的概念整理成:

HTTP Request

HttpRequest

Routing

找到 Handler

Reflection

取得 Parameters

對每個 Parameter:

Parameter Name
Parameter Type
Parameter Attribute

決定 Data Source

Route / Query / Header / Body

取得 Raw Value

Type Conversion / Deserialization

Argument

全部完成後:

Arguments[]

Invoke Handler

所以 Framework 真正呼叫 Handler 之前,其實可能已經做了大量工作。

官方怎麼說?

ASP.NET Core 的 Model Binding 會從 HTTP Request 的不同來源取得資料,並把它轉換成 Action Parameter 或 Model 所需要的 .NET Type。

常見資料來源包含:

Form Fields

Request Body

Route Data

Query String

Uploaded Files

另外也可以透過 Attribute 明確指定 Binding Source。

例如:

FromRoute

FromQuery

FromBody

FromHeader

所以今天推導出的概念,其實和真正 Framework 的設計方向一致。

Framework 會先理解 Handler 的需求,再把 Request Data 轉換成適合的參數。


以前的我:

Handler 寫:

GetUser(int id)

Framework 就會自動給 id。

現在的我:

Handler

Reflection

Parameter:
Name = id
Type = int

Parameter Binding

決定資料來源

Route / Query / Header / Body

找到 Raw Value

Convert to int

Argument

Invoke Handler

所以:

「Framework 自動給我參數」

其實背後是一套完整的 Binding Pipeline。


今天最大的發現是:

Framework 不只是幫 Request Parsing。

它還必須理解「HTTP 世界的資料」和「C# 世界的參數」之間怎麼對應。

HTTP 世界可能只有:

"10"

"true"

JSON Text

Header String

但 C# Handler 希望的是:

int

bool

User

其他 Object

Framework 就站在中間:

HTTP Data

Binding

C# Values

這又再次看到 Framework 最核心的角色:

做兩個世界之間的轉換。


Framework 會先透過 Reflection 查看 Handler 需要哪些 Parameter。

接著依照 Attribute 或既定 Convention,決定每個 Parameter 的資料來源。

再從 Request 的 Route、Query、Headers 或 Body 中取得資料。

最後經過 Type Conversion 或 Deserialization,建立真正的 Arguments,再呼叫 Handler。

🛠 與 Mini Web Framework 的關聯

目前 Mini Web Framework 的完整方向已經越來越清楚:

Browser

TCP

HttpRequest

Routing

找到 Handler

Reflection

Parameter Binding
├── Route
├── Query
├── Headers
└── Body

Arguments

Handler

HttpResponse

Browser

以前 Route Table 只是:

Path

Func

但如果加入 Parameter Binding,Handler 就有機會從固定的:

沒有參數

進化成:

Handler(int id)

Handler(string keyword)

Handler(User user)

甚至:

Handler(int id, User user)

這會讓 Mini Web Framework 的使用方式開始真正接近一般 Web Framework。

今天暫時不實作

今天依然先把概念理解清楚。

目前前面累積的 Request、Headers、Query、Body、JSON 等實作還需要一起補齊。

之後正式做 Parameter Binding 時,可以先從最簡單的 Query 開始。

例如 Handler:

Search(int page)

Request:

/search?page=2

Framework 先用 Reflection 看出:

Parameter Name = page

Parameter Type = int

再取得:

Query["page"] = "2"

最後把:

"2"

轉成:

2

並呼叫 Handler。

先讓最簡單的 Binding 跑通,再慢慢加入 Route、Body 和 Attribute。

結尾

前一天研究 Reflection 時,我開始知道 Framework 可以「看懂 Handler 長什麼樣」。

但今天才發現:

看懂 Handler 只是第一步。

Framework 真正要做到的是:

知道 Handler 需要什麼。

找到 Request 裡對應的資料。

把資料轉成正確 Type。

最後放進正確的 Parameter。

所以一個看似很簡單的:

GetUser(int id)

背後可能經過:

Reflection

Parameter Metadata

Route Matching

Raw Value = "10"

Type Conversion

int 10

Invoke Handler

這也是為什麼平常 Framework 使用起來會這麼方便。

複雜度沒有消失。

只是被移到了 Parameter Binding Pipeline 裡。


現在 Framework 已經有可能:

找到 Handler

準備 Parameter

執行 Handler

但是目前我們一直假設 Handler 最後會產生:

HttpResponse

真正 Framework 裡,Handler 可能回傳的東西其實很多。

例如:

string

User

List

JSON Result

Status Code

甚至什麼都不回傳。

那 Framework 又是怎麼把這些完全不同的回傳值,最後統一變成 HTTP Response?


上一篇
Framework 怎麼知道 Handler 需要什麼參數?Reflection 又是怎麼出現的?
下一篇
Handler 回傳的東西都不一樣,Framework 怎麼把它們變成 HTTP Response?
系列文
程式設計沒有告訴你的事:30 天破解每一個 Why24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言