首页 > 代码库 > ASP.NET MVC5学习笔记之Action参数模型绑定值提供体系

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 =http://www.mamicode.com/= 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 = http://www.mamicode.com/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 =http://www.mamicode.com/= 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 =http://www.mamicode.com/ (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     }
View Code

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     }
View Code

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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ GetDeserializedObject(controllerContext);
 58             if (jsonData =http://www.mamicode.com/= 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     }
View Code

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     }
View Code

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     }
View Code

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     }
View Code

 整体ValueProvider的继承体系统如下图: