ASP.NET WebAPI 中的参数绑定

当 WebAPI 调用 Controller 上的方法时, 必须为其参数赋值, 这个过程就是参数绑定。 本文介绍 WebAPI 如何绑定参数, 以及如何进行自定义。

WebAPI 默认使用下面的规则进行参数绑定:

  • 简单类型, WebAPI 尝试从 URL 中获取它的值。 简单类型包括:

    • .NET 原始类型(int、 bool、 float、 double 等);

    • 以及 TimeSpan 、 DateTime 、 Guid、 decimal 和 string

    • 提供了类型转换器 (Type Converter), 能够从字符串转换的类型。

  • 复杂类型则使用 media-type formatter 从 HTTP 请求的正文 (body) 中读取。

比如一个典型的 WebAPI 方法:

IHttpActionResult Put(int id, Product item) { ... }

参数 id 是一个简单类型, 所以从 request URI 中取值, 而参数 item 是复杂类型, 则从 request 正文 (body) 中取值。

使用 [FromUri]

要强制 WebAPI 从 URL 读取一个复杂类型的参数, 则需要在该参数上添加 FromUri 标记。 下面的例子定义了一个 GeoPoint 类型, 以及如何从 URI 中获取 GeoPoint 实例。

public class GeoPoint {public double Latitude { get; set; }public double Longitude { get; set; }}public class TestController : ApiController {public IHttpActionResult Get([FromUri]GeoPoint location) { ... }}

客户端可以在 QueryString 中传递 Latitude 和 Longitude 来构造 GeoPoint 实例, 示例请求如下:

http://127.0.0.1/api/test?latitude=22.3&longitude=113.2

注: QueryString 中的参数名称是不区分大小写的。

对于数组类型, 也可以使用 [FromUri] 标记, 比如:

public IHttpActionResult Get([FromUri]int[] items) { ... }

客户端这样发送请求:

http://127.0.0.1/api/test?items=1&items=2&items=3

服务端就可以接收到数组参数了。

使用 [FromBody]

要强制 WebAPI 从 request正文 (body) 中读取一个简单类型的参数, 需要在该参数上添加 FromBody 标记:

public HttpResponseMessage Post([FromBody] string name) { ... }

在这个例子中, WebAPI 需要使用 media-type formatter 从 request正文 (body) 中读取 name 的值, 示例请求如下:

POST http://localhost:5076/api/values HTTP/1.1User-Agent: FiddlerHost: localhost:5076Content-Type: application/json
Content-Length: 7"Alice"

当一个参数有 [FromBody] 标记时, WebAPI 使用 Content-Type 标头来选择正确的格式, 在上面的例子中, Content-Type 是 application/json , request正文 (body) 的内容是原始的 JSON 字符串, 而不是一个 JSON 对象。

> 一个函数中, 最多只能有一个 [FromBody] 标记, 因为客户端的请求有可能没有缓冲, 只能被读取一次。

使用 Type Converter

通过创建 Type Converter , 实现从字符串转换的方法, 可以让 WebAPI 将复杂类型参数视为简单类型参数。

以上面的 GeoPoint 为例, 再提供一个 GeoPointConverter 实现从字符串到 GeoPoint 的转换:

[TypeConverter(typeof(GeoPointConverter))]
public class GeoPoint {public double Latitude { get; set; }public double Longitude { get; set; }public bool TryParse(string s, out GeoPoint result) {result = null;var parts = s.Split(',');if (parts.Length != 2) {return false;}double latitude, longitude;if (double.TryParse(parts[0], out latitude) &&double.TryParse(parts[1], 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);}}

现在, WebAPI 会将 GeoPoint 当作简单类型, 意味着将尝试从 URI 中绑定 GeoPoint 参数的值, 也不再需要 [FromUri] 标记:

public HttpResponseMessage Get(GeoPoint location) { ... }

客户端这样发送 HTTP 请求:

https://127.0.0.1/api/test?location=22.3,113.2

使用 Model Binder

另一个比 type converter 更加灵活的是创建自定义 Model Binder 。 通过 Model Binder , 可以直接访问 http 请求、 action 描述以及路由的原始值。

要创建 Model Binder , 需要实现接口 IModelBinder , 它只定义了一个方法 BindModel :

public interface IModelBinder {bool BindModel(        HttpActionContext actionContext,        ModelBindingContext bindingContext    );}

下面是针对 GeoPoint 的实现:

public class GeoPointModelBinder : IModelBinder {// List of known locations.    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(        HttpActionContext actionContext,        ModelBindingContext bindingContext    ) {if (bindingContext.ModelType != typeof(GeoPoint)) {return false;}// exit if no value from value provider        var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);if (val == null) {return false;}// exit if row value is not a string.        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 不止局限于简单类型, 也支持复杂类型。 上面的 MobelBinder 支持两种格式的查询:

  • 使用已知的地名: http://127.0.0.1:/rest/api/test?location=redmond ;

  • 使用经纬度: http://127.0.0.1:/rest/api/test?location=47.67856,-122.131 ;

设置 Model Binder

首先, 可以在 action 方法的参数上添加 [ModelBinder] 标记, 例如:

public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)

其次, 可以在 GeoPoint 类型上添加 [ModelBinder] 标记, 例如:

[ModelBinder(typeof(GeoPointModelBinder))]public class GeoPoint {// ....}

最后, 还可以在 HttpConfiguration 类中添加一个 model-binder provider 来使用, 代码如下:

public static class WebApiConfig {public static void Register(HttpConfiguration config) {var provider = new SimpleModelBinderProvider(typeof(GeoPoint),new GeoPointModelBinder());config.Services.Insert(typeof(ModelBinderProvider),0,provider);// ...    }}

在 action 方法中仍然需要为参数添加 [ModelBinder] 标记, 来说明该参数需要使用 model-binder 来而不是 media formatter 来进行参数绑定, 不过此时就不需要再指定 ModelBinder 的类型了:

public HttpResponseMessage Get(    [ModelBinder] GeoPoint location) { ... }

使用 ValueProvider

Model Binder 需要从 Value Provider 中取值, 因此也可以创建自定义的 Value Provider 实现获取特殊的值。 要实现自定义的 ValueProvider , 需要实现接口 IValueProvider , 下面是一个从 Cookie 中获取值的 CookieValueProvider :

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;}}

同时还需要定义一个继承自 ValueProviderFactory 的 CookieValueProviderFactory , 代码如下:

public class CookieValueProviderFactory : ValueProviderFactory {public override IValueProvider GetValueProvider(HttpActionContext actionContext) {return new CookieValueProvider(actionContext);}}

然后将 CookieValueProviderFactory 注册到 HttpConfiguration 实例:

public static void Register(HttpConfiguration config) {config.Services.Add(typeof(ValueProviderFactory),new CookieValueProviderFactory());// ...}

Web API 将组合所有的 ValueProviderFactory , 当一个 model binder 调用 ValueProvider.GetValue 方法时, 将会收到第一个能够提供对应值的 ValueProviderFactory 提供的值。

或者, 也可以直接在在参数上使用 ValueProviderAttribute 标记:

public HttpResponseMessage Get(    [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location) { ... }

这样, Web API 在处理这个参数时, 就会直接使用 CookieValueProviderFactory , 不再使用其它的 CookieValueProviderFactory 。

HttpParameterBinding

Model binder 只是参数绑定中的一个特定的实例, 如果查看 ModelBinderAttribute 类的定义, 会发现它继承自抽象类 ParameterBindingAttribute , 这个类只定义了一个方法 GetBinding , 返回一个 HttpParameterBinding 实例。

public abstract class ParameterBindingAttribute : Attribute {public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);}

HttpParameterBinding 负责将参数绑定到值, 以 [ModelBinder] 为例, 这个标记返回一个 HttpParameterBinding 实现, 使用 IModelBinder 进行具体的绑定。 当然, 也可以实现自定义的 HttpParameterBinding 。

假设要获取 HTTP 请求 Header 中的 if-match 和 if-none-match 标签 (ETag) , 先定义一个类来表示 ETag :

public class ETag {public string Tag { get; set; }}

同时再定义一个枚举来指定是从 if-match 还是 if-none-match 标头中获取 ETag:

public enum ETagMatch {IfMatch,IfNoneMatch}

接下来是从 HTTP 请求头中获取 ETag 的 ETagParameterBinding ,

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 方法中实现具体的绑定, 在这个方法中, 将取得的参数的值存放到 HttpActionContext的 ActionArgument 字典中。

注意, 如果自定义的 HttpParameterBinding 需要从 HTTP 请求的正文 (body) 中读取信息, 则需要重写 WillReadBody 并返回 true 。 由于 HTTP 请求正文可能是个没有缓冲的流, 只能读取一次, 所以 Web API 加强了一个规则, 那就是每个方法只有一个绑定能够从 HTTP 请求正文读取数据。

要使用自定义的 HttpParameterBinding , 则需要创建一个自定义的标记, 继承自 ParameterBindingAttribute。 针对上面的 ETagParameterBinding , 我们来定义两个自定义标记, 分别表示从 if-match 和 if-none-match标头中获取, 代码如下:

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) { ... }

除了直接使用这个标记, 也可以在 HttpConfiguration 中进行配置, 代码如下:

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 的可插拔的服务控制,默认的按照下面的规则进行参数绑定:

  1. 在参数上查找 ` ParameterBindingAttribute , 包括 [FromBody] 、 [FromUri] 、 [ModelBinder]` 或者其它自定义标记;

  2. 然后在 HttpConfiguration.ParameterBindingRules 中查找一个返回 HttpParameterBinding 实例的函数;

  3. 最后, 使用上面提到的默认规则:

    • 如果参数是一个简单类型或者指定了类型转换器, 从 URI 绑定, 相当于在参数上添加 [FromUri] 标记;

    • 否则, 尝试从 HTTP 请求正文中读取, 相当于在参数上添加 [FromBody] 标记。

如果默认的绑定不能满足需求, 也可以实现自定义的 IActionValueBinder 来替换掉 Web API 默认的实现。

原文地址:http://beginor.github.io/2017/06/25/parameter-binding-in-aspnet-web-api.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324272.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

<table/>设置列宽度无效的问题

一、场景重现 <html> <head><title>测试</title><style type"text/css">.table {table-layout: fixed;}</style> </head> <body> <div style"width: 100%"><table class"table" styl…

公众号一年能有多少收入?

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号【雄雄的小课堂】。前言由于坚持日更公众号也有一年左右了&#xff0c;好多人问我你这公众号一年到底能收入多少啊&#xff1f;值得你花那么多的时间去摆弄吗&#xff1f;今天我就来说说我这公众号&#xff0c;一年到…

SpringBootAdmin客户端接入

只有网关微服务有方框中的内容&#xff0c;其他微服务没有

ssl初一组周六模拟赛【2018.5.12】(期中)

前言 这周竟然没有奶死自己&#xff0c;成为模拟赛第一个AK的&#xff08;然而第一题数据错了所以这次放加上第一题的分&#xff09; 先说一下成绩&#xff1a; 姓名成绩wyc400xjq290xxy255lrz225hzb205zyc190hjq180lw140 期中总结 正题 题目1&#xff1a;ssl2413 排名【…

Java自动化邮件中发送图表(一)

一、邮件需求 邮件中需要展示柱状图、折线图和饼图等图表数据。如图&#xff1a; 二、解决方案 将图表转成图片&#xff0c;采用html邮件文本&#xff0c;使用base64编码图片发送邮件。 将图表导出成图片有三种方式&#xff1a; &#xff08;1&#xff09;JFreeChart 优点…

springboot+mybatis-plus实例demo

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号【雄雄的小课堂】。前言故事还得从一次微信通话说起……一个夜深人静的晚上&#xff0c;正在电脑前看书&#xff0c;突然&#xff0c;放在旁边的手机响了起来&#xff0c;原来是一个朋友打的微信电话。“你在干嘛呢&a…

分布式事务,EventBus 解决方案:CAP【中文文档】

前言 很多同学想对CAP的机制以及用法等想有一个详细的了解&#xff0c;所以花了将近两周时间写了这份中文的CAP文档&#xff0c;对 CAP 还不知道的同学可以先看一下 .NET Core 事件总线,分布式事务解决方案&#xff1a;CAP。 本文档为 CAP 文献&#xff08;Wiki&#xff09;&…

POJ1330-Nearest Common Ancestors【tarjan,LCA】

正题 题目链接&#xff1a; http://poj.org/problem?id1330 题目大意 就是给出一棵树&#xff0c;求LCA&#xff08;最近公共祖先&#xff09; 解题思路 用tarjan求LCA&#xff0c;这里给出tarjan算法 代码 #include<cstdio> #include<iostream> using nam…

Java自动化邮件中发送图表(二)之JFreeChart

一、JFreeChart库 JFreeChart是JAVA平台上的一个开放的图表绘制类库。 JFreeChart可生成饼图&#xff08;pie charts&#xff09;、柱状图&#xff08;bar charts&#xff09;、散点图&#xff08;scatter plots&#xff09;、时序图&#xff08;time series&#xff09;、甘…

springboot点击运行没反应,什么都不显示的解决方式

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号【雄雄的小课堂】。最近这段时间一直在看开源框架&#xff0c;自己慢慢的琢磨&#xff0c;终于将jeecgboot环境搭建起来&#xff0c;并且成功的跑起来了demo&#xff0c;在此过程中&#xff0c;很明显的能感觉到自己进…

C#和NewSQL更配 —— CockroachDB入门

一、CockroachDB是什么 CockroachDB&#xff08;https://www.cockroachlabs.com&#xff09;是Google备受瞩目的Spanner的开源模仿&#xff0c;承诺提供一种高存活性、强一致性&#xff0c;可横向扩展的SQL数据库。主要的设计目标是全球一致性和可靠性&#xff0c;从蟑螂&#…

springboot+layui从控制器请求至页面时js失效的解决方法

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。 昨天遇到了个很棘手的问题&#xff0c;其实也怪自己大意了&#xff0c;然后翻来覆去一个类一个类的去看&#xff0c;看完之后挨个技术点怀疑&#xff0c;然后分别从各个技术点入手解决&#xff0c;但…

JfreeChart(八)之甘特图

转载自 JfreeChart实现甘特图 一、甘特图简介 甘特图(Gantt chart)又称为横道图、条状图(Bar chart)。以提出者亨利L甘特先生的名字命名。 甘特图内在思想简单&#xff0c;即以图示的方式通过活动列表和时间刻度形象地表示出任何特定项目的活动顺序与持续时间。基本是一条…

springboot从控制器请求至页面时js失效的解决方法

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。昨天遇到了个很棘手的问题&#xff0c;其实也怪自己大意了&#xff0c;然后翻来覆去一个类一个类的去看&#xff0c;看完之后挨个技术点怀疑&#xff0c;然后分别从各个技术点入手解决&#xff0c;但都…

ASP.NET Core 源码学习之 Options[2]:IOptions

在 上一篇 中&#xff0c;介绍了一下Options的注册&#xff0c;而使用时只需要注入 IOption 即可&#xff1a; public ValuesController(IOptions<MyOptions> options){ var opt options.Value; } IOptions IOptions 定义非常简单&#xff0c;只有一个Value属性&a…

Java自动化邮件中发送图表(三)之Highchart

一、Highchart &#xff08;1&#xff09;Highchart.js Highcharts 是一个用纯JavaScript编写的一个图表库。能够很简单便捷的在web网站或是web应用程序添加有交互性的图表。 &#xff08;2&#xff09;highcharts-serverside-export Highcharts Serverside Export框架&…

【2018.5.19】模拟赛之一-ssl2432 面积最大【数学】

正题 大意 解题思路 沟谷定理可以用半径求出高度&#xff0c;然后暴力枚举就好了 公式&#xff1a; ahr2−(a/2)2−−−−−−−−−√∗2ahr2−(a/2)2∗2bhr2−(b/2)2−−−−−−−−−√∗2bhr2−(b/2)2∗2然后计算两个的面积去掉重复的 Sa∗ahb∗bh−a∗bSa∗ahb∗bh−a…