细说 Web API参数绑定和模型绑定
今天跟大家分享下在Asp.NET Web API中Controller是如何解析从客户端传递过来的数据,然后赋值给Controller的参数的,也就是参数绑定和模型绑定。
Web API参数绑定就是简单类型的绑定,比如:string,char,bool,int,uint,byte,sbyte,short,ushort,long, float这些基元类型。模型绑定就是除此之外的复杂类型的绑定。大家都知道在MVC中模型绑定都是通过默认的DefaultModelBinder来绑定的,没有Get请求和POST请之分。然而在Web API中参数和模型绑定的机制在Get请求和POST请求是不一样的。
一:参数绑定(简单类型绑定)
Web API参数绑定时,Action默认是从路由数据(url片段)和querystring中获取数据的。我们都知道,Get请求一个服务的时候,客户端是把数据放在URL中发送到服务器端的;而POST请求是把数据放到请求体(Request Body)发送到服务器端的。所以默认情况下在WebAPI中我们只能用GET请求去发送简单类型的数据到服务器端,然后Action再获取数据,举个栗子:
准备模型:
public class Number
{
public int A { get; set; }
public int B { get; set; }
public Operation Operation { get; set; }
}
public class Operation
{
public string Add{get;set;}
public string Sub { get; set; }
}
配置路由:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/norestful/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
WebAPI Controller如下:
public class ValuesController : ApiController
{
[HttpGet, HttpPost]
public int SubNumber(int a,int b)
{
return a - b;
}
}
客户端ajax调用如下:
function ajaxOp(url,type,data,contentType) {
$.ajax({
url: url,
type: type,
data: data,
contentType:contentType
success:function(result) {
alert(result);
}
});
}
function a() {
var data = { a: 1, b: 2 };
ajaxOp('/api/norestful/Values/SubNumber', 'POST',data);
}
输出结果如下:
如果换成POST请求,则找不到匹配的action
出现这种情况的原因主要是因为简单类型的绑定默认情况下是利用【FromUri】特性来解析数据的,光看名称就知道它只负责从URL中读取数据,在后面复杂类型绑定时会讲到【FromUri】的用法。
当然你要在POST请求下去绑定简单类型,也是可以的,有三种办法可以解决。
方法一:请求的数据以querystring的方式把数据放在URL中,POST空数据。
function a() {
var data = { a: 1, b: 2 };
ajaxOp('/api/norestful/Values/SubNumber?'+$.param(data), 'POST');
}
方法二:手动从请求中读取数据:
POST请求下:
[HttpPost]
public async Task<IHttpActionResult> AddNumbers()
{
if (Request.Content.IsFormData())
{
NameValueCollection values = await Request.Content.ReadAsFormDataAsync();
int a, b;
int.TryParse(values["a"], out a);
int.TryParse(values["b"], out b);
return Ok(a - b);
}
return StatusCode(HttpStatusCode.BadRequest);
}
此方法能解决问题,但是和模型绑定无关,ReadAsFormDataAsync是HttpRequestMessage类HttpContent属性的一个扩展方法,该方法负责解析请求头中content-type类型为application/x-www-form-urlencoded类型中的数据。
Get请求下:
[HttpGet]
public IHttpActionResult SubNumberByGet()
{
Dictionary<string, string> dic = Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int a, b;
int.TryParse(dic["a"], out a);
int.TryParse(dic["b"], out b);
return Ok(a-b);
}
方法三:利用【FromBody】特性,此特性将从请求体(Request body)中获取数据。但是【FromBody】特性只能用于Action中的一个参数,如果这样写:
[HttpGet, HttpPost]
public int SubNumber([FromBody]int a,[FromBody]int b)
{
return a - b;
}
将会抛出无法将多个参数绑定到请求的异常:
所以在POST请求下绑定一个简单类型利用【FromBody】特性还是可以的,也是最常用的解决方案。
之所以应用【FromBody】特性绑定多个简单类型抛出异常的原因就是在Web API框架下,请求体(request body)中的数据会以stream流的方式发送到服务器端,而我们没办法在模型绑定系统读取stream流中数据后再去改变它;而不像在MVC框架下在请求开始之前请求体(RequestBody)中的数据被处理后以键值对的方式存储在内存当中,所以MVC Controller中绑定多个复杂类型是没有问题的。同样在Web API框架下默认一个Action也不能同时绑定多个复杂类型,这点后面会讲到,同时也会提供同时绑定多个复杂类型的相关解决方案。
在Web API框架下,参数绑定(简单类型绑定)读取数据有三种不同的方式:
1:Web API首先检测参数是否应用了【FromBody】特性,如果有,就从该特性直接从请求体中读取数据。
2:如果没有【FromBody】特性,Web API就从绑定规则中读取数据,绑定规则通过在WebApiConfig文件中设置HttpConfiguration的ParameterBindingRules属性来实现,比如:config.ParameterBindingRules.Insert(0, typeof(Number), o => o.BindWithAttribute(new FromUriAttribute())),指的就是在项目中Number类型默认都是通过【FromUri】特性来获取数据,这样不必显示提供【FromUri】特性了。
3:如果没有【FromBody】特性,没有绑定规则,则通过【FroUri】特性读取数据,这也是参数绑定的默认行为。
二:模型绑定(复杂类型绑定)
Web API复杂类型绑定时候,Action默认从请求体(request body)中获取数据,所以默认只能用POST请求去发送复杂类型到服务器端,举个栗子:
Action如下:
[HttpGet,HttpPost]
public int AddNumber(Number number)
{
return number.A + number.B;
}
客户端ajax如下:
function b() {
var data = { a: 1, b: 2};
ajaxOp('/api/norestful/Values/AddNumber', 'POST', data);
}
Action在默认POST请求下,只能绑定一个复杂类型,如果绑定多个复杂类型,将会抛出异常,原因前面已经提到过。如果要绑定多个复杂类型,至少有四个办法可以解决。
方法一:在GET请求下,所有类型应用【FromUri】特性。
Action如下:
[HttpGet, HttpPost]
public int OpNumbers([FromUri]Number number,[FromUri] Operation op)
{
return op.Add ? number.A + number.B : number.A - number.B;
}
客户端ajax如下:
function d() {
var data = { a: 1, b: 2, add: true, sub: false }
ajaxOp('/api/norestful/Values/OpNumbers', 'GET', data);
}
方法二:手动从请求中读取数据,具体实现方法跟上面简单类型手动从请求中读取数据的方法是一样的,就不多讲了。
方法三:在GET请求下利用嵌套复杂类型绑定数据,并应用【FromUri】特性
Action如下:
[HttpGet]
public int OpNumbersByNestedClass([FromUri]Number number)
{
return number.Operation.Add ? number.A + number.B : number.A - number.B;
}
客户端ajax如下:
function b2() {
var data = { a: 1, b: 2, add: true, sub: false };
ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'GET', {
'number.a': data.a,
'number.b': data.b,
'number.operation.add': data.add,
'number.operation.sub': data.sub
});
}
方法四:POST请求下:
Action如下:
[HttpGet,HttpPost]
public int OpNumbersByNestedClass(Number number)
{
return number.Operation.Add ? number.A + number.B : number.A - number.B;
}
客户端ajax如下:
function b4() {
var data = { a: 1, b: 2, 'operation.add': true, 'operation.sub': false };
ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', data);
}
在POST请求下,复杂类型的属性必须加类型名称作为前缀,或者var data={a:1,b:2,operation:{add:true,sub:false}}这样声明,Action 参数才能获取到数据。
其实说了这么多,简单类型绑定和复杂类型绑定在本质上没什么太大的区别,真正的区别在于数据绑定是通过GET请求(简单类型的默认方式,复杂类型通过设置【FromUri】实现)还是POST请求( 复杂类型的默认方式,简单类型通过设置【Frombody】实现)实现的,说白了就是【FromUri】特性和【FromBody】特性之间的区别。现在就讲下这两个特性内部是如何找到数据的。
【FromUri】特性
应用【FromUri】特性,Web API Action中参数将从URL中解析数据,而数据解析是通过值提供程序工厂创建值提供程序来获取数据的,对于简单类型,值提供程序则获取Action参数名称和参数值;对于复杂类型,值提供程序则获取类型属性名称和属性值。
下面是Web API中值提供程序工厂类的代码:
namespace System.Web.Http.ValueProviders {
public abstract class ValueProviderFactory {
public abstract IValueProvider GetValueProvider(HttpActionContext context);
}
}
ValueProviderFactory通过HttpActionContext参数来选择创建什么样的提供程序来解析数据。
【FromBody】特性
应用【Frombody】特性,Web API Action中参数将从请求体(Request Body),并且通过媒体类型格式化器获取和绑定数据,在Web API框架下有4中内置的媒体格式化器,分别是:
1:JsonMediaTypeFormatter,对应的content-type是:application/json, text/json
2:XmlMediaTypeFormatter,对应的content-type是:XmlMediaTypeFormatter
3:FormUrlEncodedMediaTypeFormatter,对应的content-type是:对应的content-type是:application/x-www-form-urlencoded。
4:JQueryMvcFormUrlEncodedFormatter,对应的content-type是:对应的content-type是:application/x-www-form-urlencoded。
在默认情况下POST请求采用JQueryMvcFormUrlEncodedFormatter来解析数据的,JQueryMvcFormUrlEncodedFormatter类通过模型绑定系统利用值提供程序从URL中读取数据,这里的值提供程序是NameValuePairsValueProvider类,该类实现IValueProvider接口来获取键值对中的数据。
当然,你也可以在客户端请求时指定请求的content-type类型,这样Web API会根据客户端的content-type来选择不同的媒体类型格式化器。如果客户端采用application/json格式来传输数据,Web API在后台则会采用JsonMediaTypeFormatter来解析数据。举个栗子,上面最后一个例子更改客户端ajax请求,Controller不变:
function b4() {
var data = { a: 1, b: 2, operation: { add: true, sub: false } };
ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', JSON.stringify(data), 'application/json');
}
在下图我们可以看到数据请求格式。这种以Json数据格式传递到Action来处理模型绑定,相信在MVC中无处不在吧。
而默认采用JQueryMvcFormUrlEncodedFormatter,则content-type如下图所示:
好了,今天就到这里吧。
细说 Web API参数绑定和模型绑定的更多相关文章
- Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)
导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html. 本文主要来讲解以下内容: ...
- Parameter Binding in ASP.NET Web API(参数绑定)
Parameter Binding in ASP.NET Web API(参数绑定) 导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnbl ...
- Web API(四):Web API参数绑定
在这篇文章中,我们将学习Web API如何将HTTP请求数据绑定到一个操作方法的参数中. 操作方法在Web API控制器中可以有一个或多个不同类型的参数.它可以是基本数据类型或复杂类型.Web API ...
- .net core Web API参数绑定规则
参数推理绑定 先从一个问题说起,微信小程序按照WebAPI定义的参数传递,Get请求服务器端可以正常接收到参数,但是Post请求取不到. Web API代码(.netcore 3.1)如下: [Htt ...
- 一张图说明 Web Api 参数绑定默认规则
请求如下: 控制器如下: 慎重说明:不管请求方式是 get 还是 post , 简单类型的参数,如 name 和 id ,其值都是从 url 里面去取. Web API 从 url 还是 body 获 ...
- asp.net web api参数
翻译自:http://www.c-sharpcorner.com/article/parameter-binding-in-asp-net-web-api/ 主要自己学习下,说是翻译,主要是把文章的意 ...
- jQuery Ajax传递数组到asp.net web api参数为空
前端: var files = []; files.push({ FileName: "1.jgp", Extension: ".jgp", FileType: ...
- 细说Web API中的Blob
在一般的Web开发中,很少会用到Blob,但Blob可以满足一些场景下的特殊需求.Blob,Binary Large Object的缩写,代表二进制类型的大对象.Blob的概念在一些数据库中有使用到, ...
- 【转载】ASP.NET MVC Web API 学习笔记---第一个Web API程序
1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过 ...
随机推荐
- ECharts 之一——入门
一.简介 ECharts是一个来自百度的开源的javascript图标库.通过ECharts我们可以呈现出多种类型的图表.ECharts底层基于ZRender(一个全新的轻量级canvas类库),创建 ...
- java 接收 char字符型
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.Scanner; public clas ...
- jQuery form插件的使用--用 formData 参数校验表单,验证后提交(简单验证).
Form Plugin API 里提供了很多有用的方法可以让你轻松的处理表单里的数据和表单的提交过程. 测试环境:部署到Tomcat中的web项目. 一.引入依赖js <script src=& ...
- Mongodb 字段类型转换
db.diningmembers.find({modifedDate:{$type:9}}).forEach(function(x){x.tel = String(x.tel);db.diningme ...
- 一个fork的面试题
前两天有人问了个关于Unix的fork()系统调用的面试题,这个题正好是我大约十年前找工作时某公司问我的一个题,我觉得比较有趣,写篇文章与大家分享一下.这个题是这样的: 题目:请问下面的程序一共输出多 ...
- java 空指针异常(java.lang.NullPointerException)
在Java中对值为null的指针调用任何方法,就会引发空指针异常(java.lang.NullPointerException).空指针异常绝对 是Java中最难查找和调试的一种异常,你永远无法得到任 ...
- 苹果手机 微信调用百度地图Javascript API 频繁闪退问题
最近在网页中调用百度地图API js大众版,但是在IOS8系统中,缩放的时候频繁闪退,安卓手机没有这个问题! 在网上查询了下,有网友回答说不要频繁的去new marker,而是初始化话一定量的mark ...
- UVA 12730 Skyrk's Bar --期望问题
题意:有n个地方,现在要站人进去,而每两个人之间至少要隔k个空地,问这n个地方能站的人数的期望是多少. 分析:考虑dp[i]表示 i 个地方能站的期望数,从左往右推, 如果i-k-1<1,那么最 ...
- 【转】Python 列表排序
很多时候,我们需要对List进行排序,Python提供了两个方法 对给定的List L进行排序, 方法1.用List的成员函数sort进行排序 方法2.用built-in函数sorted进行排序(从2 ...
- JavaScript Promise API
同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的 ...