这一节我们关注模型绑定的值提供体系,先来介绍几个重要的接口
一. IValueProvider,接口定义如下:
1 public interface IValueProvider 2 { 3 4 bool ContainsPrefix(string prefix); 5 6 ValueProviderResult GetValue(string key); 7 }
从上面可以看出,IValueProvider定义了两个方法, 一个是检测是否包含指定的前缀,一个是通过指定的Key获取查询结果.这里前缀的概念主要是针对复杂类型的绑定,复杂类型包含属性,而属性的类型又是一个复杂类型,这样一层层下来,当我们在绑定类型的属性时,我们必须有一种机制确定该属性的值是从属于某个对象的,这就有了前缀的概念。系统定义了以下几种类型的绑定语法:
1.简单类型
prefix == 变量的名称
2. 复杂类型
prefix 变量名称
prefix.Name
prefix.Address.Name
3. 数组
a. 同名数据项
多个同名数据项, ValueProviderResult直接转换成数组
b. 基于索引的数组绑定
[0].Name
[0].PhoneNo
[0].Email
[1].Name
[1].PhoneNo
[1].Email
4,集合IEnumerable<T> 与数组类似
5. 字典
[0].Key
[0].Value.Name
[0].Value.EmailAddress
[1].Key
[2].Value.Name
[3].Value.EmailAddress
二. ValueProviderResult类型
1 [Serializable] 2 public class ValueProviderResult 3 { 4 protected ValueProviderResult(); 5 6 public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture); 7 8 public string AttemptedValue { get; protected set; } 9 10 public CultureInfo Culture { get; protected set; } 11 12 public object RawValue { get; protected set; } 13 14 public object ConvertTo(Type type); 15 16 public virtual object ConvertTo(Type type, CultureInfo culture); 17 }
AttemptedValue表示从值的字符串表示,RawValue 表示值的原始值. 同时看到定义类型转换接口。 这里转换的代码值得研究一下:
1 public virtual object ConvertTo(Type type, CultureInfo culture) 2 { 3 if (type == null) 4 { 5 throw new ArgumentNullException("type"); 6 } 7 8 CultureInfo cultureToUse = culture ?? Culture; 9 return UnwrapPossibleArrayType(cultureToUse, RawValue, type); 10 } 11 12 private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType) 13 { 14 if (value == null || destinationType.IsInstanceOfType(value)) 15 { 16 return value; 17 } 18 19 // array conversion results in four cases, as below 20 Array valueAsArray = value as Array; 21 if (destinationType.IsArray) 22 { 23 Type destinationElementType = destinationType.GetElementType(); 24 if (valueAsArray != null) 25 { 26 // case 1: both destination + source type are arrays, so convert each element 27 IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length); 28 for (int i = 0; i < valueAsArray.Length; i++) 29 { 30 converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType); 31 } 32 return converted; 33 } 34 else 35 { 36 // case 2: destination type is array but source is single element, so wrap element in array + convert 37 object element = ConvertSimpleType(culture, value, destinationElementType); 38 IList converted = Array.CreateInstance(destinationElementType, 1); 39 converted[0] = element; 40 return converted; 41 } 42 } 43 else if (valueAsArray != null) 44 { 45 // case 3: destination type is single element but source is array, so extract first element + convert 46 if (valueAsArray.Length > 0) 47 { 48 value = valueAsArray.GetValue(0); 49 return ConvertSimpleType(culture, value, destinationType); 50 } 51 else 52 { 53 // case 3(a): source is empty array, so can't perform conversion 54 return null; 55 } 56 } 57 // case 4: both destination + source type are single elements, so convert 58 return ConvertSimpleType(culture, value, destinationType); 59 }
1. 如果值是目标类型的实例,直接返回
2. 尝试转换为数组,这里列了4种情况。
3. 单一类型转换
再来看一下ConvertSimpleType的代码:
1 private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType) 2 { 3 if (value == null || destinationType.IsInstanceOfType(value)) 4 { 5 return value; 6 } 7 8 // if this is a user-input value but the user didn't type anything, return no value 9 string valueAsString = value as string; 10 if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString)) 11 { 12 return null; 13 } 14 15 // In case of a Nullable object, we extract the underlying type and try to convert it. 16 Type underlyingType = Nullable.GetUnderlyingType(destinationType); 17 18 if (underlyingType != null) 19 { 20 destinationType = underlyingType; 21 } 22 23 // String doesn't provide convertibles to interesting types, and thus it will typically throw rather than succeed. 24 if (valueAsString == null) 25 { 26 // If the source type implements IConvertible, try that first 27 IConvertible convertible = value as IConvertible; 28 if (convertible != null) 29 { 30 try 31 { 32 return convertible.ToType(destinationType, culture); 33 } 34 catch 35 { 36 } 37 } 38 } 39 40 // Last resort, look for a type converter 41 TypeConverter converter = TypeDescriptor.GetConverter(destinationType); 42 bool canConvertFrom = converter.CanConvertFrom(value.GetType()); 43 if (!canConvertFrom) 44 { 45 converter = TypeDescriptor.GetConverter(value.GetType()); 46 } 47 if (!(canConvertFrom || converter.CanConvertTo(destinationType))) 48 { 49 // EnumConverter cannot convert integer, so we verify manually 50 if (destinationType.IsEnum && value is int) 51 { 52 return Enum.ToObject(destinationType, (int)value); 53 } 54 55 string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_NoConverterExists, 56 value.GetType().FullName, destinationType.FullName); 57 throw new InvalidOperationException(message); 58 } 59 60 try 61 { 62 object convertedValue = (canConvertFrom) 63 ? converter.ConvertFrom(null /* context */, culture, value) 64 : converter.ConvertTo(null /* context */, culture, value, destinationType); 65 return convertedValue; 66 } 67 catch (Exception ex) 68 { 69 string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_ConversionThrew, 70 value.GetType().FullName, destinationType.FullName); 71 throw new InvalidOperationException(message, ex); 72 } 73 }
这里也考虑了几种情况转换
1. 值是目标类型的实例直接返回
2. 值是空串返回null
3. 可空类型取其下的真正类型
4. 尝试利用IConvertible转换
5. 利用目标类型和值类型的TypeConverter
6. 检查目标类型是Enum和值类型是否int
三. ValueProviderFactory
public abstract class ValueProviderFactory { public abstract IValueProvider GetValueProvider(ControllerContext controllerContext); }
表示的值提供对象的创健工厂.
四. ValueProvider创建工厂和具体ValueProvider介绍
ValueProvider的调用入口是Controller.ValueProvider属性,它是调用ValueProviderFactories.Factories.GetValueProvider()返回值,看看ValueProviderFactories的定义
1 public static class ValueProviderFactories 2 { 3 private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection() 4 { 5 new ChildActionValueProviderFactory(), 6 new FormValueProviderFactory(), 7 new JsonValueProviderFactory(), 8 new RouteDataValueProviderFactory(), 9 new QueryStringValueProviderFactory(), 10 new HttpFileCollectionValueProviderFactory(), 11 }; 12 13 public static ValueProviderFactoryCollection Factories 14 { 15 get { return _factories; } 16 } 17 }
可以了解到系统内置几种ValueProviderFactory, 下面依次来了解.
a. ChildActionValueProviderFactory 创建ChildActionValueProvider, 提供在HtmlHelper.Action方法附加的路由信息
1 public sealed class ChildActionValueProviderFactory : ValueProviderFactory 2 { 3 public override IValueProvider GetValueProvider(ControllerContext controllerContext) 4 { 5 if (controllerContext == null) 6 { 7 throw new ArgumentNullException("controllerContext"); 8 } 9 10 return new ChildActionValueProvider(controllerContext); 11 } 12 }