目录

  1. 解决什么问题

  2. Model元数据解析

    • 复杂类型
  3. ValueProvider

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

请求地址:

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

备注

[Web API] Web API 2 深入系列(6) Model绑定(上)的更多相关文章

  1. [Web API] Web API 2 深入系列(7) Model绑定(下)

    目录 ModelBinder ModelBinderProvider 不同类型的Model绑定 简单类型 复杂类型 其他类型 ModelBinder ModelBinder是Model绑定的核心. p ...

  2. ASP.NET Web API - ASP.NET MVC 4 系列

           Web API 项目是 Windows 通信接口(Windows Communication Foundation,WCF)团队及其用户激情下的产物,他们想与 HTTP 深度整合.WCF ...

  3. 如何用Baas快速在腾讯云上开发小程序-系列1:搭建API & WEB WebSocket 服务器

    版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/221059001487422606 来源:腾云阁 h ...

  4. Java web与web gis学习笔记(二)——百度地图API调用

    系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...

  5. HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)

    1.Web Storage HTML5除了Canvas元素之外,还有一个非常重要的功能那就是客户端本地保存数据的Web Storage功能. 以前都是用cookies保存用户名等简单信息.   但是c ...

  6. 我所理解的RESTful Web API [Web标准篇]

    REST不是一个标准,而是一种软件应用架构风格.基于SOAP的Web服务采用RPC架构,如果说RPC是一种面向操作的架构风格,而REST则是一种面向资源的架构风格.REST是目前业界更为推崇的构建新一 ...

  7. 重构Web Api程序(Api Controller和Entity)续篇

    昨天有写总结<重构Web Api程序(Api Controller和Entity)>http://www.cnblogs.com/insus/p/4350111.html,把一些数据交换的 ...

  8. web api写api接口时返回

    web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返回为json呢,下面就介绍两种方法: 方法一:(改配置法) 找到Global.asax文件,在Applic ...

  9. Google Maps API Web Services

    原文:Google Maps API Web Services 摘自:https://developers.google.com/maps/documentation/webservices/ Goo ...

随机推荐

  1. 输入框三种输入方式(selenium webdriver 干货)

    在机票预定的页面,输入出发城市和到达城市输入框的时候, 发现直接使用sendkeys不好使, 大部分情况出现输入某城市后没有输入进去, 经过几天的研究,发现可以采取三种方式: 1. 先点击输入框,待弹 ...

  2. ex3-数字和数字计算

    代码: print("I will now count my chickens:") print("hens", 25+30/6)print("Roo ...

  3. 基于Redis的开源分布式服务Codis

    Redis在豌豆荚的使用历程--单实例==>多实例,业务代码中做sharding==>单个Twemproxy==>多个Twemproxy==>Codis,豌豆荚自己开发的分布式 ...

  4. ENode框架Conference案例分析系列之 - 订单处理减库存的设计

    前言 前面的文章,我介绍了Conference案例的业务.上下文划分.领域模型.架构,以及代码整体流程.接下来想针对案例中一些重要的场景,分别做进一步的分析.本文想先介绍一下Conference案例的 ...

  5. 借助 Lucene.Net 构建站内搜索引擎(下)

    前言:上一篇我们学习了Lucene.Net的基本概念.分词以及实现了一个最简单的搜索引擎,这一篇我们开始开发一个初具规模的站内搜索项目,通过开发站内搜索模块,我们可以方便地在项目中集成站内搜索功能.本 ...

  6. Python读取二进制文件

    import os import sys import socket mypath = sys.argv[1] if not os.path.exists(mypath): print "T ...

  7. Oracle 中 union 和union all 的简单使用说明

    1.刚刚工作不久,经常接触oracle,但是对oracle很多东西都不是很熟.今天我们来了解一下union和union all的简单使用说明.Union(union all): 指令的目的是将两个 S ...

  8. [大数据之Sqoop] —— 什么是Sqoop?

    介绍 sqoop是一款用于hadoop和关系型数据库之间数据导入导出的工具.你可以通过sqoop把数据从数据库(比如mysql,oracle)导入到hdfs中:也可以把数据从hdfs中导出到关系型数据 ...

  9. iOS-开发者相关的几种证书

    目录 引言 写在前面 一App IDbundle identifier 二设备Device 三开发证书Certificates 证书的概念 数字证书的概念 iOS开发证书 iOS开发证书的根证书 申请 ...

  10. Uiautomator 2.0之UiWatcher类学习小记

    1. 主要功能 使用此方法可以处理中断问题,从而保证测试用例的正常运行. 2. 相关API API 说明 registerWatcher (String name, UiWatcher watcher ...