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。
本文主要来讲解以下内容:
前言
阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html。
当Web API在一个控制器中调用一个方法的时候,它必须为参数设定值,这个过程就叫做绑定。这篇文章描述Web API如何绑定参数,以及如何自定义绑定过程。
默认情况,Web API使用如下规则来绑定参数:
1、如果参数一个"简单"类型,那么Web API试图从URI中获取值。简单的类型包含.NET的基元类型(int,bool,double等等)加上TimeSpan, DateTime, Guid, decimal, and string,再加上任何的能从字符串进行转换的类型。
2、对于复杂类型,Web API试图用媒体格式化器http://www.cnblogs.com/aehyok/p/3460164.html从消息体中来读取值。
例如,这是一个典型的Web API控制器方法:
- HttpResponseMessage Put(int id, Product item) { ... }
这个“id”参数是一个“简单”类型,因此Web API试图从请求的URI中获取参数值,这个“item”参数是一个复杂类型,因此Web API试图使用一个媒体格式化器从请求消息体中来读取参数值。
为了从URI中获取值,Web API会查看路由数据和URI查询字符串。这个路由数据被填充是在路由系统解析URI并匹配它到路由的时候。对于路由的更多信息: http://www.cnblogs.com/aehyok/p/3444710.html
在这篇文章剩余的部分我将来展示如何自定义模型绑定的过程。对于复杂类型,要尽可能的使用媒体格式化器来处理。HTTP的一个主要原则就是资源被发送在消息体中,使用内容协商http://www.cnblogs.com/aehyok/p/3481265.html来指定资源的展现。媒体格式化器被设计就是为了这个目的。
Using [FromUri]
为了更好的让Web API从URI中读取复杂类型,添加【FormUri】属性到参数上。下面的例子定义了一个GeoPoint
的类型,紧接着一个控制器方法从URI中获得这个GetPoint参数。
- public class GeoPoint
- {
- public double Latitude { get; set; }
- public double Longitude { get; set; }
- }
- public ValuesController : ApiController
- {
- public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
- }
这个客户端可以把Latitude和Longitude的值放进查询字符串中。Web API将用这两个参数来构造一个GeoPoint参数。例如:
- http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989
Using [FromBody]
为了更好的让Web API 从消息体中读取一个简单类型。添加【FromBody】属性到参数上:
- public HttpResponseMessage Post([FromBody] string name) { ... }
在这个例子中,Web API将使用媒体格式化器来读取消息体中的name值。这是一个客户端请求的例子:
- POST http://localhost:5076/api/values HTTP/1.1
- User-Agent: Fiddler
- Host: localhost:
- Content-Type: application/json
- Content-Length:
- "Alice"
当一个参数拥有【FromBody】属性的时候,Web API使用Content-Type header去选择一个格式化器。在这个例子中Content-Type是“application/json”,这个请求体是一个原始的Json字符串(而不是Json对象)。
至多一个参数被允许从消息体中读取值。因此如下这段将不会起作用:
- public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
对于这个规则的原因就是这个请求体被存储在只能被读取一次的非缓冲流中。
Type Converters
你也可以让Web API对待一个class像一个简单的类型,通过创建一个TypeConverter 并提供一个字符串的转换。
接下来的代码展示了用一个GeoPoint类来表示一个地理位置。添加一个 TypeConverter来把字符串转换为GeoPoint实例。这个GeoPoint类用了一个TypeConverter属性来修饰,并且指定了这个TypeConverter的类型。
- [TypeConverter(typeof(GeoPointConverter))]
- public class GeoPoint
- {
- public double Latitude { get; set; }
- public double Longitude { get; set; }
- public static bool TryParse(string s, out GeoPoint result)
- {
- result = null;
- var parts = s.Split(',');
- if (parts.Length != )
- {
- return false;
- }
- double latitude, longitude;
- if (double.TryParse(parts[], out latitude) &&
- double.TryParse(parts[], out longitude))
- {
- result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
- return true;
- }
- return false;
- }
- }
- public class GeoPointConverter:TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
- public override object ConvertFrom(ITypeDescriptorContext context,
- CultureInfo culture, object value)
- {
- if (value is string)
- {
- GeoPoint point;
- if (GeoPoint.TryParse((string)value, out point))
- {
- return point;
- }
- }
- return base.ConvertFrom(context, culture, value);
- }
- }
现在Web API可以把GeoPoint看做是一个简单类型。意味着它将可以从URI中绑定GeoPoint参数。在参数上你不需要添加【FromUri】属性。
客户端可以调用这个方法,例如如下的URI:
- http://localhost/api/values/?location=47.678558,-122.130989
Model Binders
比一个type converter更灵活的选项是创建一个自定义的模型绑定。有了模型绑定,你可以使用像HTTP请求,Action描述,以及路由数据中的原始值。
为了创建一个Model Binder,你需要实现IModelBinder 接口,这个接口中定义了一个方法,BindModel:
- bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext);
接下来为GeoPoint对象来创建一个Model Binder。
- public class GeoPointModelBinder:IModelBinder
- {
- private static ConcurrentDictionary<string, GeoPoint> _locations
- = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);
- static GeoPointModelBinder()
- {
- _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
- _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
- _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
- }
- public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext)
- {
- if (bindingContext.ModelType != typeof(GeoPoint))
- {
- return false;
- }
- ValueProviderResult val = bindingContext.ValueProvider.GetValue(
- bindingContext.ModelName);
- if (val == null)
- {
- return false;
- }
- string key = val.RawValue as string;
- if (key == null)
- {
- bindingContext.ModelState.AddModelError(
- bindingContext.ModelName, "Wrong value type");
- return false;
- }
- GeoPoint result;
- if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
- {
- bindingContext.Model = result;
- return true;
- }
- bindingContext.ModelState.AddModelError(
- bindingContext.ModelName, "Cannot convert value to Location");
- return false;
- }
- }
一个model binder从一个value provider中获得原始的录入值。这个设计分为两个独立的方法:
1、这个value provider接收到一个HTTP请求,并且填充一个键值对的字典。
2、然后model binder使用键值对的字典来填充model。
Web API中默认的value provider从路由数据和查询字符串中获取值。例如,这样一个URI:
- http://localhost/api/values/1?location=48,-122
value provider将会创建如下的键值对:
- id = ""
- location = "48,122"
我们假设使用的是默认的路由模版。
被绑定的参数的名称被存储在ModelBindingContext.ModelName这个属性上。model binder在字典中寻找一个键的值。如果这个值存在,并且也能被转换成GeoPoint,这个model binder将分配这个值到ModelBindingContext.Model属性。
注意:Model Binder不会限制一个简单类型的转换,这个model binder首先会在已知位置的列表中寻找,如果查找失败,将会使用 type converter。
Setting the Model Binder
这里有几种方法去设置Model Binder.首先你可以添加一个【Model Binder】属性到参数上。
- public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)
你也能添加【Model Binder】属性到这个参数类型上。Web API将指定这个model binder到这个类型的所有参数上。
- [ModelBinder(typeof(GeoPointModelBinder))]
- public class GeoPoint
- {
- // ....
- }
最后,你能添加一个model-binder的提供者到HttpConfiguration。一个model-binder的提供者就是一个简单的工厂类,它可以创建一个model binder。你能创建一个provider通过派生自 ModelBinderProvider类。无论怎样,如果你的model binder处理单个类型,它是比较容易的通过使用已经创建的SimpleModelBinderProvider。
接下来的代码展示如何启用他们:
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
- var provider = new SimpleModelBinderProvider(
- typeof(GeoPoint), new GeoPointModelBinder());
- config.Services.Insert(typeof(ModelBinderProvider), , provider);
- // ...
- }
- }
有了一个model-binding provider,你仍然需要添加一个[ModelBinder] 属性到参数上,它的目的就是告知Web API应该是用model binder,而不是使用媒体格式化器。但是现在你不需要在属性上指定这个model binder的类型。
- public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }
Value Providers
我前面提到过一个model binder是从value provider中获取值。写一个自定义的value provider,实现这个IValueProvider 接口。这个例子是从请求的cookie中获取值。
- public class CookieValueProvider:IValueProvider
- {
- private Dictionary<string, string> _values;
- public CookieValueProvider(HttpActionContext actionContext)
- {
- if (actionContext == null)
- {
- throw new ArgumentNullException("actionContext");
- }
- _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- foreach (var cookie in actionContext.Request.Headers.GetCookies())
- {
- foreach (CookieState state in cookie.Cookies)
- {
- _values[state.Name] = state.Value;
- }
- }
- }
- public bool ContainsPrefix(string prefix)
- {
- return _values.Keys.Contains(prefix);
- }
- public ValueProviderResult GetValue(string key)
- {
- string value;
- if (_values.TryGetValue(key, out value))
- {
- return new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
- }
- return null;
- }
- }
你也需要创建一个value provider 工厂通过继承自ValueProviderFactory 。
- public class CookieValueProviderFactory : ValueProviderFactory
- {
- public override IValueProvider GetValueProvider(HttpActionContext actionContext)
- {
- return new CookieValueProvider(actionContext);
- }
- }
添加value provider 工厂到HttpConfiguration ,代码如下:
- public static void Register(HttpConfiguration config)
- {
- config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());
- // ...
- }
Web API组合了所有的value provider,因此当一个model binder调用ValueProvider.GetValue,这个model binder接收从第一个value provider能提供它的值。
或者,通过使用ValueProvider属性你也能在参数级别上设置value provider 工厂,代码如下:
- public HttpResponseMessage Get(
- [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)
这将告诉Web API模型绑定使用指定的value provider 工厂,不要用任何另外的被注册的value provider。
HttpParameterBinding
模型绑定是一个更加普遍机制的特性实例。如果你看到这个 [ModelBinder] 属性,你将明白它是派生自ParameterBindingAttribute 抽象类。这个类定义了一个单独的方法,并返回一个HttpParameterBinding 对象:
- public abstract class ParameterBindingAttribute : Attribute
- {
- public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
- }
HttpParameterBinding 对象负责绑定一个值到一个参数。在[ModelBinder]修饰的情况下,这个属性返回一个HttpParameterBinding 的实现,它使用了一个IModelBinder 去展现真实的binding。你也可以实现自己的HttpParameterBinding 。
例如,假定你想从请求的if-match
和 if-none-match
的header中获取ETags。开始我们将定义一个class来代替ETags 。
- public class ETag
- {
- public string Tag { get; set; }
- }
我们也来定义一个枚举指明是否从if-match
和 if-none-match
的header中获得了ETag 。
- public enum ETagMatch
- {
- IfMatch,
- IfNoneMatch
- }
这里是HttpParameterBinding ,获取该 ETag 从所需的标头并将其绑定到类型的参数的 ETag:
- public class ETagParameterBinding : HttpParameterBinding
- {
- ETagMatch _match;
- public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match)
- : base(parameter)
- {
- _match = match;
- }
- public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
- HttpActionContext actionContext, CancellationToken cancellationToken)
- {
- EntityTagHeaderValue etagHeader = null;
- switch (_match)
- {
- case ETagMatch.IfNoneMatch:
- etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault();
- break;
- case ETagMatch.IfMatch:
- etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault();
- break;
- }
- ETag etag = null;
- if (etagHeader != null)
- {
- etag = new ETag { Tag = etagHeader.Tag };
- }
- actionContext.ActionArguments[Descriptor.ParameterName] = etag;
- var tsc = new TaskCompletionSource<object>();
- tsc.SetResult(null);
- return tsc.Task;
- }
- }
ExecuteBindingAsync 方法来处理绑定。在此方法中,添加参数值到ActionArgument 字典中并在HttpActionContext中。
如果你的ExecuteBindingAsync 方法读取请求消息体。重写这个WillReadBody 属性去返回true。这个消息体可能是只能读一次的未缓冲的流。因此Web API施行了一个规则至多有一个绑定可以读取消息体。
应用一个自定义的HttpParameterBinding,你能定义一个派生自ParameterBindingAttribute 的属性,对于ETagParameterBinding,我们将定义两个属性:一个是对于if-match
Header的,一个是对于if-none-match Header。都派生自一个抽象的基类。
- public abstract class ETagMatchAttribute : ParameterBindingAttribute
- {
- private ETagMatch _match;
- public ETagMatchAttribute(ETagMatch match)
- {
- _match = match;
- }
- public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
- {
- if (parameter.ParameterType == typeof(ETag))
- {
- return new ETagParameterBinding(parameter, _match);
- }
- return parameter.BindAsError("Wrong parameter type");
- }
- }
- public class IfMatchAttribute : ETagMatchAttribute
- {
- public IfMatchAttribute()
- : base(ETagMatch.IfMatch)
- {
- }
- }
- public class IfNoneMatchAttribute : ETagMatchAttribute
- {
- public IfNoneMatchAttribute()
- : base(ETagMatch.IfNoneMatch)
- {
- }
- }
这是一个控制器方法 使用了[IfNoneMatch]
属性。
- public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }
除了ParameterBindingAttribute 之外,对于添加一个自定义的HttpParameterBinding 有另外一个挂钩。在HttpConfiguration 对象上,ParameterBindingRules 是一个匿名方法类型(HttpParameterDescriptor -> HttpParameterBinding)的集合。例如,你可以添加一个规则:在Get请求方法中任何ETag 参数使用ETagParameterBinding
with if-none-match。
- config.ParameterBindingRules.Add(p =>
- {
- if (p.ParameterType == typeof(ETag) &&
- p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get))
- {
- return new ETagParameterBinding(p, ETagMatch.IfNoneMatch);
- }
- else
- {
- return null;
- }
- });
这个方法对于绑定不适用的参数应该返回null。
IActionValueBinder
整个参数绑定的过程被一个可插拔的服务控制,IActionValueBinder。IActionValueBinder 的默认实现将执行以下操作:
1、在参数上查看ParameterBindingAttribute ,这包括 [FromBody], [FromUri], 和[ModelBinder], 或者是自定义的属性。
2、否则,查看一个函数的HttpConfiguration.ParameterBindingRules ,它返回一个非null的HttpParameterBinding 。
3、否则,使用我之前描述的默认规则。
①、如果参数类型是一个“简单”的,或者拥有一个type converter,将会从URI进行绑定。它等价于在参数上添加[FromUri]属性。
②、否则,试图从消息体中读取参数,这等价于在参数上添加[FromBody]属性。
如果你需要,你可以用一个自定义的实现来替代整个IActionValueBinder 。
总结
本文主要来讲解参数绑定,但是通过上面也可以看出涉及到的知识点还是蛮多的,但是都是很实用的,例子也比较清晰。但是还是需要在项目中进行应用,才能更好的学习和掌握参数绑定的环节。
本文的参考链接为http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Asp.Net Web API 2第十六课——Parameter Binding in ASP.NET Web API(参数绑定)的更多相关文章
- Kali Linux Web 渗透测试视频教程— 第十六课-拒绝服务攻击
Kali Linux Web 渗透测试视频教程— 第十六课-拒绝服务攻击 文/玄魂 目录 Kali Linux Web 渗透测试视频教程— 第十六课-拒绝服务攻击................... ...
- Parameter Binding in ASP.NET Web API(参数绑定)
Parameter Binding in ASP.NET Web API(参数绑定) 导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnbl ...
- react第十六单元(redux的认识,redux相关api的掌握)
第十六单元(redux的认识,redux相关api的掌握) #课程目标 掌握组件化框架实现组件之间传参的几种方式,并了解两个没有任何关系组件之间通信的通点 了解为了解决上述通点诞生的flux架构 了解 ...
- 风炫安全WEB安全学习第二十六节课 XSS常见绕过防御技巧
风炫安全WEB安全学习第二十六节课 XSS常见绕过防御技巧 XSS绕过-过滤-编码 核心思想 后台过滤了特殊字符,比如说
- 风炫安全Web安全学习第十六节课 高权限sql注入getshell
风炫安全Web安全学习第十六节课 高权限sql注入getshell sql高权限getshell 前提条件: 需要知道目标网站绝对路径 目录具有写的权限 需要当前数据库用户开启了secure_file ...
- NeHe OpenGL教程 第四十六课:全屏反走样
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第三十六课:从渲染到纹理
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十六课:反射
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第十六课:雾
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
随机推荐
- Redmine2.5+CentOS6+Apache2
redmine是使用ruby开发的一款无任何商业限制且可自行部署的项目管理软件,其简洁的界面比较符合程序猿的定位,使用起来比较方便,由于我之前装3X没 成功,各版本之间的依存和配置都不一样,所以最后参 ...
- delphi 10 seattle 安卓服务开发(三)
delphi 10 里面的安卓服务有四种,上面的一篇文章里面的图有介绍. 今天做一个remote service 的例子.(里面一部分代码是抄别人的,如果不太清楚,自行恶补) remote servi ...
- ios delegate, block, NSNotification用法
ios中实现callback可以通过两种方法,委托和NSNotification 委托的话是一对一的关系,例如一个UIViewController里有一个tableView, 将该viewContro ...
- 去掉DLL can move
1.OptionalHeader.DllCharacteristics = wNewDllCharacteristics; 用CFF打开,如果存在DLL can move这个选项,去掉即可 2.Rel ...
- Ubuntu修改hosts方法
1.修改hostssudo gedit /etc/hosts如果不喜欢使用gedit命令,而且当前帐户为非root帐户,那么可把/etc/hosts复制到桌面上,然后手动编辑后保存,再使用命令copy ...
- UVALive 4818 - Largest Empty Circle on a Segment (计算几何)
题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...
- smarty模板原理
smarty模板原理 模板主要是用来让前端和后端分离的,前台页面只是一个前台页面,后台页面用php代码写逻辑,写完逻辑拿到前台显示. 一.写法 一般需要以下:写3个页面: 1.显示页面aa.htm ...
- JAVA定义接口格式:
[public]interface 接口名称 [extends父接口名列表] { //静态常量 [public] [static] [final] 数据类型变量名=常量值; //抽象方法 [publi ...
- C++进阶 面向对象基础(三)
类的的定义: 初始化一般建议使用构造函数初始化列表形式: Person(const string nm, const string addr):name(nm), address(addr){} th ...
- Sql Server 查询第30条数据到第40条记录数
1.select top 10 * from (select top 40 * from tablename order by id desc);