这一节我们关注模型绑定的值提供体系,先来介绍几个重要的接口
一. 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 }
b. FormValueProviderFactory 创建FormValueProvider , 提供请求表单的值
1 public sealed class FormValueProviderFactory : ValueProviderFactory
2 {
3 private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor;
4
5 public FormValueProviderFactory()
6 : this(null)
7 {
8 }
9
10 // For unit testing
11 internal FormValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
12 {
13 _unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated));
14 }
15
16 public override IValueProvider GetValueProvider(ControllerContext controllerContext)
17 {
18 if (controllerContext == null)
19 {
20 throw new ArgumentNullException("controllerContext");
21 }
22
23 return new FormValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
24 }
25 }
c. JsonValueProviderFactory 处理json请求类型(application/json),
创建DictionaryValueProvider,
1 public sealed class JsonValueProviderFactory : ValueProviderFactory
2 {
3 private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
4 {
5 IDictionary<string, object> d = value as IDictionary<string, object>;
6 if (d != null)
7 {
8 foreach (KeyValuePair<string, object> entry in d)
9 {
10 AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
11 }
12 return;
13 }
14
15 IList l = value as IList;
16 if (l != null)
17 {
18 for (int i = 0; i < l.Count; i++)
19 {
20 AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
21 }
22 return;
23 }
24
25 // primitive
26 backingStore.Add(prefix, value);
27 }
28
29 private static object GetDeserializedObject(ControllerContext controllerContext)
30 {
31 if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
32 {
33 // not JSON request
34 return null;
35 }
36
37 StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
38 string bodyText = reader.ReadToEnd();
39 if (String.IsNullOrEmpty(bodyText))
40 {
41 // no JSON data
42 return null;
43 }
44
45 JavaScriptSerializer serializer = new JavaScriptSerializer();
46 object jsonData = serializer.DeserializeObject(bodyText);
47 return jsonData;
48 }
49
50 public override IValueProvider GetValueProvider(ControllerContext controllerContext)
51 {
52 if (controllerContext == null)
53 {
54 throw new ArgumentNullException("controllerContext");
55 }
56
57 object jsonData = GetDeserializedObject(controllerContext);
58 if (jsonData == null)
59 {
60 return null;
61 }
62
63 Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
64 EntryLimitedDictionary backingStoreWrapper = new EntryLimitedDictionary(backingStore);
65 AddToBackingStore(backingStoreWrapper, String.Empty, jsonData);
66 return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
67 }
68
69 private static string MakeArrayKey(string prefix, int index)
70 {
71 return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
72 }
73
74 private static string MakePropertyKey(string prefix, string propertyName)
75 {
76 return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
77 }
78
79 private class EntryLimitedDictionary
80 {
81 private static int _maximumDepth = GetMaximumDepth();
82 private readonly IDictionary<string, object> _innerDictionary;
83 private int _itemCount = 0;
84
85 public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
86 {
87 _innerDictionary = innerDictionary;
88 }
89
90 public void Add(string key, object value)
91 {
92 if (++_itemCount > _maximumDepth)
93 {
94 throw new InvalidOperationException(MvcResources.JsonValueProviderFactory_RequestTooLarge);
95 }
96
97 _innerDictionary.Add(key, value);
98 }
99
100 private static int GetMaximumDepth()
101 {
102 NameValueCollection appSettings = ConfigurationManager.AppSettings;
103 if (appSettings != null)
104 {
105 string[] valueArray = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
106 if (valueArray != null && valueArray.Length > 0)
107 {
108 int result;
109 if (Int32.TryParse(valueArray[0], out result))
110 {
111 return result;
112 }
113 }
114 }
115
116 return 1000; // Fallback default
117 }
118 }
119 }
d. RouteDataValueProviderFactory 创建RouteDataValueProvider, 提供路由信息相关值
1 public sealed class RouteDataValueProviderFactory : 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 RouteDataValueProvider(controllerContext);
11 }
12 }
e. QueryStringValueProviderFactory 创建QueryStringValueProvider,
提供查询字符串值
1 public sealed class QueryStringValueProviderFactory : ValueProviderFactory
2 {
3 private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor;
4
5 public QueryStringValueProviderFactory()
6 : this(null)
7 {
8 }
9
10 // For unit testing
11 internal QueryStringValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
12 {
13 _unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated));
14 }
15
16 public override IValueProvider GetValueProvider(ControllerContext controllerContext)
17 {
18 if (controllerContext == null)
19 {
20 throw new ArgumentNullException("controllerContext");
21 }
22
23 return new QueryStringValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
24 }
25 }
f. HttpFileCollectionValueProviderFactory创建HttpFileCollectionValueProvider上传文件值提供
1 public sealed class HttpFileCollectionValueProviderFactory : 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 HttpFileCollectionValueProvider(controllerContext);
11 }
12 }
整体ValueProvider的继承体系统如下图: