Parameter Binding in ASP.NET Web API(参数绑定)

导航

阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html

本文主要来讲解以下内容:

  〇、前言

  Ⅰ、Using[FromUri]

  Ⅱ、Using[FromBody]

  Ⅲ、Type Converters

  Ⅳ、Model Binders

  Ⅴ、Value Providers

  Ⅵ、HttpParameterBinding

  Ⅶ、IActionValueBinder

前言

阅读本文之前,您也可以到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等等)加上TimeSpanDateTimeGuiddecimal, and string,再加上任何的能从字符串进行转换的类型。

  2、对于复杂类型,Web API试图用媒体格式化器http://www.cnblogs.com/aehyok/p/3460164.html从消息体中来读取值。

例如,这是一个典型的Web API控制器方法:

  1. 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参数。

  1. public class GeoPoint
  2. {
  3. public double Latitude { get; set; }
  4. public double Longitude { get; set; }
  5. }
  6.  
  7. public ValuesController : ApiController
  8. {
  9. public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
  10. }

这个客户端可以把Latitude和Longitude的值放进查询字符串中。Web API将用这两个参数来构造一个GeoPoint参数。例如:

  1. http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989

Using [FromBody]

为了更好的让Web API 从消息体中读取一个简单类型。添加【FromBody】属性到参数上:

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

在这个例子中,Web API将使用媒体格式化器来读取消息体中的name值。这是一个客户端请求的例子:

  1. POST http://localhost:5076/api/values HTTP/1.1
  2. User-Agent: Fiddler
  3. Host: localhost:5076
  4. Content-Type: application/json
  5. Content-Length: 7
  6.  
  7. "Alice"

当一个参数拥有【FromBody】属性的时候,Web API使用Content-Type header去选择一个格式化器。在这个例子中Content-Type是“application/json”,这个请求体是一个原始的Json字符串(而不是Json对象)。

至多一个参数被允许从消息体中读取值。因此如下这段将不会起作用:

  1. public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

对于这个规则的原因就是这个请求体被存储在只能被读取一次的非缓冲流中。

Type Converters

你也可以让Web API对待一个class像一个简单的类型,通过创建一个TypeConverter 并提供一个字符串的转换。

接下来的代码展示了用一个GeoPoint类来表示一个地理位置。添加一个 TypeConverter来把字符串转换为GeoPoint实例。这个GeoPoint类用了一个TypeConverter属性来修饰,并且指定了这个TypeConverter的类型。

  1. [TypeConverter(typeof(GeoPointConverter))]
  2. public class GeoPoint
  3. {
  4. public double Latitude { get; set; }
  5. public double Longitude { get; set; }
  6. public static bool TryParse(string s, out GeoPoint result)
  7. {
  8. result = null;
  9.  
  10. var parts = s.Split(',');
  11. if (parts.Length != 2)
  12. {
  13. return false;
  14. }
  15.  
  16. double latitude, longitude;
  17. if (double.TryParse(parts[0], out latitude) &&
  18. double.TryParse(parts[1], out longitude))
  19. {
  20. result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
  21. return true;
  22. }
  23. return false;
  24. }
  25. }
  1. public class GeoPointConverter:TypeConverter
  2. {
  3. public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
  4. {
  5. if (sourceType == typeof(string))
  6. {
  7. return true;
  8. }
  9. return base.CanConvertFrom(context, sourceType);
  10. }
  11.  
  12. public override object ConvertFrom(ITypeDescriptorContext context,
  13. CultureInfo culture, object value)
  14. {
  15. if (value is string)
  16. {
  17. GeoPoint point;
  18. if (GeoPoint.TryParse((string)value, out point))
  19. {
  20. return point;
  21. }
  22. }
  23. return base.ConvertFrom(context, culture, value);
  24. }
  25. }

现在Web API可以把GeoPoint看做是一个简单类型。意味着它将可以从URI中绑定GeoPoint参数。在参数上你不需要添加【FromUri】属性。

客户端可以调用这个方法,例如如下的URI:

  1. http://localhost/api/values/?location=47.678558,-122.130989

Model Binders

比一个type converter更灵活的选项是创建一个自定义的模型绑定。有了模型绑定,你可以使用像HTTP请求,Action描述,以及路由数据中的原始值。

为了创建一个Model Binder,你需要实现IModelBinder 接口,这个接口中定义了一个方法,BindModel:

  1. bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext);

接下来为GeoPoint对象来创建一个Model Binder。

  1. public class GeoPointModelBinder:IModelBinder
  2. {
  3. private static ConcurrentDictionary<string, GeoPoint> _locations
  4. = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);
  5.  
  6. static GeoPointModelBinder()
  7. {
  8. _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
  9. _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
  10. _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
  11. }
  12. public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext)
  13. {
  14. if (bindingContext.ModelType != typeof(GeoPoint))
  15. {
  16. return false;
  17. }
  18.  
  19. ValueProviderResult val = bindingContext.ValueProvider.GetValue(
  20. bindingContext.ModelName);
  21. if (val == null)
  22. {
  23. return false;
  24. }
  25.  
  26. string key = val.RawValue as string;
  27. if (key == null)
  28. {
  29. bindingContext.ModelState.AddModelError(
  30. bindingContext.ModelName, "Wrong value type");
  31. return false;
  32. }
  33.  
  34. GeoPoint result;
  35. if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
  36. {
  37. bindingContext.Model = result;
  38. return true;
  39. }
  40.  
  41. bindingContext.ModelState.AddModelError(
  42. bindingContext.ModelName, "Cannot convert value to Location");
  43. return false;
  44. }
  45. }

一个model binder从一个value provider中获得原始的录入值。这个设计分为两个独立的方法:

1、这个value provider接收到一个HTTP请求,并且填充一个键值对的字典。

2、然后model binder使用键值对的字典来填充model。

Web API中默认的value provider从路由数据和查询字符串中获取值。例如,这样一个URI:

  1. http://localhost/api/values/1?location=48,-122

value provider将会创建如下的键值对:

  1. id = "1"
  2. 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】属性到参数上。

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

你也能添加【Model Binder】属性到这个参数类型上。Web API将指定这个model binder到这个类型的所有参数上。

  1. [ModelBinder(typeof(GeoPointModelBinder))]
  2. public class GeoPoint
  3. {
  4. // ....
  5. }

最后,你能添加一个model-binder的提供者到HttpConfiguration。一个model-binder的提供者就是一个简单的工厂类,它可以创建一个model binder。你能创建一个provider通过派生自 ModelBinderProvider类。无论怎样,如果你的model binder处理单个类型,它是比较容易的通过使用已经创建的SimpleModelBinderProvider

接下来的代码展示如何启用他们:

  1. public static class WebApiConfig
  2. {
  3. public static void Register(HttpConfiguration config)
  4. {
  5. var provider = new SimpleModelBinderProvider(
  6. typeof(GeoPoint), new GeoPointModelBinder());
  7. config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
  8.  
  9. // ...
  10. }
  11. }

有了一个model-binding provider,你仍然需要添加一个[ModelBinder] 属性到参数上,它的目的就是告知Web API应该是用model binder,而不是使用媒体格式化器。但是现在你不需要在属性上指定这个model binder的类型。

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

Value Providers

我前面提到过一个model binder是从value provider中获取值。写一个自定义的value provider,实现这个IValueProvider 接口。这个例子是从请求的cookie中获取值。

  1. public class CookieValueProvider:IValueProvider
  2. {
  3. private Dictionary<string, string> _values;
  4.  
  5. public CookieValueProvider(HttpActionContext actionContext)
  6. {
  7. if (actionContext == null)
  8. {
  9. throw new ArgumentNullException("actionContext");
  10. }
  11.  
  12. _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  13. foreach (var cookie in actionContext.Request.Headers.GetCookies())
  14. {
  15. foreach (CookieState state in cookie.Cookies)
  16. {
  17. _values[state.Name] = state.Value;
  18. }
  19. }
  20. }
  21.  
  22. public bool ContainsPrefix(string prefix)
  23. {
  24. return _values.Keys.Contains(prefix);
  25. }
  26.  
  27. public ValueProviderResult GetValue(string key)
  28. {
  29. string value;
  30. if (_values.TryGetValue(key, out value))
  31. {
  32. return new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
  33. }
  34. return null;
  35. }
  36. }

你也需要创建一个value provider 工厂通过继承自ValueProviderFactory 。

  1. public class CookieValueProviderFactory : ValueProviderFactory
  2. {
  3. public override IValueProvider GetValueProvider(HttpActionContext actionContext)
  4. {
  5. return new CookieValueProvider(actionContext);
  6. }
  7. }

添加value provider 工厂到HttpConfiguration ,代码如下:

  1. public static void Register(HttpConfiguration config)
  2. {
  3. config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());
  4.  
  5. // ...
  6. }

Web API组合了所有的value provider,因此当一个model binder调用ValueProvider.GetValue,这个model binder接收从第一个value provider能提供它的值。

或者,通过使用ValueProvider属性你也能在参数级别上设置value provider 工厂,代码如下:

  1. public HttpResponseMessage Get(
  2. [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)

这将告诉Web API模型绑定使用指定的value  provider 工厂,不要用任何另外的被注册的value  provider。

HttpParameterBinding

模型绑定是一个更加普遍机制的特性实例。如果你看到这个 [ModelBinder] 属性,你将明白它是派生自ParameterBindingAttribute 抽象类。这个类定义了一个单独的方法,并返回一个HttpParameterBinding 对象:

  1. public abstract class ParameterBindingAttribute : Attribute
  2. {
  3. public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
  4. }

HttpParameterBinding 对象负责绑定一个值到一个参数。在[ModelBinder]修饰的情况下,这个属性返回一个HttpParameterBinding 的实现,它使用了一个IModelBinder 去展现真实的binding。你也可以实现自己的HttpParameterBinding 

例如,假定你想从请求的if-match 和 if-none-match 的header中获取ETags。开始我们将定义一个class来代替ETags 。

  1. public class ETag
  2. {
  3. public string Tag { get; set; }
  4. }

我们也来定义一个枚举指明是否从if-match 和 if-none-match 的header中获得了ETag 。

  1. public enum ETagMatch
  2. {
  3. IfMatch,
  4. IfNoneMatch
  5. }

这里是HttpParameterBinding ,获取该 ETag 从所需的标头并将其绑定到类型的参数的 ETag:

  1. public class ETagParameterBinding : HttpParameterBinding
  2. {
  3. ETagMatch _match;
  4.  
  5. public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match)
  6. : base(parameter)
  7. {
  8. _match = match;
  9. }
  10.  
  11. public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
  12. HttpActionContext actionContext, CancellationToken cancellationToken)
  13. {
  14. EntityTagHeaderValue etagHeader = null;
  15. switch (_match)
  16. {
  17. case ETagMatch.IfNoneMatch:
  18. etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault();
  19. break;
  20.  
  21. case ETagMatch.IfMatch:
  22. etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault();
  23. break;
  24. }
  25.  
  26. ETag etag = null;
  27. if (etagHeader != null)
  28. {
  29. etag = new ETag { Tag = etagHeader.Tag };
  30. }
  31. actionContext.ActionArguments[Descriptor.ParameterName] = etag;
  32.  
  33. var tsc = new TaskCompletionSource<object>();
  34. tsc.SetResult(null);
  35. return tsc.Task;
  36. }
  37. }

ExecuteBindingAsync 方法来处理绑定。在此方法中,添加参数值到ActionArgument 字典中并在HttpActionContext中。

如果你的ExecuteBindingAsync 方法读取请求消息体。重写这个WillReadBody 属性去返回true。这个消息体可能是只能读一次的未缓冲的流。因此Web API施行了一个规则至多有一个绑定可以读取消息体。

应用一个自定义的HttpParameterBinding,你能定义一个派生自ParameterBindingAttribute 的属性,对于ETagParameterBinding,我们将定义两个属性:一个是对于if-match  Header的,一个是对于if-none-match Header。都派生自一个抽象的基类。

  1. public abstract class ETagMatchAttribute : ParameterBindingAttribute
  2. {
  3. private ETagMatch _match;
  4.  
  5. public ETagMatchAttribute(ETagMatch match)
  6. {
  7. _match = match;
  8. }
  9.  
  10. public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
  11. {
  12. if (parameter.ParameterType == typeof(ETag))
  13. {
  14. return new ETagParameterBinding(parameter, _match);
  15. }
  16. return parameter.BindAsError("Wrong parameter type");
  17. }
  18. }
  19.  
  20. public class IfMatchAttribute : ETagMatchAttribute
  21. {
  22. public IfMatchAttribute()
  23. : base(ETagMatch.IfMatch)
  24. {
  25. }
  26. }
  27.  
  28. public class IfNoneMatchAttribute : ETagMatchAttribute
  29. {
  30. public IfNoneMatchAttribute()
  31. : base(ETagMatch.IfNoneMatch)
  32. {
  33. }
  34. }

这是一个控制器方法 使用了[IfNoneMatch] 属性。

  1. public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }

除了ParameterBindingAttribute 之外,对于添加一个自定义的HttpParameterBinding 有另外一个挂钩。在HttpConfiguration 对象上,ParameterBindingRules 是一个匿名方法类型(HttpParameterDescriptor -> HttpParameterBinding)的集合。例如,你可以添加一个规则:在Get请求方法中任何ETag 参数使用ETagParameterBinding with if-none-match。

  1. config.ParameterBindingRules.Add(p =>
  2. {
  3. if (p.ParameterType == typeof(ETag) &&
  4. p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get))
  5. {
  6. return new ETagParameterBinding(p, ETagMatch.IfNoneMatch);
  7. }
  8. else
  9. {
  10. return null;
  11. }
  12. });

这个方法对于绑定不适用的参数应该返回null。

IActionValueBinder

整个参数绑定的过程被一个可插拔的服务控制,IActionValueBinderIActionValueBinder 的默认实现将执行以下操作:

  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

 
 

Parameter Binding in ASP.NET Web API(参数绑定)的更多相关文章

  1. 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. 本文主要来讲解以下内容: ...

  2. Parameter Binding in ASP.NET Web API

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding ...

  3. Parameter Binding in ASP.NET Web API #Reprinted

    http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

  4. 细说 Web API参数绑定和模型绑定

    今天跟大家分享下在Asp.NET Web API中Controller是如何解析从客户端传递过来的数据,然后赋值给Controller的参数的,也就是参数绑定和模型绑定. Web API参数绑定就是简 ...

  5. Web API(四):Web API参数绑定

    在这篇文章中,我们将学习Web API如何将HTTP请求数据绑定到一个操作方法的参数中. 操作方法在Web API控制器中可以有一个或多个不同类型的参数.它可以是基本数据类型或复杂类型.Web API ...

  6. .net core Web API参数绑定规则

    参数推理绑定 先从一个问题说起,微信小程序按照WebAPI定义的参数传递,Get请求服务器端可以正常接收到参数,但是Post请求取不到. Web API代码(.netcore 3.1)如下: [Htt ...

  7. asp.net web api参数

    翻译自:http://www.c-sharpcorner.com/article/parameter-binding-in-asp-net-web-api/ 主要自己学习下,说是翻译,主要是把文章的意 ...

  8. 一张图说明 Web Api 参数绑定默认规则

    请求如下: 控制器如下: 慎重说明:不管请求方式是 get 还是 post , 简单类型的参数,如 name 和 id ,其值都是从 url 里面去取. Web API 从 url 还是 body 获 ...

  9. jQuery Ajax传递数组到asp.net web api参数为空

    前端: var files = []; files.push({ FileName: "1.jgp", Extension: ".jgp", FileType: ...

随机推荐

  1. 我也来谈javascript高级编程之:javascript函数编译过程

    前言 题目有点大,其实也就是手痒...跟大家来扯一下javascript编译过程. 那么到底什么是“编译”呢 这个...本人文笔太差,我还是直接举例子吧. 相信玩过js童鞋应该都看过下面这样一个面试题 ...

  2. c语言获取符号位整数和浮点

    1. 为什么你应该得到的签位 非常多的时间,我们需要推断的数目值正和负,做了相应的逻辑处理.完成这一要求条件推断语句可以很好. 有时会出现以下情况, if (x > 0) { x = x - 1 ...

  3. 多重集组合数 (DP)

    输入: n=3 m=3 a={1,2,3} M=10000 输出: 6  (0+0+3,0+1+2,0+2+1,1+0+2,1+1+1,1+2+0) 为了不重复计数,同一种类的物品最好一次性处理好.于 ...

  4. 【软件project】生存期模型(含图)

    为了反映软件生存周期内各个工作应怎样组织,各阶段怎样衔接,须要软件开发模型给出直观图示表达.软件开发模型是软件思想的详细化,是实施在过程模块中的软件开发方法和工具. 以下来介绍开发模型的特点以及他们的 ...

  5. Android Fragment 真正彻底的解决(下一个)

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因.以及一些主要的使用方 ...

  6. jQuery - 基于serializeArray的serializeObject

    将表单序列化成JSON对象,注意不管是自实现的serializeObject()还是原生的serializeArray(),所要序列化的控件都必须要有name,而不是id jQuery.prototy ...

  7. URAL 1404. Easy to Hack! (模拟)

    space=1&num=1404">1404. Easy to Hack! Time limit: 1.0 second Memory limit: 64 MB When Vi ...

  8. bootstrap标准模板

    <!DOCTYPE html><!--html5定义--> <html lang="en"> <head> <meta cha ...

  9. 使用JFinal框架中Validator

    Validator是JFinal框架中的校验组件,在Validator类中提供了我们经常使用的校验方法,而Validator本身实现了Interceptor接口,所以Validator也相当于一个拦截 ...

  10. 小结css2与css3的区别

    CSS3引进了一些新的元素新的特性,我收集以下,自己做了一个小结: animation(基础动画)eg:  div{animation: myfirst 5s linear 2s infinite a ...