ASP.NET MVC5学习笔记之Action参数模型绑定值提供体系
这一节我们关注模型绑定的值提供体系,先来介绍几个重要的接口
一. 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的继承体系统如下图: