有一天,當我出現一個關於模型綁定的問題時,我正在追趕最新的ASP.NET社區Standup,這個問題我之前沒有接過(你可以在46:30左右看到這個問題)。它指出在ASP.NET Core(ASP.NET 5 的新名稱)中,您不能再簡單地將JSON數據發佈到MVC控制器並自動綁定它,您以前可以在ASP.NET 4 / MVC中執行此操作5。
在這篇文章中,我將展示如果您將項目轉換爲ASP.NET Core並且發現您的JSON POST
不起作用該怎麼辦。我將演示MVC 5模型綁定和MVC Core模型綁定之間的區別,突出顯示兩者之間的差異,以及如何根據您期望的數據爲您的項目設置控制器。
TL; DR:將
[FromBody]
屬性添加到ASP.NET Core控制器操作中的參數注意,如果您使用的是ASP.NET Core 2.1,則還可以使用該[ApiController]
屬性自動推斷[FromBody]
複雜操作方法參數的綁定源。有關詳細信息,請參閱文檔
我的數據在哪裏?
想象一下,您已經創建了一個閃亮的新ASP.NET核心項目,您正在使用它來重寫現有的ASP.NET 4應用程序(當然只是出於明智的原因!)您將舊的WebApi控制器複製並粘貼到.NET Core Controller中,清理命名空間,測試GET
操作,一切似乎都運行良好。
注意:在ASP.NET 4中,儘管MVC和WebApi管道的行爲非常相似,但它們完全是分開的。因此,您分別擁有WebApi和Mvc的單獨
ApiController
和Controller
類(以及所有相關的命名空間混淆)。在ASP.NET Core中,管道都已合併,只有單個Controller
類。
當您的GET
請求正常工作時,您知道您的大部分管道(例如路由)可能已正確配置。您甚至可以提交一個測試表單,該表單將一個發送POST
到控制器並接收它發回的JSON值。一切都很好看。
作爲拼圖的最後一部分,你測試發送一個POST
帶有JSON數據的AJAX ,它們都崩潰了 - 你收到了一個200 OK
,但你對象上的所有屬性都是空的。但爲什麼?
什麼是模型綁定?
在我們詳細瞭解這裏發生的事情之前,我們需要對模型綁定有一個基本的瞭解。模型綁定是MVC或WebApi管道獲取原始HTTP請求並將其轉換爲控制器上的操作方法調用的參數的過程。
例如,考慮以下WebApi控制器和Person
類:
public class PersonController : ApiController
{
[HttpPost]
public Person Index(Person person)
{
return person;
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
我們可以看到控制器上有一個動作方法,一個POST
動作,它接受一個參數 - 一個Person
類的實例。然後控制器只是回顯該對象,然後回到響應中。
那麼Person
參數來自哪裏?模型綁定救援!模型綁定器可以在許多不同的位置查找數據以便水合人物對象。模型綁定器具有高度可擴展性,並允許自定義實現,但常見綁定包括:
- 路由值 - 導航到諸如
{controller}/{action}/{id}
允許綁定到id
參數的路由 - 查詢字符串 - 如果您已將變量作爲查詢字符串參數傳遞,例如
?FirstName=Andrew
,則FirstName
可以綁定參數。 - 正文 - 如果您在帖子的正文中發送數據,則可以將其綁定到Person對象
- 標頭 - 您也可以綁定到HTTP標頭值,儘管這種情況不太常見。
因此,您可以看到有多種方法可以將數據發送到服務器,並讓模型綁定器自動爲您創建正確的方法參數。有些需要explcit配置,而有些則需要免費獲得。例如,路徑值和查詢字符串參數始終是綁定的,對於複雜類型(即不是像string
或的基元int
),主體也是綁定的。
重要的是要注意,如果模型綁定器由於某種原因未能綁定參數,它們將不會拋出錯誤,而是您將收到一個默認對象,沒有設置任何屬性,這是我們之前顯示的行爲。
它在ASP.NET 4中的工作原理
爲了解決這裏發生的事情,我創建了兩個項目,一個使用ASP.NET 4,另一個使用最新的ASP.NET Core(在編寫本文時非常接近RC2)。你可以在這裏和這裏的 GitHub上找到它們。
在ASP.NET WebApi項目中,有一個簡單的控制器,它接受一個Person
對象並簡單地返回該對象,如我在上一節中所示。
在一個簡單的網頁上,我們然後製作POST
s(爲方便起見使用jQuery),發送請求x-www-form-urlencoded
(正如您從普通表單中獲得的POST
)或作爲JSON。
//form encoded data
var dataType = 'application/x-www-form-urlencoded; charset=utf-8';
var data = $('form').serialize();
//JSON data
var dataType = 'application/json; charset=utf-8';
var data = {
FirstName: 'Andrew',
LastName: 'Lock',
Age: 31
}
console.log('Submitting form...');
$.ajax({
type: 'POST',
url: '/Person/Index',
dataType: 'json',
contentType: dataType,
data: data,
success: function(result) {
console.log('Data received: ');
console.log(result);
}
});
這將爲編碼POST
類似於(簡稱爲簡潔)的表單創建HTTP請求:
POST /api/Person/UnProtected HTTP/1.1
Host: localhost:5000
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
FirstName=Andrew&LastName=Lock&Age=31
併爲JSON帖子:
POST /api/Person/UnProtected HTTP/1.1
Host: localhost:5000
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json; charset=UTF-8
{"FirstName":"Andrew","LastName":"Lock","Age":"31"}
發送這兩個POST
s會引發以下控制檯響應:
在這兩種情況下,控制器都綁定到HTTP請求的主體,並且我們發送的參數被返回給我們,而我們不必做任何聲明性的操作。模型粘合劑爲我們做了所有的魔術。請注意,雖然我一直在使用WebApi控制器,但MVC控制器模型綁定器在此示例中的行爲相同,並且將綁定兩個POST
s。
ASP.NET Core中的新方法
因此,繼續使用ASP.NET Core,我們創建了一個類似的控制器,使用與Person
以前相同的類作爲參數:
public class PersonController : Controller
{
[HttpPost]
public IActionResult Index(Person person)
{
return Json(person);
}
}
使用與以前相同的HTTP請求,我們看到以下控制檯輸出,其中x-www-url-formencoded
POST
綁定正確,但JSON POST
不是。
爲了在ASP.NET Core中正確綁定JSON,您必須修改操作以[FromBody]
在參數中包含該屬性。這告訴框架使用content-type
請求的標頭來決定使用哪個配置的IInputFormatter
s進行模型綁定。
默認情況下,當你打電話AddMvc()
的Startup.cs
,一個JSON格式,JsonInputFormatter
被自動配置,但如果需要,你可以添加額外的格式化,例如XML綁定到一個對象。
考慮到這一點,我們的新控制器如下所示:
public class PersonController : Controller
{
[HttpPost]
public IActionResult Index([FromBody] Person person)
{
return Json(person);
}
}
而我們的JSON POST
現在再次像魔術一樣!
所以只包括[FromBody]?
因此,如果您認爲您可以隨時使用[FromBody]
您的方法,請抓住您的馬匹。讓我們看看當您使用x-www-url-formencoded
請求命中新端點時會發生什麼:
噢親愛的。在這種情況下,我們特意告訴ModelBinder綁定帖子的主體,即FirstName=Andrew&LastName=Lock&Age=31
使用IInputFormatter
。不幸的是,JSON格式化程序是我們唯一的格式化程序,並且與我們的內容類型不匹配,因此我們得到415
錯誤響應。
爲了專門綁定到表單參數,我們可以刪除FromBody
屬性或添加替代FromForm
屬性,這兩個屬性都允許我們的表單數據綁定,但同樣會再次阻止JSON綁定。
但是,如果我需要綁定兩種數據類型呢?
在某些情況下,您可能需要能夠將兩種類型的數據綁定到操作。在這種情況下,你有點卡住,因爲不可能讓相同的終點接收兩組不同的數據。
相反,您需要創建兩個不同的操作方法,這些方法可以專門綁定您需要發送的數據,然後將處理調用委託給一個公共方法:
public class PersonController : Controller
{
//This action at /Person/Index can bind form data
[HttpPost]
public IActionResult Index(Person person){
return DoSomething(person);
}
//This action at /Person/IndexFromBody can bind JSON
[HttpPost]
public IActionResult IndexFromBody([FromBody] Person person){
return DoSomething(person);
}
private IActionResult DoSomething(Person person){
// do something with the person here
// ...
return Json(person);
}
}
您可能會發現必須使用兩種不同的路徑才能實現基本相同的操作。不幸的是,路徑顯然在模型綁定發生之前映射到動作,因此模型綁定器不能用作鑑別器。如果您嘗試將上述兩個操作映射到同一路徑,則會收到錯誤消息Request matched multiple actions resulting in ambiguity
。有可能創建一個自定義路由來根據標頭值調用相應的操作,但很可能只會比它的價值更多的努力!
爲什麼要改變?
那爲什麼這一切都改變了?舊方式不簡單易行嗎?好吧,也許,但是有許多的陷阱需要提防的,尤其是當POST
荷蘭國際集團基本類型。
根據Damian Edwards在社區站立中的主要原因是出於安全原因,特別是跨站點請求僞造(CSRF)預防。我將在ASP.NET Core中稍後發佈關於反CSRF的帖子,但實質上,當模型綁定可以從多個不同的源發生時,就像在ASP.NET 4中那樣,默認情況下生成的堆棧不安全。我承認我沒有理解爲什麼現在還是如何被利用,但我認爲這與FormToken
你從多個來源獲取數據時識別你的反CSRF有關。
摘要
簡而言之,如果您的模型綁定無法正常工作,請確保它嘗試從請求的正確部分進行綁定,並且您已註冊了相應的格式化程序。如果它是你正在做的JSON綁定,那麼添加[FromBody]
你的參數就可以了!
參考
- https://docs.asp.net/en/latest/mvc/models/model-binding.html
- https://lbadri.wordpress.com/2014/11/23/web-api-model-binding-in-asp-net-mvc-6-asp-net-5/
原文鏈接:https://andrewlock.net/model-binding-json-posts-in-asp-net-core/