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

備注


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM