[Web API] Web API 2 深入系列(6) Model绑定(上)
目录
解决什么问题
Model元数据解析
- 复杂类型
ValueProvider
ValueProviderFactory
解决什么问题
Model: Action方法上的参数
Model绑定: 对Action方法参数绑定
通过2个实例说明它的作用
定义控制器和特性路由
[RoutePrefix("demo")]
public class DemoController : ApiController
{
[Route("get/{x?}/{y?}/{z?}")]
public IEnumerable<int> Get(int x, int y, int z)
{
yield return x;
yield return y;
yield return z;
}
}
SelfHost
using (var server = new HttpSelfHostServer(new HttpSelfHostConfiguration("http://localhost:10000")))
{
server.Configuration.MapHttpAttributeRoutes();
server.OpenAsync();
Console.Read();
}
请求地址:
请求结果,截图:

我们可以看到都返回了同样的结果,说明数据被绑定上了.
除了简单类型(基元类型和可空的值类型)支持绑定外,复杂类型也支持绑定.
[Route("get2/{x}/{y}/{z}")]
public Model Get(Model model)
{
return model;
}
[ModelBinder]
public class Model
{
public string X { get; set; }
public string Y { get; set; }
public string Z { get; set; }
}
请求地址:
请求结果,截图:

同样成功绑定上!
补充:
- 查询参数的绑定优先级高于路由绑定
Model元数据解析
从上面的例子中,我们看到复杂类型同样能实现Model绑定.其依赖于Model元数据.
Model元数据 不仅对复杂类型本身做描述,对复杂类型下的每个属性 同样也有描述.
ModelMetadata则为Model元数据
public class ModelMetadata
{
//类型
public Type ModelType { get; }
//是否复杂类型
public virtual bool IsComplexType { get; }
//是否可空类型
public bool IsNullableValueType { get; }
//父容器类型(root 为 null)
public Type ContainerType { get; }
//当前属性名
public string PropertyName { get; }
//当前属性值
public object Model { get; set; }
//所有子属性
public virtual IEnumerable<ModelMetadata> Properties { get: }
}
复杂类型
IsComplexType判断是否为复杂类型
标准:是否允许字符串类型向该类型转换.
默认:基元类型 和 可空值类型
public virtual bool IsComplexType
{
get { return !HasStringConverter(this.ModelType); }
}
internal static bool HasStringConverter(Type type)
{
//获取TypeConverter ,调用CanConvertFrom判断是否为复杂类型
return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof (string));
}
定义一个TypeConverter
public class PointTypeConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
return ParsePoint(value as string);
}
return base.ConvertFrom(context, culture, value);
}
static Point ParsePoint(string value)
{
var point = new Point();
var split = value.Split(',');
point.X = double.Parse(split[0]);
point.Y = double.Parse(split[1]);
return point;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
}
应用Point的TypeConverter 为 PointTypeConverter
[TypeConverter(typeof(PointTypeConverter))]
public class Point
{
public double X { get; set; }
public double Y { get; set; }
}
在DemoController加入一个Action
[Route("get/{point}")]
public Point Get(Point point)
{
return point;
}
请求地址:
- 路由地址 注意地址为1,2
Web API默认描述元数据类型为CachedDataAnnotationsModelMetadata
public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>
{
}
//主要存储了Model上的特性
public class CachedDataAnnotationsMetadataAttributes
{
public DisplayAttribute Display { get; protected set; }
public DisplayNameAttribute DisplayName { get; protected set; }
public DisplayFormatAttribute DisplayFormat { get; protected set; }
public EditableAttribute Editable { get; protected set; }
public ReadOnlyAttribute ReadOnly { get; protected set; }
//从Model特性上赋值
public CachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute> attributes)
{
this.Display = attributes.OfType<DisplayAttribute>().FirstOrDefault<DisplayAttribute>();
this.DisplayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault<DisplayFormatAttribute>();
this.DisplayName = attributes.OfType<DisplayNameAttribute>().FirstOrDefault<DisplayNameAttribute>();
this.Editable = attributes.OfType<EditableAttribute>().FirstOrDefault<EditableAttribute>();
this.ReadOnly = attributes.OfType<ReadOnlyAttribute>().FirstOrDefault<ReadOnlyAttribute>();
}
}
补充:
- Editable优先级高于ReadOnly
我们可以通过一个例子获取Model元数据
//获取ModelMetadataProvider
var provider = new HttpConfiguration().Services.GetModelMetadataProvider();
foreach (CachedDataAnnotationsModelMetadata property in provider.GetMetadataForType(() => new Model { X = "1" }, typeof(Model)).Properties)
{
Console.WriteLine($"{property.GetDisplayName()}-{property.Model}-{property.ModelType}-{property.IsReadOnly}");
}
运行截图:

ModelMetadataProvider
WebAPI利用ModelMetadataProvider 获取ModelMetadata
public abstract class ModelMetadataProvider
{
//获取容器下所有属性元数据
public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
//获取容器下指定属性元数据(modelAccessor 通过委托返回对象)
public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
//获取复杂数据的元数据
public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
}
而WebAPI中 默认的ModelMetadataProvider 为DataAnnotationsModelMetadataProvider,这也印证了上面的代码可行性
public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
{
//根据特性创建ModelMetadata
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
{
return new CachedDataAnnotationsModelMetadata(this, containerType, modelType, propertyName, attributes);
}
//根据原型创建ModelMetadata
protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
{
return new CachedDataAnnotationsModelMetadata(prototype, modelAccessor);
}
}
ValueProvider
通过第1节,我们知道Model绑定的数据源有2个:路由和查询字符串.
ValueProvider可以视为数据源
IValueProvider
public interface IValueProvider
{
//是否存在指定前缀
bool ContainsPrefix(string prefix);
//根据key 查找对应数据项
ValueProviderResult GetValue(string key);
}
IEnumerableValueProvider
public interface IEnumerableValueProvider : IValueProvider
{
//根据指定前缀 返回所有该前缀的key
IDictionary<string, string> GetKeysFromPrefix(string prefix);
}
ValueProviderResult
public class ValueProviderResult
{
//转换成字符串类型的值
public string AttemptedValue { get; protected set; }
//原始数据
public object RawValue { get; protected set; }
//类型转换 使用
public CultureInfo Culture { get; protected set; }
//类型转换
public object ConvertTo(Type type)
public virtual object ConvertTo(Type type, CultureInfo culture)
}
NameValuePairsValueProvider则是IEnumerableValueProvider的一个实现
public class NameValuePairsValueProvider : IEnumerableValueProvider, IValueProvider
{
public virtual ValueProviderResult GetValue(string key)
{
object rawValue;
if (this.Values.TryGetValue(key, out rawValue))
return new ValueProviderResult(rawValue, string.Join(",", (IEnumerable<string>) rawValue), this._culture);
return (ValueProviderResult) null;
}
}
我们在本节开始已经说明Model绑定有2个数据来源,其对应的ValueProvider为
RouteDataValueProvider
public class RouteDataValueProvider : NameValuePairsValueProvider
{
public RouteDataValueProvider(HttpActionContext actionContext, CultureInfo culture)
: base(RouteDataValueProvider.GetRouteValues(actionContext.ControllerContext.RouteData), culture)
{
}
internal static IEnumerable<KeyValuePair<string, string>> GetRouteValues(IHttpRouteData routeData)
{
foreach (KeyValuePair<string, object> keyValuePair in (IEnumerable<KeyValuePair<string, object>>) routeData.Values)
{
string value = keyValuePair.Value == null ? (string) null : keyValuePair.Value.ToString();
yield return new KeyValuePair<string, string>(keyValuePair.Key, value);
}
}
}
QueryStringValueProvider
public class QueryStringValueProvider : NameValuePairsValueProvider
{
public QueryStringValueProvider(HttpActionContext actionContext, CultureInfo culture)
: base(actionContext.ControllerContext.Request.GetQueryNameValuePairs(), culture)
{
}
}
除了NameValuePairsValueProvider,Web API还定义了一个特殊的Provider
CompositeValueProvider 既是1个Provider 又是1个Provider集合
public class CompositeValueProvider : Collection<IValueProvider>, IEnumerableValueProvider, IValueProvider
{
public CompositeValueProvider(IList<IValueProvider> list)
//调用内部Provider集合
public virtual ValueProviderResult GetValue(string key)
//调用内部Provider集合
public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
}
ValueProviderFactory
ValueProvider是用来提供数据源的.Web API同时定义了ValueProviderFactory来创建ValueProvider
public abstract class ValueProviderFactory
{
//根据HttpActionContext获取IValueProvider
public abstract IValueProvider GetValueProvider(HttpActionContext actionContext);
}
同样,也有对应的RouteDataValueProviderFactory和QueryStringValueProviderFactory,另外,在这2个Factory中,做了同一次请求只创建一次的缓存处理.
对应的Web API也提供了CompositeValueProviderFactory
public class CompositeValueProviderFactory : ValueProviderFactory
{
public CompositeValueProviderFactory(IEnumerable<ValueProviderFactory> factories)
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
//通过返回CompositeValueProvider来返回多个ValueProvider
return (IValueProvider) new CompositeValueProvider(factories);
}
}
通过ServicesContainer.GetValueProviderFactories()可以获取到HttpConfiguration注册的Factory.
而默认注册到ServicesContainer上的为DefaultServices
public DefaultServices(HttpConfiguration configuration)
{
//由于QueryStringValueProviderFactory先注册,所以查询字符串的方式优先级高于路由数据
this.SetMultiple<ValueProviderFactory>(new ValueProviderFactory[2]
{
(ValueProviderFactory) new QueryStringValueProviderFactory(),
(ValueProviderFactory) new RouteDataValueProviderFactory()
});
}
备注
文章中的代码并非完整WebAPI代码,一般是经过自己精简后的.
本篇内容使用MarkDown语法编辑
[Web API] Web API 2 深入系列(6) Model绑定(上)的更多相关文章
- [Web API] Web API 2 深入系列(7) Model绑定(下)
目录 ModelBinder ModelBinderProvider 不同类型的Model绑定 简单类型 复杂类型 其他类型 ModelBinder ModelBinder是Model绑定的核心. p ...
- ASP.NET Web API - ASP.NET MVC 4 系列
Web API 项目是 Windows 通信接口(Windows Communication Foundation,WCF)团队及其用户激情下的产物,他们想与 HTTP 深度整合.WCF ...
- 如何用Baas快速在腾讯云上开发小程序-系列1:搭建API & WEB WebSocket 服务器
版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/221059001487422606 来源:腾云阁 h ...
- Java web与web gis学习笔记(二)——百度地图API调用
系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...
- HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)
1.Web Storage HTML5除了Canvas元素之外,还有一个非常重要的功能那就是客户端本地保存数据的Web Storage功能. 以前都是用cookies保存用户名等简单信息. 但是c ...
- 我所理解的RESTful Web API [Web标准篇]
REST不是一个标准,而是一种软件应用架构风格.基于SOAP的Web服务采用RPC架构,如果说RPC是一种面向操作的架构风格,而REST则是一种面向资源的架构风格.REST是目前业界更为推崇的构建新一 ...
- 重构Web Api程序(Api Controller和Entity)续篇
昨天有写总结<重构Web Api程序(Api Controller和Entity)>http://www.cnblogs.com/insus/p/4350111.html,把一些数据交换的 ...
- web api写api接口时返回
web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返回为json呢,下面就介绍两种方法: 方法一:(改配置法) 找到Global.asax文件,在Applic ...
- Google Maps API Web Services
原文:Google Maps API Web Services 摘自:https://developers.google.com/maps/documentation/webservices/ Goo ...
随机推荐
- Android再学习
1.点击事件的几种实现方式 Button Btn1 = (Button)findViewById(R.id.button1);//获取按钮资源 Btn1.setOnClickListener(new ...
- 用 string 进行插入、替代、查找输出下标等操作
string s; s = "; string::iterator it; it = s.begin();//让s指向第一个元素 cout << s; system(" ...
- 基于 Jenkins 快速搭建持续集成环境
什么是持续集成 随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题.尤其是近些年来,敏捷(Agile) 在软件工程领域越来越红火 ...
- 你写的Try...Catch真的有必要么?
很多人喜欢用Try...Catch把每一个方法都包裹起来,可是真的有必要么? 为什么要这样做?我估计是大家被BUG吓怕了,生怕生产环境出现各种莫名其妙的错误,比如最经典的NullReferenceEx ...
- 谷歌chrome浏览器www.tradeadexchange.com广告弹窗跳转劫持病毒
近期大量网友出现chrome浏览器被劫持的情况,表现如下: · 点击(访问)任意网站任意链接均有概率弹出www.tradeadexchange.com. · ...
- C语言 · 特殊回文数
问题描述 123321是一个非常特殊的数,它从左边读和从右边读是一样的. 输入一个正整数n, 编程求所有这样的五位和六位十进制数,满足各位数字之和等于n . 输入格式 输入一行,包含一个正整数n. 输 ...
- Atitit 边缘检测原理attilax总结
Atitit 边缘检测原理attilax总结 1. 边缘检测的概念1 1.1. 边缘检测的用途1 2. 边缘检测方法分类1 3. 边缘检测的基本方法2 3.1. Roberts边缘检测算子2 3.2. ...
- JavaScript易错点
JavaScript知识点1.变量作用域 var a = 1;function test() { var a = 2; console.log(a); // 2} test(); ...
- rabbitMQ第五篇:Spring集成RabbitMQ
前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> ...
- Discuz门户首页关键词和描述显示“首页”的解决方法
Discuz社区在后台设置好门户标题.关键字.描述,更新缓存,发现用户登录状态下,门户首页的关键字和描述正常显示:但在游客状态下不显示,在某工具中查看到的情况如下: 现找到两种解决办法:(记得修改前备 ...