前言
相信一直關注我的園友都知道,我寫的博文都沒有特別枯燥理論性的東西,主要是當每開啟一門新的技術之旅時,剛開始就直接去看底層實現原理,第一會感覺索然無味,第二也不明白到底為何要這樣做,所以只有當你用到了,你再去看理論性的文章時才會豁然開朗,這是我一直以來學習技術的方法。本文我們來講解.NET Core中的模型綁定。
話題
在ASP.NET Core之前MVC和Web APi被分開,也就說其請求管道是獨立的,而在ASP.NET Core中,WebAPi和MVC的請求管道被合並在一起,當我們建立控制器時此時只有一個Controller的基類而不再是Controller和APiController。所以才有本節的話題在模型綁定上呈現出有何不同呢?下面我們一起來看看。
ASP.NET MVC模型綁定
我們首先還是老規矩給出測試類
public class Person { public string Name { get; set; } public string Address { get; set; } public int Age { get; set; } }
接着POST請求通過Action方法進行模型綁定。
[HttpPost] public JsonResult PostPerson(Person p) { return Json(p); }
到這里,后台就大概over了,是不是就這么完了呢,我們一直在強調模型綁定這個詞語,那么到底什么是模型綁定呢,有必要解釋下。我們PostPerson這個方法中有一個Person的變量參數,那么問題來了,前台發出請求到這個方法從而該參數接收到傳遞過來的數據從而進行響應,這個p到底是怎么接收到的呢,恩,通過模型綁定唄,為了將數據聚合到對象或者其他簡單的參數可以通過模型綁定來查找數據,常見的綁定方式有如下四種。
路由值(Route Values):通過導航到路由如{controller}/{action}/{id}此時將綁定到id參數。
查詢字符串(QueryStrings):通過查詢字符串中的參數來綁定,如name=Jeffcky&id=1,此時name和id將進行綁定。
請求Body(Body):通過在POST請求中將數據傳入到Body中此時將綁定如上述Person對象中。
請求Header(Header):綁定數據到Http中的請求頭中,這種相對來說比較少見。
所以通過上述講述我們知道有多種方式將數據從客戶端傳遞到服務端,然后模型綁定會自動為我們創建正確的方法來綁定到后台參數中,簡單和復雜的類型參數都會進行綁定。
接下來我們來演示在ASP.NET MVC中綁定的方式。此時只需給出前台頁面了。
<form id="form"> <div class="form-group"> <div class="control-label col-md-2">名稱:</div> <div class="col-md-10"> <input name="Name" id="Name" class="form-control" /> </div> </div> <div class="form-group"> <div class="control-label col-md-2">年齡:</div> <div class="col-md-10"> <input name="Age" id="Age" class="form-control" /> </div> </div> <div class="form-group"> <div class="control-label col-md-2">家鄉地址:</div> <div class="col-md-10"> <input name="Address" id="Address" class="form-control" /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="button" id="btnForm" value="MVC提交表單" class="btn btn-success" /> <input type="button" id="btnJson" value="WebAPi提交" class="btn btn-warning" /> </div> </div> </form>
首先我們提交表單形式來傳輸數據。
$("#btnForm").on("click", function () { var dataform = $('form').serialize(); $.ajax({ url: "../MVC/PostPerson", contentType: "application/x-www-form-urlencoded;charset=utf-8", dataType: "json", type: "post", data: dataform, success: function (data) { console.log(data); } }); });
然后我們通過傳輸JSON的數據同樣來發出POST請求。
$("#btnForm").on("click", function () { $.ajax({ url: "../MVC/PostPerson", contentType: "application/json;charset=utf-8", dataType: "json", type: "post", data: JSON.stringify(datajson), success: function (data) { console.log(data); } }); });
結果同樣返回上述數據,就不再演示,下面我們看看WebAPi中的情況。
ASP.NET WebAPi模型綁定
當然上述利用JSON傳輸數據同樣也適用於WebAPi,下面我們來看看利用查詢字符串在WebAPi中的情況。
$("#btnJson").on("click", function () { var dataform = $('form').serialize(); console.log(dataform); $.ajax({ url: "../api/WebAPi/PostPerson", contentType: "application/x-www-form-urlencoded;charset=utf-8", dataType: "json", type: "post", data: dataform, success: function (data) { console.log(data); } }); });
我們再來看看在WebAPi中表單的形式。
$("#btnJson").on("click", function () { var datajson = { Name: "Jeffcky", Age: 24, Address: "湖南省" }; console.log(datajson); $.ajax({ url: "../api/WebAPi/PostPerson", contentType: "application/x-www-form-urlencoded;charset=utf-8", dataType: "json", type: "post", data: datajson, success: function (data) { console.log(data); } }); });
上述我們看到在ASP.NET MVC/WebAPi中無論是以表單POST的形式抑或JSON的形式控制器具有綁定都Http請求Body的能力同時數據都會返回給我們,我們不需要做出任何特別的說明,所以都沒毛病。接下來我們來看看ASP.NET Core MVC/WebAPi中的模型綁定形式。
ASP.NET Core MVC/WebAPi
在ASP.NET Core中為了加載服務器的靜態文件如css、js、文件等等記住需要在Startup.cs中的Configure方法下添加如下一句來啟用靜態文件:
app.UseStaticFiles();
由於在ASP.NET Core中MVC和WebAPi請求管道合並,所以只有Controller基類,我們在控制器下建立如下方法:
[Route("[controller]")] public class HomeController : Controller { [HttpGet("Index")] public IActionResult Index() { return View(); } [HttpPost("PostPerson")] public IActionResult PostPerson(Person p) { return Json(p); } }
此時加載頁面如下:
接下來我們分別演示以表單形式和JSON形式來發出POST請求。
$(function () { $("#btn").on("click", function () { var dataform = $('form').serialize(); console.log(dataform); $.ajax({ url: "../Home/PostPerson", contentType: "application/x-www-form-urlencoded;charset=utf-8", dataType: "json", type: "post", data: dataform, success: function (data) { console.log(data); } }); }); });
接下來我們再來看看傳輸JSON。
var datajson = { Name: "Jeffcky", Age: 24, Address: "湖南省" }; $(function () { $("#btn").on("click", function () { console.log(datajson); $.ajax({ url: "../Home/PostPerson", contentType: "application/json;charset=utf-8", dataType: "json", type: "post", data: JSON.stringify(datajson), success: function (data) { console.log(data); } }); }); });
此時就和ASP.NET MVC/WebAPi中情況就不一樣,此時后台接收不到數據,從而返回null。在ASP.NET Core中為了正確綁定到JSON我們需要在Action方法中對參數顯式指定[FromBody]。
[HttpPost("PostPerson")] public IActionResult PostPerson([FromBody]Person p) { return Json(p); }
通過使用[FromBody]則能正常使用了,那么到了這里你是不是就認為我們應該總是使用[FromBody]特性呢,如果你這樣想就大錯特錯了,我們將上述contentType修改成表單形式
contentType: "application/x-www-form-urlencoded;charset=utf-8",
此時會得到415不支持此媒體類型,當我們使用[FromBody]特性時,也就是明確告訴.NET Core要使用請求中的contentType頭來決定輸入參數對於模型綁定。默認情況下在我們注入MVC服務時被配置使用的時JsonInputFormatter,當然我們可以配置其他formatter比如xml,所以在這里我們將綁定到請求的Body中,但是輸入參數不對,因為其格式為Name=Jeffcky&Age=24&Address=湖南省,所以會出現不支持該媒體類型,在這里我們要么去除[FromBody]特性,要么添加[FromForm]特性。如果我們既需要綁定表單也需要綁定JSON該如何是好呢?我們只能寫兩個方法。如下:
[HttpPost("PostFormPerson")] public IActionResult PostFormPerson(Person p) { return Json(p); } [HttpPost("PostJsonPerson")] public IActionResult PostJsonPerson([FromBody] Person p) { return Json(p); }
現在看來想想是不是沒有之前ASP.NET MVC/WebAPi靈活和方便呢,微軟是不是閑的蛋疼啊,所以我們是不是覺得雖然是兩個方法我們將其路由定義成相同的,那么當我們在調用時讓其自己去匹配不就得了,於是乎就有了如下的情況。
[HttpPost("PostPerson")] public IActionResult PostFormPerson(Person p) { return Json(p); } [HttpPost("PostPerson")] public IActionResult PostJsonPerson([FromBody] Person p) { return Json(p); }
此時還沒到控制器下的路由方法就已經發生500錯誤了,如下:
好了看到這里我們本節的內容就已經接近尾聲了,是不是覺得微軟閑的沒事干了,明明一個方法就可以ok的事,非得要我們寫兩個方法,原因到底是什么呢,據了解社區是為了安全考慮,主要原因是為了防止CSRF(Cross-Site Rquest Forgery)究竟內部到底是怎么防止CSRF的呢,不得而知,難道像之前MVC中的For...那樣么,不得而知。
總結
本節我們比較詳細的討論了ASP.NET Core MVC/WebAPi中的模型綁定,如果在前台是JSON綁定,在ASP.NET Core MVC/WebAPi必須要用[FromBody]明確標識,否則你懂的。