ASP.NET Core 的 Model Binding 基本上和 ASP.NET Framework 差不多,但實際接觸後,一開始用起來卻卡卡的XD
本文將紀錄一些當初以為理所當然,結果卻不是這麼一回事的狀況。
先來科普一下~
最基本的有三個傳入來源
1. Form
2. Route
3. Query
可以三個都傳入,但是有其優先序Form > Route > Query
可以看到結果:
透過 Form 傳入 MyId1 = 1
透過 Query 參數傳入 MyId1 = 3
, MyId2 = 4
因為優先序的關係,MyId1 為 Form 傳入的 1 ,而 MyId2 因為 Form 沒有傳入,所以是吃到 Query 的 4 。
除了預設的三個來源,其餘皆需設定 Attribute 來指定接收來源。
但要注意, 一經指定,資料就只能透過指定的方式傳入 。也就是說,如果指定的 Attribute 本來就屬於預設的來源,那傳入 Attribute 指定的來源以外,也無法自動 Binding 上去。
[FromHeader]
[FromForm]
[FromRoute]
[FromQuery]
[FromBody]
加入的方式可以
1. 直接在 Action 的參數前面指定
2. 複雜型別可以到 Model 中的成員加上
以上面的例子來說,在 MyId2 指定了 [FromQuery]
,那就算在優先序高的 Form 中傳入 MyId2 = 2
,也不會被影響到,而是吃 Query 傳入的 MyId2 = 4
。
Q: 但如果 Query 沒有傳入 MyId2,而是只有 Form 傳入呢?
A: 如果在沒有指定 Attribute 的情況下,會吃到 Form 傳入的,但如果指定了
[FromQuery]
,就算 Form 有傳入,也吃不到。
下圖可以看到 MyId2 沒吃到,所以回傳 int 預設的 0。
實際開發時,最頭痛的資料來源就屬 Json 了。
為什麼頭痛,先來看看下面這個例子...
若有兩個 Model A、B
public class A { public string NameA { get; set; } } public class B { public string NameB { get; set; } }
以 ASP.NET Framework 來說,不管 Ajax ContentType 是 Form 還是 Json,都能夠這樣直接收
public IActionResult GetSomething(A a, B b)
但如果是 ASP.NET Core,要接收 Json 需要指定 [FromBody]
,否則收不進來
public IActionResult GetSomething([FromBody] A a)
而且 [FromBody]
只能指定到一個參數上面,所以就算這樣寫,也只會把 Value 都嘗試 Bind 到 A 上
public IActionResult GetSomething([FromBody] A a, [FromBody] B b)
簡單型別透過 JSON 也只能帶一個上來
public IActionResult GetSomething([FromBody] string strA, [FromBody] string strB)
這樣寫一樣綁不上去
$.ajax({ contentType: "application/json; charset=utf-8", dataType: "json", data: JSON.stringify({ strA: "Bob", strB: "Alice" }) })
你以為這樣就綁得上去了嗎?
$.ajax({ contentType: "application/json; charset=utf-8", dataType: "json", data: JSON.stringify{ strA: "Bob" } })
因為不符合直接傳 string,所以名字一樣也收不了
要直接傳一個 string 才收的到
$.ajax({ contentType: "application/json; charset=utf-8", dataType: "json", data: JSON.stringify("Bob") })
夠難受吧,如果是多個簡單型別,別用 Json 了,直接發 Form 上來。
那如果前端指定 Json,要傳多個參數上來,該怎麼辦呢?
{ str: "HiHi", a: { NameA: "Bob" }, b: { NameB: "Alice" } }
把 Action 的 Model 整理成與 Request Data 一致。
維持使用
[FromBody]
收 JSON 的話,就要把接收的 model 調整成與 Ajax 送上來的格式一致。
public class C { public string str { get; set; } public A a { get; set; } public B b { get; set; } } public IActionResult GetSomething([FromBody] C c)
接收 Json 只能有一個 [FromBody]
的限制,但 Form 沒有,所以在前端可以改 ContentType、而且你真的懶得再抽一個 Model 時,可以選擇不玩了,直接收 Form 就好XD
改用
x-www-form-urlencoded
可收多個參數(可以不用加
[FromForm]
)。
public IActionResult GetSomething(string str, A a, B b)
$.ajax({ contentType: "application/x-www-form-urlencoded; charset=utf-8", dataType: "json", data: { str: "HiHi", NameA: "Bob", NameB: "Alice" } })
Data 的 Key 會自動去 Mapping 可能的 Model 欄位。
像是傳入 NameA,則會自動綁到 Model A 的 NameA。
接收多個參數
的問題,如果透過解法二
處理,直接改接收 Form,那麼馬上隨之而來會有一個問題。
「平常印象中 Form 就是簡單的 key-value,沒有辦法帶 Object 格式」
先來看看這個,如果兩個 Model 的 Name 一樣...
public IActionResult GetSomething(A a, B b)
public class A { public string Name { get; set; } } public class B { public string Name { get; set; } }
如果是傳 Json,可能就會很自然的直接寫
data: { "a": { "Name": "AAA" }, "b": { "Name": "BBB" } }
但 Form 該怎麼帶成上面那樣有階層式的阿...
如果直接傳兩個 Name 上去,結果會將第一個抓到的 Name 綁到 A 和 B 的 Name 上。
Q: Form 表單的資料格式看起來都是 key = value,該怎麼帶多個同名 key 呢?
A: 答案是可以這樣寫:
$.ajax({ contentType: "application/x-www-form-urlencoded; charset=utf-8", dataType: "json", data: { a[Name]: "AAA", b[Name]: "BBB" } })
甚至資料是這樣子呢?
{ str: "myStr", a: { NameA: "Bob", AModel: { Address: "AAAAdress" } }, b: { NameB: "Alice", BModel: { Address: "BBBAdress" } } }
Q: Form 表單的資料格式看起來都是 key = value,該怎麼帶 value 是 object 的資料呢?
A: 答案是可以這樣寫:
$.ajax({ contentType: "application/x-www-form-urlencoded; charset=utf-8", dataType: "json", data: { a[NameA]: "Bob", a[AModel][Address]: "AAAAddress", b[NameB]: "Alice", b[BModel][Address]: "BBBAddress", str: "myStr" } })
或是這樣寫(能夠自動匹配的直接傳,同名不能的就指定參數)
$.ajax({ contentType: "application/x-www-form-urlencoded; charset=utf-8", dataType: "json", data: { NameA: "Bob", AModel[Address]: "AAAAddress", NameB: "Alice", BModel[Address]: "BBBAddress", str: "myStr" } })
其實 jQuery 在發 Ajax(x-www-form-urlencoded
) 的時候,就會自動把參數組合成上面那樣,你什麼都不用做。
var myData = { str: "myStr", a: { NameA: "Bob", AModel: { Address: "AAAAdress" } }, b: { NameB: "Alice", BModel: { Address: "BBBAdress" } } } $.ajax({ contentType: "application/x-www-form-urlencoded; charset=utf-8", dataType: "json", data: myData })
送上去的 Form Data 也會是上面範例中的型式
後端也能正確解析
補充:如果在送出前就想要拿到這種格式,可以呼叫 param
函數,效果是一樣的。
$.param(myData)
使用了無數次 Ajax,卻不曾停下來看他一眼,趁這次也更清楚 Form 與 Json 的用法差別。
再來是 ASP.Net Core Model Binding 的資料來源
1. 一個 Action 只能有一個參數掛 [FromBody]
2. 簡單型別盡量不要用 Json
3. 多個複雜型別,要再向外抽一個 Model,讓他的格式完全符合前端送上來的 Json 格式
1. 不會受限於只能有一個參數接收 Form 來源
2. 多個複雜型別懶得再抽一個 Model,可以考慮用一下
以上資訊如有錯誤歡迎交流補充~
下一篇會再提到 ASP.NET Core 接收 Json 還有一些討厭的狀況會讓 Model 綁不上去。