细说 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如下图所示:

好了,今天就到这里吧。

 

时间: 2024-08-04 18:48:45

细说 Web API参数绑定和模型绑定的相关文章

asp.net web api参数

翻译自:http://www.c-sharpcorner.com/article/parameter-binding-in-asp-net-web-api/ 主要自己学习下,说是翻译,主要是把文章的意思记录下,下面进入正题 web api 对于一般的基本类型(primitive type)(bool,int ,double,log,timespan,datetime,guid,string)直接从url读取,对于复杂类型,web api从请求的body获取,需要使用media type. 对于这

web api 参数绑定

简单类型参数: url读取(string,bool,int...) 复杂类型参数:从message body用media-type formatter 读取 url读取route data(路由解析uri的时候得到)和URI query string 强制复杂类型从uri获取 public class GeoPoint { public double Latitude { get; set; } public double Longitude { get; set; } } public Val

ASP.NET MVC Web API 学习笔记---Web API概述及程序示例

1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过在浏览器中使用 JavaScript来创建更丰富的HTML体验.所以我相信Web API会越来越有它的用武之地. 说道Web API很多人都会想到Web服务,但是他们仍然有一定的区别:Web API服务是通过一般的 HTTP公开了,而不是通过更正式的服务合同 (如SOAP) 2. ASP.NET W

ASP.NET MVC Web API 学习笔记---第一个Web API程序

http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html 1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过在浏览器中使用 JavaScript来创建更丰富的HTML体验.所以我相信Web API会越来越有它的用武之地. 说道Web API很多人都会想到Web服务,但是他们仍然有

【转载】ASP.NET MVC Web API 学习笔记---第一个Web API程序

1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过在浏览器中使用 JavaScript来创建更丰富的HTML体验.所以我相信Web API会越来越有它的用武之地. 说道Web API很多人都会想到Web服务,但是他们仍然有一定的区别:Web API服务是通过一般的 HTTP公开了,而不是通过更正式的服务合同 (如SOAP)  2. ASP.NET

ASP.NET MVC Web API 学习笔记---第一个Web API程序---近来很多大型的平台都公开了Web API

1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过在浏览器中使用 JavaScript来创建更丰富的HTML体验.所以我相信Web API会越来越有它的用武之地. 说道Web API很多人都会想到Web服务,但是他们仍然有一定的区别:Web API服务是通过一般的 HTTP公开了,而不是通过更正式的服务合同 (如SOAP)  2. ASP.NET

ASP.Net Web API 的参数绑定[翻译]

原文地址:Parameter Binding in ASP.NET Web API 译文如下: 当Web API相应Controller的一个方法时,它必定存在一个设置参数的过程,叫作数据绑定.这篇文章描述了Web API如何绑定参数以及如何自定义绑定过程. 一般情况下,Web API绑定参数符合如下规则: 如果参数为简单类型,Web API 尝试从URI中获取.简单参数类型包含.Net源生类型(int,bool,double...),加上TimeSpan,DateTime,Guid,decim

ASP.NET Core MVC/WebAPi 模型绑定探索

前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法.本文我们来讲解.NET Core中的模型绑定. 话题 在ASP.NET Core之前MVC和Web APi被分开,也就说其请求管道是独立的,而在ASP.NET Core中,WebAPi和MVC的请求管道被合并在一起,当我们建立控

Web APi之捕获请求原始内容的实现方法以及接受POST请求多个参数多种解决方案(十四)

前言 我们知道在Web APi中捕获原始请求的内容是肯定是很容易的,但是这句话并不是完全正确,前面我们是不是讨论过,在Web APi中,如果对于字符串发出非Get请求我们则会出错,为何?因为Web APi对于简单的值不能很好的映射.之前我们谈论过请求内容注意事项问题,本节我们将更加深入的来讨论这个问题,我们会循序渐进进行探讨,并给出可行的解决方案,.细细品,定让你收货多多! 捕获请求原始内容实现方法 捕获复杂属性值 Web APi对于复杂属性值以JSON或者XML的形式成功发送到服务器,基于这点