模型綁定ASP.NET Core中的JSON POST(你的.net core 爲什麼無法接收JSON參數)

有一天,當我出現一個關於模型綁定的問題時,我正在追趕最新的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的單獨ApiControllerController類(以及所有相關的命名空間混淆)。在ASP.NET Core中,管道都已合併,只有單個Controller類。

當您的GET請求正常工作時,您知道您的大部分管道(例如路由)可能已正確配置。您甚至可以提交一個測試表單,該表單將一個發送POST到控制器並接收它發回的JSON值。一切都很好看。

將x-www-url-formencoded內容發佈到ASP.NET Core控制器

作爲拼圖的最後一部分,你測試發送一個POST帶有JSON數據的AJAX ,它們都崩潰了 - 你收到了一個200 OK,但你對象上的所有屬性都是空的。但爲什麼?

將JSON內容發佈到ASP.NET Core控制器

什麼是模型綁定?

在我們詳細瞭解這裏發生的事情之前,我們需要對模型綁定有一個基本的瞭解。模型綁定是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對象並簡單地返回該對象,如我在上一節中所示。

在一個簡單的網頁上,我們然後製作POSTs(爲方便起見使用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"}

發送這兩個POSTs會引發以下控制檯響應:

ASP.NET 4控制器成功發佈的圖片

在這兩種情況下,控制器都綁定到HTTP請求的主體,並且我們發送的參數被返回給我們,而我們不必做任何聲明性的操作。模型粘合劑爲我們做了所有的魔術。請注意,雖然我一直在使用WebApi控制器,但MVC控制器模型綁定器在此示例中的行爲相同,並且將綁定兩個POSTs。

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不是。

x-www-url-formencoded帖子綁定正確,但JSON帖子不正確

爲了在ASP.NET Core中正確綁定JSON,您必須修改操作以[FromBody]在參數中包含該屬性。這告訴框架使用content-type請求的標頭來決定使用哪個配置的IInputFormatters進行模型綁定。

默認情況下,當你打電話AddMvc()Startup.cs,一個JSON格式JsonInputFormatter自動配置,但如果需要,你可以添加額外的格式化,例如XML綁定到一個對象。

考慮到這一點,我們的新控制器如下所示:

public class PersonController : Controller
{
    [HttpPost]
    public IActionResult Index([FromBody] Person person)
    {
        return Json(person);   
    } 
}

而我們的JSON POST現在再次像魔術一樣!

JSON綁定與FromBody屬性一起正常工作

所以只包括[FromBody]?

因此,如果您認爲您可以隨時使用[FromBody]您的方法,請抓住您的馬匹。讓我們看看當您使用x-www-url-formencoded請求命中新端點時會發生什麼:

使用x-www-url-formencoded with FromBody屬性的不支持的媒體類型

噢親愛的。在這種情況下,我們特意告訴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://andrewlock.net/model-binding-json-posts-in-asp-net-core/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章