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

  这一节我们关注模型绑定的值提供体系,先来介绍几个重要的接口

一. IValueProvider,接口定义如下:

 public interface IValueProvider
{ bool ContainsPrefix(string prefix); ValueProviderResult GetValue(string key);
}

从上面可以看出,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类型

  [Serializable]
public class ValueProviderResult
{
protected ValueProviderResult(); public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture); public string AttemptedValue { get; protected set; } public CultureInfo Culture { get; protected set; } public object RawValue { get; protected set; } public object ConvertTo(Type type); public virtual object ConvertTo(Type type, CultureInfo culture);
}

AttemptedValue表示从值的字符串表示,RawValue 表示值的原始值. 同时看到定义类型转换接口。 这里转换的代码值得研究一下:

 public virtual object ConvertTo(Type type, CultureInfo culture)
{
if (type == null)
{
throw new ArgumentNullException("type");
} CultureInfo cultureToUse = culture ?? Culture;
return UnwrapPossibleArrayType(cultureToUse, RawValue, type);
} private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
{
if (value == null || destinationType.IsInstanceOfType(value))
{
return value;
} // array conversion results in four cases, as below
Array valueAsArray = value as Array;
if (destinationType.IsArray)
{
Type destinationElementType = destinationType.GetElementType();
if (valueAsArray != null)
{
// case 1: both destination + source type are arrays, so convert each element
IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
for (int i = ; i < valueAsArray.Length; i++)
{
converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
}
return converted;
}
else
{
// case 2: destination type is array but source is single element, so wrap element in array + convert
object element = ConvertSimpleType(culture, value, destinationElementType);
IList converted = Array.CreateInstance(destinationElementType, );
converted[] = element;
return converted;
}
}
else if (valueAsArray != null)
{
// case 3: destination type is single element but source is array, so extract first element + convert
if (valueAsArray.Length > )
{
value = valueAsArray.GetValue();
return ConvertSimpleType(culture, value, destinationType);
}
else
{
// case 3(a): source is empty array, so can't perform conversion
return null;
}
}
// case 4: both destination + source type are single elements, so convert
return ConvertSimpleType(culture, value, destinationType);
}

1. 如果值是目标类型的实例,直接返回

2. 尝试转换为数组,这里列了4种情况。

3. 单一类型转换

再来看一下ConvertSimpleType的代码:

 private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
{
if (value == null || destinationType.IsInstanceOfType(value))
{
return value;
} // if this is a user-input value but the user didn't type anything, return no value
string valueAsString = value as string;
if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString))
{
return null;
} // In case of a Nullable object, we extract the underlying type and try to convert it.
Type underlyingType = Nullable.GetUnderlyingType(destinationType); if (underlyingType != null)
{
destinationType = underlyingType;
} // String doesn't provide convertibles to interesting types, and thus it will typically throw rather than succeed.
if (valueAsString == null)
{
// If the source type implements IConvertible, try that first
IConvertible convertible = value as IConvertible;
if (convertible != null)
{
try
{
return convertible.ToType(destinationType, culture);
}
catch
{
}
}
} // Last resort, look for a type converter
TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
bool canConvertFrom = converter.CanConvertFrom(value.GetType());
if (!canConvertFrom)
{
converter = TypeDescriptor.GetConverter(value.GetType());
}
if (!(canConvertFrom || converter.CanConvertTo(destinationType)))
{
// EnumConverter cannot convert integer, so we verify manually
if (destinationType.IsEnum && value is int)
{
return Enum.ToObject(destinationType, (int)value);
} string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_NoConverterExists,
value.GetType().FullName, destinationType.FullName);
throw new InvalidOperationException(message);
} try
{
object convertedValue = (canConvertFrom)
? converter.ConvertFrom(null /* context */, culture, value)
: converter.ConvertTo(null /* context */, culture, value, destinationType);
return convertedValue;
}
catch (Exception ex)
{
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ValueProviderResult_ConversionThrew,
value.GetType().FullName, destinationType.FullName);
throw new InvalidOperationException(message, ex);
}
}

  这里也考虑了几种情况转换
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的定义

 public static class ValueProviderFactories
{
private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection()
{
new ChildActionValueProviderFactory(),
new FormValueProviderFactory(),
new JsonValueProviderFactory(),
new RouteDataValueProviderFactory(),
new QueryStringValueProviderFactory(),
new HttpFileCollectionValueProviderFactory(),
}; public static ValueProviderFactoryCollection Factories
{
get { return _factories; }
}
}

可以了解到系统内置几种ValueProviderFactory, 下面依次来了解.

a. ChildActionValueProviderFactory 创建ChildActionValueProvider, 提供在HtmlHelper.Action方法附加的路由信息

 public sealed class ChildActionValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} return new ChildActionValueProvider(controllerContext);
}
}

b. FormValueProviderFactory 创建FormValueProvider , 提供请求表单的值

 public sealed class FormValueProviderFactory : ValueProviderFactory
{
private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor; public FormValueProviderFactory()
: this(null)
{
} // For unit testing
internal FormValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
{
_unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated));
} public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} return new FormValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
}
}

c. JsonValueProviderFactory 处理json请求类型(application/json), 创建DictionaryValueProvider,

  public sealed class JsonValueProviderFactory : ValueProviderFactory
{
private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
{
IDictionary<string, object> d = value as IDictionary<string, object>;
if (d != null)
{
foreach (KeyValuePair<string, object> entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
} IList l = value as IList;
if (l != null)
{
for (int i = ; i < l.Count; i++)
{
AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
}
return;
} // primitive
backingStore.Add(prefix, value);
} private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
// not JSON request
return null;
} StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
// no JSON data
return null;
} JavaScriptSerializer serializer = new JavaScriptSerializer();
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
} public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} object jsonData = GetDeserializedObject(controllerContext);
if (jsonData == null)
{
return null;
} Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
EntryLimitedDictionary backingStoreWrapper = new EntryLimitedDictionary(backingStore);
AddToBackingStore(backingStoreWrapper, String.Empty, jsonData);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
} private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
} private static string MakePropertyKey(string prefix, string propertyName)
{
return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
} private class EntryLimitedDictionary
{
private static int _maximumDepth = GetMaximumDepth();
private readonly IDictionary<string, object> _innerDictionary;
private int _itemCount = ; public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
{
_innerDictionary = innerDictionary;
} public void Add(string key, object value)
{
if (++_itemCount > _maximumDepth)
{
throw new InvalidOperationException(MvcResources.JsonValueProviderFactory_RequestTooLarge);
} _innerDictionary.Add(key, value);
} private static int GetMaximumDepth()
{
NameValueCollection appSettings = ConfigurationManager.AppSettings;
if (appSettings != null)
{
string[] valueArray = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
if (valueArray != null && valueArray.Length > )
{
int result;
if (Int32.TryParse(valueArray[], out result))
{
return result;
}
}
} return ; // Fallback default
}
}
}

d. RouteDataValueProviderFactory 创建RouteDataValueProvider, 提供路由信息相关值

  public sealed class RouteDataValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} return new RouteDataValueProvider(controllerContext);
}
}

e. QueryStringValueProviderFactory 创建QueryStringValueProvider, 提供查询字符串值

 public sealed class QueryStringValueProviderFactory : ValueProviderFactory
{
private readonly UnvalidatedRequestValuesAccessor _unvalidatedValuesAccessor; public QueryStringValueProviderFactory()
: this(null)
{
} // For unit testing
internal QueryStringValueProviderFactory(UnvalidatedRequestValuesAccessor unvalidatedValuesAccessor)
{
_unvalidatedValuesAccessor = unvalidatedValuesAccessor ?? (cc => new UnvalidatedRequestValuesWrapper(cc.HttpContext.Request.Unvalidated));
} public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} return new QueryStringValueProvider(controllerContext, _unvalidatedValuesAccessor(controllerContext));
}
}

f. HttpFileCollectionValueProviderFactory创建HttpFileCollectionValueProvider上传文件值提供

 public sealed class HttpFileCollectionValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
} return new HttpFileCollectionValueProvider(controllerContext);
}
}

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

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

上一篇:VS2017无法进入安装界面问题的解决方法


下一篇:Video Target Tracking Based on Online Learning—TLD单目标跟踪算法详解