在进行Model绑定过程中需要根据基于Action方法参数的绑定上下文从请求数据中提取相应的数据以提供相应的数据具体来说Model绑定的数据具有多个来源可能来源于Post的表单或者JSON字符串或者来源于当前的路由数据也可能来源于请求地址的插叙字符串ASPNET MVC将这种基于不同数据来源的数据获取/提供机制实现在一个叫做ValueProvider的组件中 一IValueProvider与ValueProviderResult 一般来讲一个ValueProvider采用的数据源是一个字典类型的数据结构我们通过它从这个字典中获取一个Key与当前绑定上下文匹配的值ValueProvider实现了具有如下定义的接口IValueProviderGetValue方法根据指定的Key从数据源中获取对应的值对象这个Key是基于当前绑定上下文的这个Key和存在于数据源中对应数据条目的Key可能并非完全一致后者可能在前者基础上添加相应的前缀而ContainsPrefix方法用于判断数据源字典的Key是否具有指定的前缀 : public interface IValueProvider : { : bool ContainsPrefix(string prefix) : ValueProviderResult GetValue(string key) : } IValueProvider的GetValue返回的是一个ValueProviderResult对象我们可以将ValueProviderResult看成是对ValueProvider提供对象的封装如下面的代码片断所示ValueProviderResult具有三个只读属性其中RawValue表示原始的值对象而AttemptedValue表示以值对象的字符串表示该属性主要用于显示 : [Serializable] : public class ValueProviderResult : { : public ValueProviderResult(object rawValue string attemptedValue CultureInfo culture) : public object ConvertTo(Type type) : public virtual object ConvertTo(Type type CultureInfo culture) : : public string AttemptedValue { get; } : public CultureInfo Culture { get; } : public object RawValue { get; } : } ValueProviderResult提供了两个ConvertTo方法重载以实现向指定目标类型的转换某些类型的格式化行为依赖于相应的语言文化(比如时间日期和货币等)而这个辅助格式湖的语言文化信息通过Culture属性表示其中第一个ValueProviderResult方法重载通过属性Culture表示的语言文化进行类型转化 二NameValueCollectionValueProvider 前面已经说过Model数据源一般具有类似于字典的结构而NameValueCollection可以表示为Key不具有唯一性的字典将NameValueCollection对象作为数据源的ValueProvider通过具有如下定义的NameValueCollectionValueProvider类型表示表示数据源的NameValueCollection对象在构造函数中指定构造函数的另一个CultureInfo类型的参数表示服务于数据转换的语言文化信息 : public class NameValueCollectionValueProvider : IUnvalidatedValueProvider IEnumerableValueProvider IValueProvider : { : //其他成员 : public NameValueCollectionValueProvider(NameValueCollection collection CultureInfo culture) : : 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) : } : : public interface IEnumerableValueProvider : IValueProvider : { : IDictionary<string string> GetKeysFromPrefix(string prefix) : } : : public interface IUnvalidatedValueProvider : IValueProvider : { : ValueProviderResult GetValue(string key bool skipValidation) : } 从上面的代码片断我们可以看到除了IValueProvider接口NameValueCollectionValueProvider还实现了IEnumerableValueProvider和IUnvalidatedValueProvider两个接口顾名思义IEnumerableValueProvider主要用于针对目标类型为集合的数据提供方法GetKeysFromPrefix以一字典的形式返回具有指定前缀的Key在默认的情况下在进行数据提供的同时会对数据进行验证而IUnvalidatedValueProvider接口提供了一个额外的GetValue方法是我们可以忽略对数据的验证 三两种前缀形式 辅助实现Model绑定的数据提供机制是以Model元数据为基础的通过《初识Model元数据》我们知道用于描述一个复杂数据类型的Model元数据具有一个树型的层次化结构而作为数据源的NameValueCollection却是一个扁平的结构两者之前的匹配通过前缀来表示举个简单的例子假设通过NameValueCollectionValueProvider提供对象的目标类型为具有如下定义的Contact表示联系地址的属性是一个复杂类型Address所以针对Contact类型的Model元数据树具有两个层级 : public class Contact : { : public string Name { get; set; } : public string PhoneNo { get; set; } : public string EmailAddress { get; set; } : public Address Address { get; set; } : } : public class Address : { : public string Province { get; set; } : public string City { get; set; } : public string District { get; set; } : public string Street { get; set; } : } 由于NameValueCollection中每个元数据的值都是一个字符串所以不可能单独表示一个复杂类型复杂类型对象需要通过多个元素值组装而成如果通过NameValueCollectionValueProvider来初始化一个完整的Contact对象表示数据源的NameValueCollection至少需要包含个元素分别针对Contact除Address属性的三个属性值和作为Address的四个属性值两类元素在NameValueCollection中通过基于属性的前缀来区分具体的结构如下所示 : Name:Foo : PhoneNo: : EmailAddress: : AddressProvince: 江苏 : AddressCity: 苏州 : AddressDistrict: 工业园区 : AddressStreet: 星湖街号 将点号()作为分隔符的前缀除了表示基于属性的层级关系之外还可以用于数据筛选如下面的代码片断所示我们在ContactController中定义了一个用于添加联系人的AddContacts它具有两个Contact类型的参数foo和bar表示添加的两个不同的联系人 : public class ContactController : { : public void AddContacts(Contact foo Contact bar) : { : //省略实现 : } : } 如果我们采用NameValueCollectionValueProvider来提供作为AddContacts方法参数的两个Contact对象保存在NameValueCollection的数据元素必须能够与它们进行合理映射一般情况下这可以通过针对参数名的前缀来实现具体数据结构如下所示 : fooName:Foo : fooPhoneNo: : fooEmailAddress: : fooAddressProvince: 江苏 : fooAddressCity: 苏州 : fooAddressDistrict: 工业园区 : fooAddressStreet: 星湖街号 : : barName:Bar : barPhoneNo: : barEmailAddress: : barAddressProvince: 江苏 : barAddressCity: 苏州 : barAddressDistrict: 工业园区 : barAddressStreet: 机场路号 除了采用基于的前缀之外数组或者集合类型的数据源元素可以采用基于索引的前缀这样的前缀通过方括号[]表示如下的数据结构就可以表示包含两个元素的Contact数组或者集合 : []Name:Foo : []PhoneNo: : []EmailAddress: : … : []Name:Bar : []PhoneNo: : []EmailAddress: : … : 除了采用数字作为索引之前我们还可以按照如下的方式通过文字作为索引针对两种不同形式的索引的Model绑定机制有所不同我们会在后续的部分予以讲述 : [foo]Name:Foo : [foo]PhoneNo: : [foo]EmailAddress: : … : [bar]Name:Bar : [bar]PhoneNo: : [bar]EmailAddress: : … 如果数据源元素针对不同的目标集合对象同样需要采用相应的前缀予以区分相面的数据结构可以看成是针对两个Contact列表(first和second)的数据源 : first[]Name:Zhao : first[]PhoneNo: : first[]EmailAddress:zhao : … : first[]Name:Qian : first[]PhoneNo: : first[]EmailAddress: : … : : second[]Name:Sun : second[]PhoneNo: : second[]EmailAddress: : … : second[]Name:Li : second[]PhoneNo: : second[]EmailAddress: 四实例演示返回指定前缀的Key 在了解两种不同类型的前缀之后我们来关注一下NameValueCollectionValueProvider实现的GetKeysFromPrefix方法从该方法的定义可以看出它返回的是一个IDictionary<string string>对象但是这个对象具有怎样的数据呢?我们为此来进行一个实例演示在通过Visual Studio的ASPNET MVC项目模板创建的空Web应用中我们定义了如下一个默认的HomeController在Action方法Index中我们创建了一个NameValueCollection对象并针对它创建一个NameValueCollectionValueProvider : public class HomeController : Controller : { : public void Index() : { : NameValueCollection datasource = new NameValueCollection() : datasourceAdd(fooName Foo) : datasourceAdd(fooPhoneNo ) : datasourceAdd(fooEmailAddress ) : datasourceAdd(fooAddressProvince 江苏) : datasourceAdd(fooAddressCity 苏州) : datasourceAdd(fooAddressDistrict 工业园区) : datasourceAdd(fooAddressStreet 星湖街号) : NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(datasource CultureInfoInvariantCulture) : : var keyDictionary = valueProviderGetKeysFromPrefix(foo) : ResponseWrite(foo<br/>) : foreach (var item in keyDictionary) : { : ResponseWrite(stringFormat({}: {}<br/> itemKey itemValue)) : } : : keyDictionary = valueProviderGetKeysFromPrefix(fooAddress) : ResponseWrite(<br/>fooAddress<br/>) : foreach (var item in keyDictionary) : { : ResponseWrite(stringFormat({}: {}<br/> itemKey itemValue)) : } : } : } 通过上面的代码片断可以看出作为NameValueCollectionValueProvider的数据元素是按照Contact类型的属性定义来添加的我们分别将foo和fooAddress作为前缀返回以此作为前缀的Key运行该程序后会在浏览器上得到如下的输出结果我们可以看到对于针对指定前缀返回的字典对象其Key和Value的不同之处在于前者没有包含指定的前缀而后者包含此外字典对象包含的元素全部处于同一级别将foo指定为前缀时返回的元素针对于Contact的四个属性虽然NameValueCollection中并不包含一个名为fooAddress的元素但是依然会将其单独作为以foo为前缀的Key : foo : Name : fooName : PhoneNo : fooPhoneNo : EmailAddress : fooEmailAddress : Address : fooAddress : : fooAddress : Province : fooAddressProvince : City : fooAddressCity : District : fooAddressDistrict : Street : fooAddressStreet 接下来我们采用相应的方式来演示基于索引的前缀为此我们将HomeController的Index反方法进行了如下的改写作为数据源的NameValueCollection对象针对一个包含两个元素的Contact集合前缀first可以作为集合对象的名称 : public class HomeController : Controller : { : public void Index() : { : NameValueCollection datasource = new NameValueCollection() : datasourceAdd(first[]Name Foo) : datasourceAdd(first[]PhoneNo ) : datasourceAdd(first[]EmailAddress ) : : datasourceAdd(first[]Name Bar) : datasourceAdd(first[]PhoneNo ) : datasourceAdd(first[]EmailAddress ) : NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(datasource CultureInfoInvariantCulture) : : var keyDictionary = valueProviderGetKeysFromPrefix(first) : ResponseWrite(first<br/>) : foreach (var item in keyDictionary) : { : ResponseWrite(stringFormat({}: {}<br/> itemKey itemValue)) : } : : keyDictionary = valueProviderGetKeysFromPrefix(first[]) : ResponseWrite(<br/>first[]<br/>) : foreach (var item in keyDictionary) : { : ResponseWrite(stringFormat({}: {}<br/> itemKey itemValue)) : } : : keyDictionary = valueProviderGetKeysFromPrefix(first[]) : ResponseWrite(<br/>first[]<br/>) : foreach (var item in keyDictionary) : { : ResponseWrite(stringFormat({}: {}<br/> itemKey itemValue)) : } : } : } 我们分别针对三个前缀firstfirst[]和first[]获取相应字典对象并将其Key和Value呈现出来该程序执行之后会在浏览器中产生如下的输出如果我们将[和]视为和一样的分割符GetKeysFromPrefix针对索引作为前缀的规则与基于前缀的规则没有本质的区别 : first : : first[] : : first[] : : first[] : Name : first[]Name : PhoneNo : first[]PhoneNo : EmailAddress: first[]EmailAddress : : first[] : Name : first[]Name : PhoneNo : first[]PhoneNo : EmailAddress: first[]EmailAddress 五FormValueProvider与QueryStringValueProvider 在ASPNET MVC 应用编程接口中NameValueCollectionValueProvider具有两个继承者即FormValueProvider和QueryStringValueProvider对于FormValueProvider来说最终作为数据源的NameValueCollection对象通过请求表单创建Name和Value分别来源于表单元素的名称和值它的定义基本上可以通过如下的代码表示(实际定义有所差异) : public sealed class FormValueProvider : NameValueCollectionValueProvider : { : public FormValueProvider(ControllerContext controllerContext) : : base(controllerContextRequestContextHttpContextRequestForm CultureInfoCurrentCulture) : { } : } 对于QueryStringValueProvider来说无须多说其作为数据源的NameValueCollection对象爱那个自然来源于请求的查询字符串其定义基本上可以通过如下的代码表示(实际定义有所差异) : public sealed class QueryStringValueProvider: NameValueCollectionValueProvider : { : public NameValueCollection(ControllerContext controllerContext) : : base(controllerContextRequestContextHttpContextRequestQueryString CultureInfoCurrentCulture) : { } : } |