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的继承体系统如下图:

时间: 2024-10-26 20:55:04

ASP.NET MVC5学习笔记之Action参数模型绑定值提供体系的相关文章

mvc action 参数绑定——值提供器【学习笔记】

每次http请求的各种数据(表单数据.url的数据.路由数据等等)都保存在不同的IValueProvider接口的实现类中. 而IValueProvider接口的实现类是通过ValueProviderFactory创建的. 在mvc中原生的ValueProviderFactory有六种: ChildActionValueProviderFactory:根据给定的Controller上下文创建一个ChildActionValueProvider对象. FormValueProviderFactor

ASP.NET MVC5学习笔记01

由于之前在项目中也使用MVC进行开发,但是具体是那个版本就不是很清楚了,但是我觉得大体的思想是相同的,只是版本高的在版本低的基础上增加了一些更加方便操作的东西.下面是我学习ASP.NET MVC5高级编程(5)的一些知识笔记,有些简单的操作没有进行记录,一些知识点就使用思维导图来写出大体的知识架构. MVC5是完全Bin部署的,也就是说我们最后发布的时候只是将Bin文件下面的应用程序的程序集发布就可以,对于服务器来说只需要有.NET 4.5就可以进行安装了. 一条至理名言"约定优于配置"

asp.net MVC5 学习笔记(一)

Html.ActionLink("linkText","actionName") 该重载的第一个参数是该链接要显示的文字,第二个参数是对应的控制器的方法,默认控制器为当前页面的控制器, 如果当前页面的控制器为Products,则 Html.ActionLink("detail","Detail") 则会生成 <a href="/Products/Detail">all</a> Htm

Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim

Identity学习笔记 Asp.Net Identity学习笔记+MVC5默认项目解析_基础用法 Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim Identity学习笔记授权以角色授权IdentityRoleRoleManager基于声明的(Claims)IPrincipalIIdentityCalimsIdentityClaim用户登入用户授权其他细节Claim Type命名空间 授权 最常用的授权就是给Controller或Action打上[Authori

Asp.Net Identity学习笔记+MVC5默认项目解析_第三方登入&授权总结

Identity学习笔记 Asp.Net Identity学习笔记+MVC5默认项目解析_基础用法 Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim Asp.Net Identity学习笔记+MVC5默认项目解析_第三方登入&授权总结 Identity学习笔记第三方登入配置登入案例登入技术总结本地,已登入本地,未登入第三方登入 第三方登入 本文介绍Identity的第三方登入技术.到目前为止只介绍了CookieAuthentication这种授权方式,即浏览

[Asp.net本质论]学习笔记(1)

引言 之前大部分时间,一直在学c#,打算将asp.net本质论好好学习一下,之前虽然已经看了两边了,总感觉看过,没做笔记等于白看了,一点印象也没.打算将书中的代码,自己实现一下,在敲代码时要一直反思,为什么作者那样实现,如果是自己该如何实现? web应用程序 资源的地址——通用资源标识符(URI) 我们在浏览器地址栏中输入的内容统称为通用资源标识符(Universal Resource Identifier,URI),它有很多种形式,在web中我们通常使用统一资源定位符(Uniform Reso

JavaScript学习笔记——js变量的布尔值

typeof(1): numbertypeof(NaN): numbertypeof(Number.MIN_VALUE): numbertypeof(Infinity): numbertypeof("123"): stringtypeof(true): booleantypeof(window): objecttypeof(Array()): objecttypeof(function(){}): functiontypeof(document): objecttypeof(null)

iOS学习笔记(6)键值编码——KVC

在KVC编程方式中,无论调用setValue:forKey:方法,还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性,其中forKey:标签用户传入属性名的. 对于setValue:属性值[email protected]“name”;代码,底层的执行机制如下. (1)程序优先考虑调用“setName:属性值;”代码通过setter方法完成设置. (2)如果该类没有setName:方法,KVC机制会搜索该类名为_name的成员变量,无论该成员变量是在类接口部分定义

Asp.Net Identity学习笔记+MVC5默认项目解析_基础用法

前言ASP.NET Identity特性Identity包基本IdentityUserUserManager准备工作ApplicationDbContextApplicationUserManager注册案例登入案例用户信息验证用户名密码验证器自定义验证器 前言 本文简单介绍Identity的使用,使用的案例是基于默认的Mvc5项目(只勾选MVC,不勾选WebApi).读者可以拿着项目源码对照地看. ASP.NET Identity特性 One ASP.NET Identity 系统 更容易加入