ASP.NET Core MVC/WebAPi 模型绑定探索
前言
相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法。本文我们来讲解.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]明确标识,否则你懂的。