• [Web API] Web API 2 深入系列(6) Model绑定(上)


    目录

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

    备注

  • 相关阅读:
    hadoop hbase 完全分布式
    在linux 下安装 solr 的IK中文分词
    Solr的多核配置和分布式
    vi命令
    SilverLigth的Chart不要图例(Legend)的方法
    【转载】Silverlight 应用 WCF RIA Services 在 IIS6 部署问题总结
    上下文菜单点击获取ContextMenu实例方法
    WPF中StringFormat的用法可以参照C#中string.Format的用法
    WPF的转换器中使用Brush应注意问题
    VS2010 SL4解决方案前端发现不了解决方案中WCF服务问题
  • 原文地址:https://www.cnblogs.com/neverc/p/6001891.html
Copyright © 2020-2023  润新知