在進行Model綁定過程中
需要根據基於Action方法參數的綁定上下文從請求數據中提取相應的數據以提供相應的數據
具體來說
Model綁定的數據具有多個來源
可能來源於Post的表單或者JSON字符串
或者來源於當前的路由數據
也可能來源於請求地址的插敘字符串
ASP
NET MVC將這種基於不同數據來源的數據獲取/提供機制實現在一個叫做ValueProvider的組件中
一
IValueProvider與ValueProviderResult
一般來講
一個ValueProvider采用的數據源是一個字典類型的數據結構
我們通過它從這個字典中獲取一個Key與當前綁定上下文匹配的值
ValueProvider實現了具有如下定義的接口IValueProvider
GetValue方法根據指定的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:
: Address
Province: 江蘇
: Address
City: 蘇州
: Address
District: 工業園區
: Address
Street: 星湖街
號
將點號(
)作為分隔符的前綴除了表示基於屬性的層級關系之外
還可以用於數據篩選
如下面的代碼片斷所示
我們在ContactController中定義了一個用於添加聯系人的AddContacts
它具有兩個Contact類型的參數foo和bar
表示添加的兩個不同的聯系人
: public class ContactController
: {
: public void AddContacts(Contact foo
Contact bar)
: {
: //省略實現
: }
: }
如果我們采用NameValueCollectionValueProvider來提供作為AddContacts方法參數的兩個Contact對象
保存在NameValueCollection的數據元素必須能夠與它們進行合理映射
一般情況下這可以通過針對參數名的前綴來實現
具體數據結構如下所示
: foo
Name:Foo
: foo
PhoneNo:
: foo
EmailAddress:
: foo
Address
Province: 江蘇
: foo
Address
City: 蘇州
: foo
Address
District: 工業園區
: foo
Address
Street: 星湖街
號
:
: bar
Name:Bar
: bar
PhoneNo:
: bar
EmailAddress:
: bar
Address
Province: 江蘇
: bar
Address
City: 蘇州
: bar
Address
District: 工業園區
: bar
Address
Street: 機場路
號
除了采用基於
的前綴之外
數組或者集合類型的數據源元素可以采用基於
索引
的前綴
這樣的前綴通過方括號
[]
表示
如下的數據結構就可以表示包含兩個元素的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的ASP
NET MVC項目模板創建的空Web應用中
我們定義了如下一個默認的HomeController
在Action方法Index中我們創建了一個NameValueCollection對象
並針對它創建一個NameValueCollectionValueProvider
: public class HomeController : Controller
: {
: public void Index()
: {
: NameValueCollection datasource = new NameValueCollection()
: datasource
Add(
foo
Name
Foo
)
: datasource
Add(
foo
PhoneNo
)
: datasource
Add(
foo
EmailAddress
)
: datasource
Add(
foo
Address
Province
江蘇
)
: datasource
Add(
foo
Address
City
蘇州
)
: datasource
Add(
foo
Address
District
工業園區
)
: datasource
Add(
foo
Address
Street
星湖街
號
)
: NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(datasource
CultureInfo
InvariantCulture)
:
: var keyDictionary = valueProvider
GetKeysFromPrefix(
foo
)
: Response
Write(
foo<br/>
)
: foreach (var item in keyDictionary)
: {
: Response
Write(string
Format(
{
}: {
}<br/>
item
Key
item
Value))
: }
:
: keyDictionary = valueProvider
GetKeysFromPrefix(
foo
Address
)
: Response
Write(
<br/>foo
Address<br/>
)
: foreach (var item in keyDictionary)
: {
: Response
Write(string
Format(
{
}: {
}<br/>
item
Key
item
Value))
: }
: }
: }
通過上面的代碼片斷可以看出
作為NameValueCollectionValueProvider的數據元素是按照Contact類型的屬性定義來添加的
我們分別將
foo
和
foo
Address
作為前綴返回以此作為前綴的Key
運行該程序後會在浏覽器上得到如下的輸出結果
我們可以看到對於針對指定前綴返回的字典對象
其Key和Value的不同之處在於前者沒有包含指定的前綴而後者包含
此外
字典對象包含的元素全部處於同一級別
將
foo
指定為前綴時返回的元素針對於Contact的四個屬性
雖然NameValueCollection中並不包含一個名為
foo
Address
的元素
但是依然會將其單獨作為以
foo
為前綴的Key
: foo
: Name : foo
Name
: PhoneNo : foo
PhoneNo
: EmailAddress : foo
EmailAddress
: Address : foo
Address
:
: foo
Address
: Province : foo
Address
Province
: City : foo
Address
City
: District : foo
Address
District
: Street : foo
Address
Street
接下來我們采用相應的方式來演示基於索引的前綴
為此我們將HomeController的Index反方法進行了如下的改寫
作為數據源的NameValueCollection對象針對一個包含兩個元素的Contact集合
前綴
first
可以作為集合對象的名稱
: public class HomeController : Controller
: {
: public void Index()
: {
: NameValueCollection datasource = new NameValueCollection()
: datasource
Add(
first[
]
Name
Foo
)
: datasource
Add(
first[
]
PhoneNo
)
: datasource
Add(
first[
]
EmailAddress
)
:
: datasource
Add(
first[
]
Name
Bar
)
: datasource
Add(
first[
]
PhoneNo
)
: datasource
Add(
first[
]
EmailAddress
)
: NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(datasource
CultureInfo
InvariantCulture)
:
: var keyDictionary = valueProvider
GetKeysFromPrefix(
first
)
: Response
Write(
first<br/>
)
: foreach (var item in keyDictionary)
: {
: Response
Write(string
Format(
{
}: {
}<br/>
item
Key
item
Value))
: }
:
: keyDictionary = valueProvider
GetKeysFromPrefix(
first[
]
)
: Response
Write(
<br/>first[
]<br/>
)
: foreach (var item in keyDictionary)
: {
: Response
Write(string
Format(
{
}: {
}<br/>
item
Key
item
Value))
: }
:
: keyDictionary = valueProvider
GetKeysFromPrefix(
first[
]
)
: Response
Write(
<br/>first[
]<br/>
)
: foreach (var item in keyDictionary)
: {
: Response
Write(string
Format(
{
}: {
}<br/>
item
Key
item
Value))
: }
: }
: }
我們分別針對三個前綴
first
first[
]
和
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
在ASP
NET MVC 應用編程接口中
NameValueCollectionValueProvider具有兩個繼承者
即FormValueProvider和QueryStringValueProvider
對於FormValueProvider來說
最終作為數據源的NameValueCollection對象通過請求表單創建
Name和Value分別來源於表單元素的名稱和值
它的定義基本上可以通過如下的代碼表示(實際定義有所差異)
: public sealed class FormValueProvider : NameValueCollectionValueProvider
: {
: public FormValueProvider(ControllerContext controllerContext)
: : base(controllerContext
RequestContext
HttpContext
Request
Form
CultureInfo
CurrentCulture)
: { }
: }
對於QueryStringValueProvider來說
無須多說
其作為數據源的NameValueCollection對象愛那個自然來源於請求的查詢字符串
其定義基本上可以通過如下的代碼表示(實際定義有所差異)
: public sealed class QueryStringValueProvider: NameValueCollectionValueProvider
: {
: public NameValueCollection(ControllerContext controllerContext)
: : base(controllerContext
RequestContext
HttpContext
Request
QueryString
CultureInfo
CurrentCulture)
: { }
: }
From:http://tw.wingwit.com/Article/program/net/201311/13339.html