目錄
-
解決什么問題
-
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語法編輯