好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ASP.NET MVC以ValueProvider为核心的值提供系统: NameValueColle

ASP.NET MVC以ValueProvider为核心的值提供系统: NameValueCollectionValueProvider

在进行Model绑定过程中,需要根据基于Action方法参数的绑定上下文从请求数据中提取相应的数据以提供相应的数据。具体来说,Model绑定的数据具有多个来源,可能来源于Post的表单或者JSON字符串,或者来源于当前的路由数据,也可能来源于请求地址的插叙字符串。ASP.NET MVC将这种基于不同数据来源的数据获取/提供机制实现在一个叫做ValueProvider的组件中。[本文已经同步到《 How ASP.NET MVC Works? 》中]

目录 
一、IValueProvider与ValueProviderResult 
二、NameValueCollectionValueProvider 
三、两种前缀形式 
四、实例演示:返回指定前缀的Key 
五、FormValueProvider与QueryStringValueProvider

一、IValueProvider与ValueProviderResult

一般来讲,一个ValueProvider采用的数据源是一个字典类型的数据结构,我们通过它从这个字典中获取一个Key与当前绑定上下文匹配的值。ValueProvider实现了具有如下定义的接口 IValueProvider ,GetValue方法根据指定的Key从数据源中获取对应的值对象,这个Key是基于当前绑定上下文的。这个Key和存在于数据源中对应数据条目的Key可能并非完全一致,后者可能在前者基础上添加相应的前缀,而ContainsPrefix方法用于判断数据源字典的Key是否具有指定的前缀。

    1:   public   interface  IValueProvider
    2:  {
    3:       bool  ContainsPrefix( string  prefix);
    4:      ValueProviderResult GetValue( string  key);
    5:  }

IValueProvider的GetValue返回的是一个 ValueProviderResult 对象,我们可以将ValueProviderResult看成是对ValueProvider提供对象的封装。如下面的代码片断所示,ValueProviderResult具有三个只读属性,其中RawValue表示原始的值对象。而AttemptedValue表示以值对象的字符串表示,该属性主要用于显示。

    1:  [Serializable]
    2:   public   class  ValueProviderResult
    3:  {    
    4:       public  ValueProviderResult( object  rawValue,  string  attemptedValue, CultureInfo culture);    
    5:       public   object  ConvertTo(Type type);
    6:       public   virtual   object  ConvertTo(Type type, CultureInfo culture);
    7:   
    8:       public   string  AttemptedValue { get; }
    9:       public  CultureInfo Culture { get; }
   10:       public   object  RawValue { get; }
   11:  }

ValueProviderResult提供了两个ConvertTo方法重载以实现向指定目标类型的转换。某些类型的格式化行为依赖于相应的语言文化(比如时间、日期和货币等),而这个辅助格式湖的语言文化信息通过Culture属性表示。其中第一个ValueProviderResult方法重载通过属性Culture表示的语言文化进行类型转化。

二、NameValueCollectionValueProvider

前面已经说过,Model数据源一般具有类似于字典的结构,而NameValueCollection可以表示为Key不具有唯一性的字典,将NameValueCollection对象作为数据源的ValueProvider通过具有如下定义的 NameValueCollectionValueProvider 类型表示。表示数据源的NameValueCollection对象在构造函数中指定,构造函数的另一个CultureInfo类型的参数表示服务于数据转换的语言文化信息。

    1:   public   class  NameValueCollectionValueProvider : IUnvalidatedValueProvider, IEnumerableValueProvider, IValueProvider
    2:  {   
    3:       //其他成员 
    4:       public  NameValueCollectionValueProvider(NameValueCollection collection,  CultureInfo culture);
    5:   
    6:       public   virtual   bool  ContainsPrefix( string  prefix);
    7:       public   virtual  IDictionary< string ,  string > GetKeysFromPrefix( string  prefix);
    8:       public   virtual  ValueProviderResult GetValue( string  key);
    9:       public   virtual  ValueProviderResult GetValue( string  key,  bool  skipValidation);  
   10:  }
   11:   
   12:   public   interface  IEnumerableValueProvider : IValueProvider
   13:  {
   14:      IDictionary< string ,  string > GetKeysFromPrefix( string  prefix);
   15:  }
   16:   
   17:   public   interface  IUnvalidatedValueProvider : IValueProvider
   18:  {
   19:      ValueProviderResult GetValue( string  key,  bool  skipValidation);
   20:  }

从上面的代码片断我们可以看到,除了IValueProvider接口,NameValueCollectionValueProvider还实现了IEnumerableValueProvider和IUnvalidatedValueProvider两个接口。顾名思义,IEnumerableValueProvider主要用于针对目标类型为集合的数据提供,方法GetKeysFromPrefix以一字典的形式返回具有指定前缀的Key。在默认的情况下,在进行数据提供的同时会对数据进行验证,而IUnvalidatedValueProvider接口提供了一个额外的GetValue方法是我们可以忽略对数据的验证。

三、两种前缀形式

辅助实现Model绑定的数据提供机制是以Model元数据为基础的,通过《 初识Model元数据 》我们知道用于描述一个复杂数据类型的Model元数据具有一个树型的层次化结构,而作为数据源的NameValueCollection却是一个“扁平”的结构,两者之前的匹配通过前缀来表示。举个简单的例子,假设通过NameValueCollectionValueProvider提供对象的目标类型为具有如下定义的Contact。表示联系地址的属性是一个复杂类型Address,所以针对Contact类型的Model元数据树具有两个层级。

    1:   public   class  Contact
    2:  {
    3:       public   string  Name { get; set; }
    4:       public   string  PhoneNo { get; set; }
    5:       public   string  EmailAddress { get; set; }
    6:       public  Address Address { get; set; }
    7:  }
    8:   public   class  Address
    9:  {
   10:       public   string  Province { get; set; }
   11:       public   string  City { get; set; }
   12:       public   string  District { get; set; }
   13:       public   string  Street { get; set; }
   14:  }

由于NameValueCollection中每个元数据的值都是一个字符串,所以不可能单独表示一个复杂类型,复杂类型对象需要通过多个元素值组装而成。如果通过NameValueCollectionValueProvider来初始化一个完整的Contact对象,表示数据源的NameValueCollection至少需要包含7个元素,分别针对Contact除Address属性的三个属性值和作为Address的四个属性值,两类元素在NameValueCollection中通过基于属性的前缀来区分,具体的结构如下所示。

    1:  Name:Foo
    2:  PhoneNo:123456789
    3:  EmailAddress:Foomail测试数据
    4:  Address.Province: 江苏
    5:  Address.City: 苏州
    6:  Address.District: 工业园区
    7:  Address.Street: 星湖街328号

将点号(.)作为分隔符的前缀除了表示基于属性的层级关系之外,还可以用于数据筛选。如下面的代码片断所示,我们在ContactController中定义了一个用于添加联系人的AddContacts,它具有两个Contact类型的参数foo和bar,表示添加的两个不同的联系人。

    1:   public   class  ContactController
    2:  {
    3:       public   void  AddContacts(Contact foo, Contact bar)
    4:      { 
    5:           //省略实现 
    6:      }
    7:  }

如果我们采用NameValueCollectionValueProvider来提供作为AddContacts方法参数的两个Contact对象,保存在NameValueCollection的数据元素必须能够与它们进行合理映射。一般情况下这可以通过针对参数名的前缀来实现,具体数据结构如下所示。

    1:  foo.Name:Foo
    2:  foo.PhoneNo:123456789
    3:  foo.EmailAddress:Foo@gmail测试数据
    4:  foo.Address.Province: 江苏
    5:  foo.Address.City: 苏州
    6:  foo.Address.District: 工业园区
    7:  foo.Address.Street: 星湖街328号
    8:   
    9:  bar.Name:Bar
   10:  bar.PhoneNo:987654321
   11:  bar.EmailAddress:Bar@gmail测试数据
   12:  bar.Address.Province: 江苏
   13:  bar.Address.City: 苏州
   14:  bar.Address.District: 工业园区
   15:  bar.Address.Street: 机场路328号

除了采用基于“.”的前缀之外,数组或者集合类型的数据源元素可以采用基于“索引”的前缀,这样的前缀通过方括号“[]”表示,如下的数据结构就可以表示包含两个元素的Contact数组或者集合。

    1:  [0].Name:Foo
    2:  [0].PhoneNo:123456789
    3:  [0].EmailAddress:Foo@gmail测试数据
    4:  ...
    5:  [1].Name:Bar
    6:  [1].PhoneNo:987654321
    7:  [1].EmailAddress:Bar@gmail测试数据
    8:  ...
    9:   

除了采用数字作为索引之前,我们还可以按照如下的方式通过文字作为索引。针对两种不同形式的索引的Model绑定机制有所不同,我们会在后续的部分予以讲述。

    1:  [foo].Name:Foo
    2:  [foo].PhoneNo:123456789
    3:  [foo].EmailAddress:Foo@gmail测试数据
    4:  ...
    5:  [bar].Name:Bar
    6:  [bar].PhoneNo:987654321
    7:  [bar].EmailAddress:Bar@gmail测试数据
    8:  ...

如果数据源元素针对不同的目标集合对象,同样需要采用相应的前缀予以区分,相面的数据结构可以看成是针对两个Contact列表(first和second)的数据源。

    1:  first[0].Name:Zhao
    2:  first[0].PhoneNo:12
    3:  first[0].EmailAddress:zhao @gmail测试数据
    4:  ...
    5:  first[1].Name:Qian
    6:  first[1].PhoneNo:34
    7:  first[1].EmailAddress:qian@gmail测试数据
    8:  ...
    9:   
   10:  second[0].Name:Sun
   11:  second[0].PhoneNo:56
   12:  second[0].EmailAddress:sun@gmail测试数据
   13:  ...
   14:  second[1].Name:Li
   15:  second[1].PhoneNo:78
   16:  second[1].EmailAddress:li@gmail测试数据


四、实例演示:返回指定前缀的Key

在了解两种不同类型的前缀之后,我们来关注一下NameValueCollectionValueProvider实现的GetKeysFromPrefix方法。从该方法的定义可以看出它返回的是一个IDictionary<string, string>对象,但是这个对象具有怎样的数据呢?我们为此来进行一个实例演示。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们定义了如下一个默认的HomeController。在Action方法Index中我们创建了一个NameValueCollection对象,并针对它创建一个NameValueCollectionValueProvider.

    1:   public   class  HomeController : Controller
    2:  {
    3:       public   void  Index()
    4:      {
    5:          NameValueCollection datasource =  new  NameValueCollection();
    6:          datasource.Add( "foo.Name" ,  "Foo" );
    7:          datasource.Add( "foo.PhoneNo" ,  "123456789" );
    8:          datasource.Add( "foo.EmailAddress" ,  "Foo@gmail测试数据" );
    9:          datasource.Add( "foo.Address.Province" ,  "江苏" );
   10:          datasource.Add( "foo.Address.City" ,  "苏州" );
   11:          datasource.Add( "foo.Address.District" ,  "工业园区" );
   12:          datasource.Add( "foo.Address.Street" ,  "星湖街328号" );
   13:          NameValueCollectionValueProvider valueProvider =  new  NameValueCollectionValueProvider(datasource, CultureInfo.InvariantCulture);
   14:   
   15:          var keyDictionary = valueProvider.GetKeysFromPrefix( "foo" );
   16:          Response.Write( "foo<br/>" );
   17:           foreach  (var item  in  keyDictionary)
   18:          {
   19:              Response.Write( string .Format( "{0}: {1}<br/>" , item.Key, item.Value));
   20:          }
   21:   
   22:          keyDictionary = valueProvider.GetKeysFromPrefix( "foo.Address" );
   23:          Response.Write( "<br/>foo.Address<br/>" );
   24:           foreach  (var item  in  keyDictionary)
   25:          {
   26:              Response.Write( string .Format( "{0}: {1}<br/>" , item.Key, item.Value));
   27:          }
   28:      }
   29:  }

通过上面的代码片断可以看出,作为NameValueCollectionValueProvider的数据元素是按照Contact类型的属性定义来添加的。我们分别将“foo”和“foo.Address”作为前缀返回以此作为前缀的Key。运行该程序后会在浏览器上得到如下的输出结果。我们可以看到对于针对指定前缀返回的字典对象,其Key和Value的不同之处在于前者没有包含指定的前缀而后者包含。此外,字典对象包含的元素全部处于同一级别,将“foo”指定为前缀时返回的元素针对于Contact的四个属性。虽然NameValueCollection中并不包含一个名为“foo.Address”的元素,但是依然会将其单独作为以“foo”为前缀的Key。

    1:  foo
    2:  Name            : foo.Name
    3:  PhoneNo         : foo.PhoneNo
    4:  EmailAddress    : foo.EmailAddress
    5:  Address         : foo.Address
    6:   
    7:  foo.Address
    8:  Province        : foo.Address.Province
    9:  City            : foo.Address.City
   10:  District        : foo.Address.District
   11:  Street          : foo.Address.Street

接下来我们采用相应的方式来演示基于索引的前缀,为此我们将HomeController的Index反方法进行了如下的改写。作为数据源的NameValueCollection对象针对一个包含两个元素的Contact集合,前缀“first”可以作为集合对象的名称。

    1:   public   class  HomeController : Controller
    2:  {
    3:       public   void  Index()
    4:      {
    5:          NameValueCollection datasource =  new  NameValueCollection();
    6:          datasource.Add( "first[0].Name" ,  "Foo" );
    7:          datasource.Add( "first[0].PhoneNo" ,  "123456789" );
    8:          datasource.Add( "first[0].EmailAddress" ,  "Foo@gmail测试数据" );
    9:   
   10:          datasource.Add( "first[1].Name" ,  "Bar" );
   11:          datasource.Add( "first[1].PhoneNo" ,  "987654321" );
   12:          datasource.Add( "first[1].EmailAddress" ,  "Bar@gmail测试数据" );
   13:          NameValueCollectionValueProvider valueProvider =  new  NameValueCollectionValueProvider(datasource, CultureInfo.InvariantCulture);
   14:   
   15:          var keyDictionary = valueProvider.GetKeysFromPrefix( "first" );
   16:          Response.Write( "first<br/>" );
   17:           foreach  (var item  in  keyDictionary)
   18:          {
   19:              Response.Write( string .Format( "{0}: {1}<br/>" , item.Key, item.Value));
   20:          }
   21:   
   22:          keyDictionary = valueProvider.GetKeysFromPrefix( "first[0]" );
   23:          Response.Write( "<br/>first[0]<br/>" );
   24:           foreach  (var item  in  keyDictionary)
   25:          {
   26:              Response.Write( string .Format( "{0}: {1}<br/>" , item.Key, item.Value));
   27:          }
   28:   
   29:          keyDictionary = valueProvider.GetKeysFromPrefix( "first[1]" );
   30:          Response.Write( "<br/>first[1]<br/>" );
   31:           foreach  (var item  in  keyDictionary)
   32:          {
   33:              Response.Write( string .Format( "{0}: {1}<br/>" , item.Key, item.Value));
   34:          }
   35:      }
   36:  }

我们分别针对三个前缀“first”、“first[0]”和“first[1]”获取相应字典对象并将其Key和Value呈现出来。该程序执行之后会在浏览器中产生如下的输出,如果我们将“[”和“]”视为和”.”一样的分割符,GetKeysFromPrefix针对索引作为前缀的规则与基于“.”前缀的规则没有本质的区别。

    1:  first
    2:  0: first[0]
    3:  1: first[1]
    4:   
    5:  first[0]
    6:  Name        : first[0].Name
    7:  PhoneNo     : first[0].PhoneNo
    8:  EmailAddress: first[0].EmailAddress
    9:   
   10:  first[1]
   11:  Name        : first[1].Name
   12:  PhoneNo     : first[1].PhoneNo
   13:  EmailAddress: first[1].EmailAddress


五、FormValueProvider与QueryStringValueProvider

在ASP.NET MVC 应用编程接口中,NameValueCollectionValueProvider具有两个继承者,即 FormValueProvider 和 QueryStringValueProvider 。对于FormValueProvider来说,最终作为数据源的NameValueCollection对象通过请求表单创建,Name和Value分别来源于表单元素的名称和值,它的定义基本上可以通过如下的代码表示(实际定义有所差异)。

    1:   public   sealed   class  FormValueProvider : NameValueCollectionValueProvider
    2:  {
    3:       public  FormValueProvider(ControllerContext controllerContext)
    4:          :  base (controllerContext.RequestContext.HttpContext.Request.Form, CultureInfo.CurrentCulture)
    5:      { }
    6:  }

对于QueryStringValueProvider来说,无须多说,其作为数据源的NameValueCollection对象爱那个自然来源于请求的查询字符串,其定义基本上可以通过如下的代码表示(实际定义有所差异)。

    1:   public   sealed   class  QueryStringValueProvider: NameValueCollectionValueProvider
    2:  {
    3:       public  NameValueCollection(ControllerContext controllerContext)
    4:          :  base (controllerContext.RequestContext.HttpContext.Request.QueryString, CultureInfo.CurrentCulture)
    5:      { }
    6:  }

ASP.NET MVC以ValueProvider为核心的值提供系统: NameValueCollectionValueProvider  
ASP.NET MVC以ValueProvider为核心的值提供系统: DictionaryValueProvider  
ASP.NET MVC以ValueProvider为核心的值提供系统: ValueProviderFactory

作者: Artech
出处: http://artech.cnblogs测试数据/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于ASP.NET MVC以ValueProvider为核心的值提供系统: NameValueColle的详细内容...

  阅读:49次