NameValueCollectionValueProvider采用一个NameValueCollection作为数据源DictionnaryValueProvider的数据源类型自然就是一个DictionnaryNameValueCollection和Dictionnary都是一个键值对的集合它们之间的不同之处在NameValueCollection运行元素具有相同的KeyDictionnary却要求元素的Key具有唯一性
一DictionaryValueProvider<TValue>DictionnaryValueProvider的类型全名为SystemWebMvcDictionaryValueProvider<TValue>如下面的代码片断所示DictionaryValueProvider<TValue>实现了IEnumerableValueProvider和IValueProvider接口构造函数接受一个IDictionary<string TValue>对象该对象表示作为数据源的字典定义在DictionaryValueProvider<TValue>中所有方法的逻辑与定义在NameValueCollectionValueProvider中的同名方法并没有本质区别
: public class DictionaryValueProvider<TValue> : IEnumerableValueProvider IValueProvider
: {
: public DictionaryValueProvider(IDictionary<string TValue> dictionary CultureInfo culture);
: public virtual bool ContainsPrefix(string prefix);
: public virtual IDictionary<string string> GetKeysFromPrefix(string prefix);
: public virtual ValueProviderResult GetValue(string key);
: }
二RouteDataValueProvider将当前路由数据作为数据源的RouteDataValueProvider继承自DictionaryValueProvider<TValue>如下面的代码片断所示基于当前Controller上下文构建的RouteDataValueProvider直接将表示当前路由数据的RouteData对象的Values属性(这是一个RouteValueDictionary对象)作为数据来源
: public sealed class RouteDataValueProvider : DictionaryValueProvider<object>
: {
: public RouteDataValueProvider(ControllerContext controllerContext) :
: base(controllerContextRouteDataValues CultureInfoInvariantCulture)
: {
: }
: }
三HttpFileCollectionValueProvider我们可以通过类型为file的输入元素进行文件的上传在表示HTTP请求的HttpRequestBase对象中上传文件通过只读属性Files表示从下面的代码片断所示该属性类型为HttpFileCollectionBase是一个元素类型为HttpPostedFileBase的集合
: public abstract class HttpRequestBase
: {
: public virtual HttpFileCollectionBase Files { get; }
: }
: public abstract class HttpFileCollectionBase : NameObjectCollectionBase ICollection IEnumerable
: {
: public virtual string[] AllKeys { get; }
: public override int Count { get; }
: public virtual HttpPostedFileBase this[int index] { get; }
: public virtual HttpPostedFileBase this[string name] { get; }
: }
: public abstract class HttpPostedFileBase
: {
: public virtual void SaveAs(string filename);
:
: public virtual int ContentLength { get; }
: public virtual string ContentType { get; }
: public virtual string FileName { get; }
: public virtual Stream InputStream { get; }
: }
用于处理上传文件的Action方法通常定义类型为HttpPostedFileBase及其列表的参数来表示上传的文件针对HttpPostedFileBase参数的Model绑定选用的数据就来源于表示当前请求的HttpRequestBase的Files属性而具体参数值的提供最终通过具有如下定义的HttpFileCollectionValueProvider来实现
: public sealed class HttpFileCollectionValueProvider : DictionaryValueProvider<HttpPostedFileBase[]>
: {
: public HttpFileCollectionValueProvider(ControllerContext controllerContext);
: }
如上面的代码所示HttpFileCollectionValueProvider继承自DictionaryValueProvider<TValue>泛型参数TValue的类型为HttpPostedFileBase数组这是因为在同一个表单中可以定义多个同名的文件输入元素所以在以文件元素名称作为Key的字典中字典元素的值自然就是一个HttpPostedFileBase的列表
为了让读者对HttpFileCollectionValueProvider采用的针对上传文件的值对象提供机制具有一个深刻的认识我们来进行一个简单的实例演示在通过Visual Studio的ASPNET MVC项目模板创建的空Web应用中创建一个具有如下定义的HomeController该Controller类型中定义了两个Action方法默认的Index方法会将默认的View呈现出来DisplayPostedFiles方法则通过创建的HttpFileCollectionValueProvider对象将上传文件的文件名称呈现出来
: public class HomeController : Controller
: {
: public ActionResult Index()
: {
: return View();
: }
: [HttpPost]
: public void DisplayPostedFiles()
: {
: HttpFileCollectionValueProvider valueProvider = new HttpFileCollectionValueProvider(ControllerContext);
: IEnumerable<HttpPostedFileBase> foo = (IEnumerable<HttpPostedFileBase>)valueProviderGetValue(foo)ConvertTo(typeof(IEnumerable<HttpPostedFileBase>));
: IEnumerable<HttpPostedFileBase> bar = (IEnumerable<HttpPostedFileBase>)valueProviderGetValue(bar)ConvertTo(typeof(IEnumerable<HttpPostedFileBase>));
:
: ResponseWrite(foo<br/>);
: foreach (var file in foo)
: {
: ResponseWrite(fileFileName + <br/>);
: }
:
: ResponseWrite(<br/>bar<br/>);
: foreach (var file in bar)
: {
: ResponseWrite(fileFileName + <br/>);
: }
: }
: }
在DisplayPostedFiles方法中我们针对当前Controller上下文创建HttpFileCollectionValueProvider对象然后分别将字符foo和bar作为Key得到两个HttpPostedFileBase对象列表并将它们的文件名打印出来下面的代码表示Action方法Index对应的View在一个针对Action方法DisplayPostedFiles的表单中我们定义了三个文件输入元素其中前两个名称为foo和bar
: @{
: using(HtmlBeginForm(DisplayPostedFilesHome
: FormMethodPostnew {enctype=multipart/formdata}))
: {
: <ul>
: <li>File : <input type=file name=foo/></li>
: <li>File : <input type=file name=foo/></li>
: <li>File : <input type=file name=bar/></li>
: </ul>
: <input type=submit value=提交 />
: }
: }
当我们运行该程序的时候浏览器上会出现一个包含三个文件输入元素和提交按钮的页面然后我们从本地选择任意三个文件(比如texttxttexttxt和texttxt)并点击提交按钮界面上会出现如下所示的输出结果
: foo
: texttxt
: texttxt
:
: bar
: texttxt
四ChildActionValueProvider子Action和普通意义上的Action的不同之处在于它不能用于响应来自客户端的请求而在某个View中被调用以生成某个部分的HTMLView中针对某个子Action方法的调用通过如下所示的HtmlHelper的扩展方法Action来实现
: public static class ChildActionExtensions
: {
: //其他成员
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName);
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName object routeValues);
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName string controllerName);
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName RouteValueDictionary routeValues);
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName string controllerName object routeValues);
: public static MvcHtmlString Action(this HtmlHelper htmlHelper string actionName string controllerName RouteValueDictionary routeValues);
: }
顾名思义ChildActionValueProvider专门服务于针对子Action方法参数的Model绑定如下面的代码片断所示ChildActionValueProvider依然是DictionaryValueProvider<TValue>的继承者不过这里的泛型参数类型Object那么在作为数据源的字典中具体的Key和Value究竟是怎样一个对象呢?
: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
: {
: public ChildActionValueProvider(ControllerContext controllerContext);
: public override ValueProviderResult GetValue(string key);
: }
当我们创建针对指定的Controller上下文创建一个ChildActionValueProvider对象时会获取描述针对该上下文路由信息的RouteData对象并将其Values属性表示的RouteValueDictionary对象作为其数据源这可以从如下所示的ChildActionValueProvider的构造函数定义看出来
: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
: {
: //其他成员
: public ChildActionValueProvider(ControllerContext controllerContext) : base(controllerContextRouteDataValues CultureInfoInvariantCulture)
: {
: }
: }
但是ChildActionValueProvider的GetValue方法获取的值却并不是简单地来源于构造时针对当前上下文的路由信息不然ChildActionValueProvider就和RouteDataValueProvider没有什么分别了实际上ChildActionValueProvider的GetValue方法获取的值来源于调用HtmHelper的扩展方法Action时通过参数routeValues指定的RouteValueDictionary
现在我们来简单介绍一下定义在ChildActionValueProvider的GetValue方法中的对象值的提供机制如下面的代码片断所示ChildActionValueProvider具有一个字符串类型的静态字段_childActionValuesKey当该类型第一次被加载时该字段被初始化成一个GUID
: public sealed class ChildActionValueProvider : DictionaryValueProvider<object>
: {
: //其他成员
: private static string _childActionValuesKey = GuidNewGuid()ToString();
: }
在某个View中通过HtmlHelper的扩展方法Action执行子Action方法时如果通过参数routeValues指定的RouteValueDictionary不为空会基于这个对象创建一个DictionaryValueProvider<TValue>对象然后将这个对象添加到通过routeValues表示的原始的RouteValueDictionary对象中对应的Key就是ChildActionValueProvider的静态属性_childActionValuesKey表示的GUID
这个RouteValueDictionary被进一步封装成表示请求上下文的RequestContext对象目标子Action所在的Controller会在该请求上下文中被激活而在Controller激活过程中表示Controller上下文的ControllerContext被创建出来毫无疑问它包含了之前创建的RouteValueDictionary对象而我们针对当前Controller上下文创建ChildActionValueProvider的时候指定的作为数据源的RouteValueDictionary对象就是这么一个对象
: @HtmlAction(XxxChildAction new {Foo= Bar = Baz=})
举个例子假设我们在某个View中如果如下的方式调用当前Controller的子Action方法 XxxChildAction并指定相应的路由数据(FooBar和Baz)最终作为ChildActionValueProvider数据源的Dictionary<stringobject>对象结构如下图所示
当调用ChildActionValueProvider的GetValue方法获取指定Key的值时实际上并不会直接根据指定的Key去获取对应的值而是根据通过其静态字段_childActionValuesKey值去获取对应的DictionaryValueProvider<object>对象然后再调用该对象的GetValue根据指定的Key去获得相应的值
五实例演示ChildActionValueProvider的值提供机制为了印证上面介绍的关于ChildActionValueProvider的值提供机制我们来演示一个简单的实例在进行演示之前有一个点需要作一下简单说明对于DictionaryValueProvider<TValue>对象来说最终作为其数据源的通过私有字段_values表示的一个Dictionary<string ValueProviderResult对象当我们调用GetValue方法是只需要根据指定的Key从该字典对象中返回相应的ValueProviderResult即可
: public class DictionaryValueProvider<TValue> : IEnumerableValueProvider IValueProvider
: {
: //其他成员
: private readonly Dictionary<string ValueProviderResult> _values;
: }
在通过Visual Studio的ASPNET MVC 项目模板创建的空Web应用中定义如下一个默认的HomeController默认的Action方法Index仅仅是将默认的View呈现出来而已并没有特别之处在另一个Action方法DisplayRouteData中我们名称分别为FooBar和Baz的三个路由数据篡改成abcijk和zyz然后根据当前Controller上下文创建一个ChildActionValueProvider对象并通过反射的方式获取通过它的私有字段_values表示的Dictionary<string ValueProviderResult对象
: public class HomeController : Controller
: {
: public ActionResult Index()
: {
: return View();
: }
:
: public ActionResult DisplayRouteData()
: {
: ControllerContextRouteDataValues[Foo] = abc;
: ControllerContextRouteDataValues[Bar] = ijk;
: ControllerContextRouteDataValues[Baz] = xyz;
:
: StringBuilder sb = new StringBuilder();
: ChildActionValueProvider valueProvider = new ChildActionValueProvider(ControllerContext);
: FieldInfo valuesField = typeof(DictionaryValueProvider<object>)GetField(_values BindingFlagsInstance|BindingFlagsNonPublic);
: Dictionary<string ValueProviderResult> values = (Dictionary<string ValueProviderResult>)valuesFieldGetValue(valueProvider);
: foreach (string key in valuesKeys)
: {
: sbAppend(stringFormat({}: {}<br/> key values[key]RawValue));
: DictionaryValueProvider<object> innerValueProvider = values[key]RawValue as DictionaryValueProvider<object>;
: if (innerValueProvider == null)
: {
: continue;
: }
: sbAppend(stringFormat( {}: {}<br/> Foo innerValueProviderGetValue(Foo)RawValue));
: sbAppend(stringFormat( {}: {}<br/> Bar innerValueProviderGetValue(Bar)RawValue));
: sbAppend(stringFormat( {}: {}<br/> Baz innerValueProviderGetValue(Baz)RawValue));
: }
:
: sbAppend(<br/>ChildActionValueProviderGetValue()<br/>);
: sbAppend(stringFormat({}: {}<br/> Foo valueProviderGetValue(Foo)RawValue));
: sbAppend(stringFormat({}: {}<br/> Bar valueProviderGetValue(Bar)RawValue));
: sbAppend(stringFormat({}: {}<br/> Baz valueProviderGetValue(Baz)RawValue));
:
: return Content(sbToString());
: }
: }
我们创建一个StringBuilder对象并将用于输出获取到的Dictionary<string ValueProviderResult>对象的Key和Value的HTML添加其中在进行遍历过程中如果ValueProviderResult对象的RawValue属性是一个DictionaryValueProvider<object>对象则调用其GetValue方法得到Key分别为FooBar和Baz的值相应的输出的HTML一并添加到StringBuilder中
在程序的最后我们直接调用ChildActionValueProvider的GetValue方法获取针对FooBar和Baz作为Key的值并将输出Key和Value的HTML添加到StringBuilder中最终针对生成的HTML字符串返回一个ContentResult对象如下所示的代码反映Action方法Index对应的View的定义在这里我们直接调用HtmlHelper的扩展方法Action执行定义在HomeController的Action方法DisplayRouteData并指定了相应的路由数据(FooBar和Baz)
: @HtmlAction(DisplayRouteData new { Foo = Bar = Baz = })
运行我们的程序会在浏览器中得到如下的输出结果我们可以从中看到针对于Controller和Action名称的路由数据和调用HtmlHelper扩展方法Action指定的数据数据均在作为ChildActionValueProvider数据源的字典对象中除此之外还具有一个DictionaryValueProvider<object>对象对应的Key是一个GUID这正是我们上面介绍的针对在HtmlHelper扩展方法Action中指定的路由数据创建的DictionaryValueProvider<object>对象而调用GetValue方法获取到的值最终是通过它提供的
: Foo: abc
: Bar: ijk
: Baz: xyz
: controller: Home
: action: DisplayRouteData
: fdfbababbba:
: SystemWebMvcDictionaryValueProvider`[SystemObject]
: Foo:
: Bar:
: Baz:
:
: ChildActionValueProviderGetValue()
: Foo:
: Bar:
: Baz:
六ValueProviderCollection类型ValueProviderCollection不仅仅表示一个ValueProvider对象的集合还作为一个单纯的ValueProvider来使用如下面的代码片断所示ValueProviderCollection不仅仅继承自Collection<IValueProvider>还同时实现了IValueProviderIEnumerableValueProvider和IUnvalidatedValueProvider三个接口
: public class ValueProviderCollection : Collection<IValueProvider> IUnvalidatedValueProvider IEnumerableValueProvider IValueProvider
: {
: public ValueProviderCollection();
: public ValueProviderCollection(IList<IValueProvider> list);
:
: public virtual bool ContainsPrefix(string prefix);
: public virtual IDictionary<string string> GetKeysFromPrefix(string prefix);
: public virtual ValueProviderResult GetValue(string key);
: public virtual ValueProviderResult GetValue(string key bool skipValidation);
: }
对于两个实现值提供机制的GetValue方法重载来说ValueProviderCollection会遍历集合直到找到一个GetValue方法返回值不为Null的ValueProvider而该返回值就是该方法的返回值如果所有ValueProvider的GetValue方法均返回Null则ValueProviderCollection的GetValue方法也为Null也就是说ValueProvider在集合中的先后次序决定了其使用优先级
如果有任何一个ValueProvider的ContainsPrefix方法返回True则ValueProviderCollection的ContainsPrefix也返回TrueGetKeysFromPrefix方法的逻辑与GetValue方法类似它会遍历作为集合中实现了IEnumerableValueProvider接口的所有ValueProvider对象并将指定的前缀作为参数调用ContainsPrefix方法如果返回值为True则直接返回GetKeysFromPrefix方法的结果否则返回一个空的Dictionary<string string>对象